Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion COPY.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ These exact strings. Do not paraphrase them per page.
| CLI line | Search, run, install, and keep Skills current. | guidance only: the `/cli` line under the H1. It is the CLI's own `--help` line, so change it in the CLI first |
| CLI intro | Give your Agent Skills that real maintainers write. A run reads the current source every time, and one command updates the Skills you install. Install the skilld Skill once, and your Agent searches and loads Skills on its own. | guidance only: the `/cli` intro under the line |
| CLI claims line | Open-source CLI Β· No telemetry Β· 19 Agent targets | guidance only: the `/cli` claims line. The page counts the `--agent` values of `skilld install --help` |
| CLI install label | Install the CLI | guidance only: the label beside `npm install --global skilld` on `/cli` |
| CLI install label | Install the CLI | guidance only: the label beside the CLI install chip on `/cli`. A switch above the chip picks `macOS / Linux`, `Windows`, or `npm`, and `macOS / Linux` is preselected |
| CLI install consequence | One native binary. It upgrades itself from signed releases. Β· Needs Node.js. npm handles upgrades. | guidance only: the line under the CLI install chip on `/cli`. The first is for `macOS / Linux` and `Windows`, the second for `npm`. `crates/skilld-command/src/upgrade.rs` in the CLI decides both. If it changes, change these strings |
| Claim: open source | open-source CLI | guidance only: the CLI. Never call skilld.dev or its site open source |
| Claim: privacy | analytics without cookies or IPs | guidance only: analytics and privacy copy. Never the bare "privacy-friendly" |
| Claim: comparison | skills.sh alternative | guidance only: always a link to `/vs/skills-sh` |
Expand Down
69 changes: 61 additions & 8 deletions app/components/CliInstallChip.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
<script setup lang="ts">
import type { InstallTarget } from '~/composables/useInstallCopy'
import { cliGlobalInstallCmd } from '#shared/skill-commands'
import { cliGlobalInstallCmd, cliNativeInstallCmd, cliWindowsInstallCmd } from '#shared/skill-commands'
import ChipSwitch from './_ChipSwitch.vue'
import CommandChip from './_CommandChip.vue'

/**
* The solid install chip for the CLI itself: one global npm install. It sits
* beside `SkilldInstallChip` on the `/cli` page, which teaches the Agent to
* drive what this one installs.
* The solid install chip for the CLI itself. It sits beside
* `SkilldInstallChip` on the `/cli` page, which teaches the Agent to drive
* what this one installs.
*
* A platform switch sits above it, with the native installer preselected: that
* install upgrades itself, and an npm install waits for npm to upgrade it.
*/
const { surface, quiet = false } = defineProps<{
/** The analytics surface, such as `cli-hero`. */
Expand All @@ -15,14 +19,40 @@ const { surface, quiet = false } = defineProps<{
quiet?: boolean
}>()

const command = cliGlobalInstallCmd()
type Platform = 'unix' | 'windows' | 'npm'

const NATIVE_CONSEQUENCE = 'One native binary. It upgrades itself from signed releases.'

const PLATFORMS: Record<Platform, { label: string, command: string, consequence: string }> = {
unix: { label: 'macOS / Linux', command: cliNativeInstallCmd(), consequence: NATIVE_CONSEQUENCE },
windows: { label: 'Windows', command: cliWindowsInstallCmd(), consequence: NATIVE_CONSEQUENCE },
npm: { label: 'npm', command: cliGlobalInstallCmd(), consequence: 'Needs Node.js. npm handles upgrades.' },
}

const options = (Object.keys(PLATFORMS) as Platform[]).map(value => ({ value, label: PLATFORMS[value].label }))

const platform = ref<Platform>('unix')
const rootRef = ref<HTMLElement>()
const consequenceId = useId()

const command = computed(() => PLATFORMS[platform.value].command)
// The CLI is the `skilld-dev/skilld` Repository, not a Skill, so copies count as a repo target.
const target: InstallTarget = { kind: 'repo', owner: 'skilld-dev', repo: 'skilld' }
const consequenceId = useId()

function choose(next: Platform) {
if (next === platform.value)
return
platform.value = next
// A fallback selection would now cover the other command, so drop it.
const selection = window.getSelection()
if (selection?.anchorNode && rootRef.value?.contains(selection.anchorNode))
selection.removeAllRanges()
}
</script>

<template>
<div class="min-w-0">
<div ref="rootRef" class="cli-install-chip">
<ChipSwitch :options="options" :selected="platform" label="Platform" @select="choose" />
<CommandChip
:command="command"
mode="install"
Expand All @@ -33,6 +63,29 @@ const consequenceId = useId()
copy-label="Copy CLI install command"
:described-by="consequenceId"
/>
<span :id="consequenceId" class="sr-only">Installs the skilld CLI for your user account.</span>
<p :id="consequenceId" class="cli-install-chip__consequence" aria-live="polite">
{{ PLATFORMS[platform].consequence }}
</p>
</div>
</template>

<style scoped>
.cli-install-chip {
display: grid;
gap: 0.625rem;
justify-items: start;
min-width: 0;
}

.cli-install-chip > :deep(.command-chip) {
justify-self: stretch;
}

.cli-install-chip__consequence {
margin: 0;
font-family: var(--font-mono);
font-size: 0.75rem;
line-height: 1.45;
color: var(--ui-text-muted);
}
</style>
61 changes: 6 additions & 55 deletions app/components/RunChip.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { InstallCopyMode, InstallTarget } from '~/composables/useInstallCopy'
import { skillInstallCmd, skillRunCmd } from '#shared/skill-commands'
import ChipSwitch from './_ChipSwitch.vue'
import CommandChip from './_CommandChip.vue'

/**
Expand Down Expand Up @@ -78,23 +79,15 @@ function choose(next: InstallCopyMode) {
ref="rootRef"
class="run-chip"
>
<div class="run-chip__switch" role="group" aria-label="Command">
<button
v-for="item in modes"
:key="item.value"
type="button"
class="run-chip__option"
:aria-pressed="mode === item.value"
@click="choose(item.value)"
>
<ChipSwitch :options="modes" :selected="mode" label="Command" @select="choose">
<template #lead="{ value }">
<span
class="run-chip__legend"
:class="item.value === 'install' && 'run-chip__legend--filled'"
:class="value === 'install' && 'run-chip__legend--filled'"
aria-hidden="true"
/>
{{ item.label }}
</button>
</div>
</template>
</ChipSwitch>
<CommandChip
:command="command"
:mode="mode"
Expand Down Expand Up @@ -124,42 +117,6 @@ function choose(next: InstallCopyMode) {
display: block;
}

.run-chip__switch {
display: inline-flex;
padding: 2px;
border: 1px solid var(--ui-border-accented);
border-radius: var(--ui-radius);
background: var(--ui-bg);
}

.run-chip__option {
display: inline-flex;
align-items: center;
gap: 0.4375rem;
min-height: 1.875rem;
padding: 0 0.75rem;
border: 0;
border-radius: calc(var(--ui-radius) - 2px);
background: none;
color: var(--ui-text-muted);
font-family: var(--font-mono);
font-size: 0.75rem;
cursor: pointer;
transition: color 200ms ease-out, background-color 200ms ease-out;
}

@media (hover: hover) {
.run-chip__option:hover {
color: var(--ui-text);
}
}

.run-chip__option[aria-pressed='true'] {
background: var(--ui-bg-muted);
color: var(--ui-text);
font-weight: 500;
}

/* The same dots as the chip, in ink: a legend, so the chip keeps the only rose dot. */
.run-chip__legend {
flex: none;
Expand All @@ -180,10 +137,4 @@ function choose(next: InstallCopyMode) {
line-height: 1.45;
color: var(--ui-text-muted);
}

@media (pointer: coarse) {
.run-chip__option {
min-height: 2.75rem;
}
}
</style>
75 changes: 75 additions & 0 deletions app/components/_ChipSwitch.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<script setup lang="ts" generic="T extends string">
/**
* The switch above a command chip. Each option is a toggle button, so a screen
* reader hears which command the chip holds. The `lead` slot draws a mark
* before each label, such as the run chip's legend dots.
*/
const { options, selected, label } = defineProps<{
options: readonly { value: T, label: string }[]
selected: T
/** Names the group, such as `Command`. */
label: string
}>()

const emit = defineEmits<{ select: [value: T] }>()
</script>

<template>
<div class="chip-switch" role="group" :aria-label="label">
<button
v-for="item in options"
:key="item.value"
type="button"
class="chip-switch__option"
:aria-pressed="selected === item.value"
@click="emit('select', item.value)"
>
<slot name="lead" :value="item.value" />
{{ item.label }}
</button>
</div>
</template>

<style scoped>
.chip-switch {
display: inline-flex;
padding: 2px;
border: 1px solid var(--ui-border-accented);
border-radius: var(--ui-radius);
background: var(--ui-bg);
}

.chip-switch__option {
display: inline-flex;
align-items: center;
gap: 0.4375rem;
min-height: 1.875rem;
padding: 0 0.75rem;
border: 0;
border-radius: calc(var(--ui-radius) - 2px);
background: none;
color: var(--ui-text-muted);
font-family: var(--font-mono);
font-size: 0.75rem;
cursor: pointer;
transition: color 200ms ease-out, background-color 200ms ease-out;
}

@media (hover: hover) {
.chip-switch__option:hover {
color: var(--ui-text);
}
}

.chip-switch__option[aria-pressed='true'] {
background: var(--ui-bg-muted);
color: var(--ui-text);
font-weight: 500;
}

@media (pointer: coarse) {
.chip-switch__option {
min-height: 2.75rem;
}
}
</style>
12 changes: 2 additions & 10 deletions layers/marketing/app/pages/cli/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
* the parent route of `/cli/authorize` and swallow the CLI sign-in page.
*/
import {
cliNativeInstallCmd,
skillInstallCmd,
skillOutdatedCmd,
skillPageUrl,
Expand Down Expand Up @@ -62,7 +61,6 @@ const EXAMPLE = { owner: 'antfu', repo: 'skills', skill: 'vue' } as const

const agentPrompt = skillRunPrompt(skillPageUrl(EXAMPLE.owner, EXAMPLE.repo, EXAMPLE.skill))
const askPrompt = 'Find a skilld Skill for Vue and use it'
const nativeInstall = cliNativeInstallCmd()

/** Each id is a stable anchor, so other pages can link one verb. */
const verbs = [
Expand Down Expand Up @@ -223,13 +221,7 @@ const codeClass = 'font-mono text-xs text-default'
Run the two commands at the top of this page. Then ask your Agent in your own words.
</p>
</div>
<div :class="stepActionClass">
<SetupSnippet :code="askPrompt" label="example prompt" />
<p class="mt-4 text-sm leading-relaxed text-muted">
No Node.js? On macOS and Linux, install one native binary:
</p>
<SetupSnippet class="mt-2" :code="nativeInstall" label="native install command" format="bash" />
</div>
<SetupSnippet :class="stepActionClass" :code="askPrompt" label="example prompt" />
</li>
</ol>
</div>
Expand Down Expand Up @@ -352,7 +344,7 @@ const codeClass = 'font-mono text-xs text-default'
One native binary
</dt>
<dd class="mt-2 text-sm leading-relaxed text-muted">
No runtime to install, and it starts in under a millisecond. The npm package selects the same native executable for your system.
No runtime to install, and it starts in under a millisecond. A curl or PowerShell install upgrades itself from signed releases. An npm install runs the same executable, and npm handles its upgrades.
</dd>
</div>
<div>
Expand Down
4 changes: 2 additions & 2 deletions layers/marketing/content/pages/docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ title: skilld CLI reference
description: Every skilld command with its help text. Generated from the CLI help for skilld 3.3.0.
label: Reference
author: Harlan Wilton
command: npm install --global skilld
command: curl -fsSL https://skilld.dev/install.sh | sh
publishedAt: 2026-09-01
updatedAt: 2026-10-02
---

Generated from `skilld --help` for skilld `3.3.0`. Run `npx skilld <command>` or install the CLI once with `npm install --global skilld`.
Generated from `skilld --help` for skilld `3.3.0`. Run `npx skilld <command>`, or install the CLI once. On macOS and Linux, run `curl -fsSL https://skilld.dev/install.sh | sh`. On Windows, run `irm https://skilld.dev/install.ps1 | iex` in PowerShell. With Node.js, run `npm install --global skilld`.

The npm package selects a native executable for your system. It has no JavaScript engine or fallback.

Expand Down
4 changes: 4 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { externalCheckin } from './shared/checkin-external'
import { iconifyCollections } from './shared/icon-collections'
import { SENTRY_DSN, sentryRelease, sentryReportingEnabled } from './shared/sentry'
import { SESSION_NAME } from './shared/server/session-access'
import { CLI_INSTALL_SCRIPTS } from './shared/skill-commands'

/** Every `/agents/*` page file, so the sitemap reads the admission decision for a page nobody listed. */
function discoveredAgentRoutes(): string[] {
Expand Down Expand Up @@ -617,6 +618,9 @@ export default defineNuxtConfig({
// .md (aiReady.llmsTxt.markdownLinks), so the dump redirects there. 302 so
// this reverses the moment per-section splitting is worth building.
'/llms-full.txt': { redirect: { to: '/llms.txt', statusCode: 302 } } as any,
// The printed native install commands. `CLI_INSTALL_SCRIPTS` says why each is a 302.
'/install.sh': { redirect: { to: CLI_INSTALL_SCRIPTS['/install.sh'], statusCode: 302 } } as any,
'/install.ps1': { redirect: { to: CLI_INSTALL_SCRIPTS['/install.ps1'], statusCode: 302 } } as any,
// `/gh` has no index page: owner hubs live at `/gh/<owner>`. It 404'd while
// `/orgs` 301'd straight into it, so every legacy orgs-index link dead-ended
// on a redirect chain. `/community` is the browsable owner surface.
Expand Down
5 changes: 3 additions & 2 deletions scripts/generate-cli-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { execFile } from 'node:child_process'
import { writeFile } from 'node:fs/promises'
import process from 'node:process'
import { promisify } from 'node:util'
import { cliGlobalInstallCmd, cliNativeInstallCmd, cliWindowsInstallCmd } from '../shared/skill-commands'

const run = promisify(execFile)
const PACKAGE = 'skilld'
Expand Down Expand Up @@ -74,12 +75,12 @@ export function renderCliDocs(version: string, root: string, commands: readonly
`description: Every skilld command with its help text. Generated from the CLI help for skilld ${version}.`,
'label: Reference',
'author: Harlan Wilton',
'command: npm install --global skilld',
`command: ${cliNativeInstallCmd()}`,
'publishedAt: 2026-09-01',
`updatedAt: ${today}`,
'---',
'',
`Generated from \`skilld --help\` for skilld \`${version}\`. Run \`npx skilld <command>\` or install the CLI once with \`npm install --global skilld\`.`,
`Generated from \`skilld --help\` for skilld \`${version}\`. Run \`npx skilld <command>\`, or install the CLI once. On macOS and Linux, run \`${cliNativeInstallCmd()}\`. On Windows, run \`${cliWindowsInstallCmd()}\` in PowerShell. With Node.js, run \`${cliGlobalInstallCmd()}\`.`,
'',
'The npm package selects a native executable for your system. It has no JavaScript engine or fallback.',
'',
Expand Down
4 changes: 4 additions & 0 deletions scripts/lib/production-smoke.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CLUSTER_BY_SLUG } from '../../layers/registry/server/data/clusters'
import { CLI_INSTALL_SCRIPTS } from '../../shared/skill-commands'

export interface SmokeExpectation {
path: string
Expand Down Expand Up @@ -112,6 +113,9 @@ export const PRODUCTION_SMOKE_EXPECTATIONS: SmokeExpectation[] = [
// The leaderboard is now the `all` range of the trending board. This checks
// the 301 that carries its ~4,100/mo repository cluster across.
{ path: '/skills/leaderboard', status: 301, location: '/skills/trending?range=all' },
// Every native install command fetches one of these, so a lost redirect
// breaks the install for each person who copies it.
...Object.entries(CLI_INSTALL_SCRIPTS).map(([path, location]) => ({ path, status: 302, location })),
// The category surface is the reason the rework exists, so it is checked for
// rendered content, not just a 200.
//
Expand Down
Loading