diff --git a/COPY.md b/COPY.md index 474a9e8f..d3474c3c 100644 --- a/COPY.md +++ b/COPY.md @@ -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` | diff --git a/app/components/CliInstallChip.vue b/app/components/CliInstallChip.vue index 8e50a060..a018972d 100644 --- a/app/components/CliInstallChip.vue +++ b/app/components/CliInstallChip.vue @@ -1,12 +1,16 @@ + + diff --git a/app/components/RunChip.vue b/app/components/RunChip.vue index c6176963..23452f7c 100644 --- a/app/components/RunChip.vue +++ b/app/components/RunChip.vue @@ -1,6 +1,7 @@ + + + + diff --git a/layers/marketing/app/pages/cli/index.vue b/layers/marketing/app/pages/cli/index.vue index 6cbcaca9..1ca48b91 100644 --- a/layers/marketing/app/pages/cli/index.vue +++ b/layers/marketing/app/pages/cli/index.vue @@ -14,7 +14,6 @@ * the parent route of `/cli/authorize` and swallow the CLI sign-in page. */ import { - cliNativeInstallCmd, skillInstallCmd, skillOutdatedCmd, skillPageUrl, @@ -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 = [ @@ -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.

-
- -

- No Node.js? On macOS and Linux, install one native binary: -

- -
+ @@ -352,7 +344,7 @@ const codeClass = 'font-mono text-xs text-default' One native binary
- 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.
diff --git a/layers/marketing/content/pages/docs/cli.md b/layers/marketing/content/pages/docs/cli.md index 52ae213a..40b1a3c8 100644 --- a/layers/marketing/content/pages/docs/cli.md +++ b/layers/marketing/content/pages/docs/cli.md @@ -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 ` or install the CLI once with `npm install --global skilld`. +Generated from `skilld --help` for skilld `3.3.0`. Run `npx skilld `, 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. diff --git a/nuxt.config.ts b/nuxt.config.ts index 1d37c69d..00bc5087 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -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[] { @@ -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/`. 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. diff --git a/scripts/generate-cli-docs.ts b/scripts/generate-cli-docs.ts index 89c3c9d8..408807d2 100644 --- a/scripts/generate-cli-docs.ts +++ b/scripts/generate-cli-docs.ts @@ -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' @@ -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 \` or install the CLI once with \`npm install --global skilld\`.`, + `Generated from \`skilld --help\` for skilld \`${version}\`. Run \`npx skilld \`, 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.', '', diff --git a/scripts/lib/production-smoke.ts b/scripts/lib/production-smoke.ts index 8fc1a850..95a6208b 100644 --- a/scripts/lib/production-smoke.ts +++ b/scripts/lib/production-smoke.ts @@ -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 @@ -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. // diff --git a/shared/skill-commands.ts b/shared/skill-commands.ts index ba2960fc..a980d5fc 100644 --- a/shared/skill-commands.ts +++ b/shared/skill-commands.ts @@ -88,17 +88,38 @@ export function skillRemoveCmd(name: string): string { return `${CLI_PREFIX} remove ${name}` } +const CLI_RELEASE_DOWNLOAD = 'https://github.com/skilld-dev/skilld/releases/latest/download' + +/** + * The native install scripts, by the site path that serves them. Each path is a + * 302 to the latest release asset, so the printed command stays short and + * GitHub Releases stays the source. `latest` moves, so it is never a 301. + */ +export const CLI_INSTALL_SCRIPTS = { + '/install.sh': `${CLI_RELEASE_DOWNLOAD}/install.sh`, + '/install.ps1': `${CLI_RELEASE_DOWNLOAD}/install.ps1`, +} as const + /** * Installs the CLI itself. npm runs it, so it is no skilld subcommand and the - * grammar gate does not check it. The package selects a native executable. + * grammar gate does not check it. The package selects a native executable, and + * npm owns its upgrades. */ export function cliGlobalInstallCmd(): string { return 'npm install --global skilld' } -/** Installs the CLI as one native binary in `~/.skilld/bin`, with no Node.js. macOS and Linux. */ +/** + * Installs the CLI as one native binary in `~/.skilld/bin`, with no Node.js. + * macOS and Linux. This install upgrades itself from signed releases. + */ export function cliNativeInstallCmd(): string { - return 'curl -fsSL https://github.com/skilld-dev/skilld/releases/latest/download/install.sh | sh' + return `curl -fsSL ${SITE_ORIGIN}/install.sh | sh` +} + +/** The same native binary on Windows, from PowerShell, in `%LOCALAPPDATA%\skilld\bin`. */ +export function cliWindowsInstallCmd(): string { + return `irm ${SITE_ORIGIN}/install.ps1 | iex` } /** diff --git a/test/a11y.spec.ts b/test/a11y.spec.ts index 8ba32428..a89c2477 100644 --- a/test/a11y.spec.ts +++ b/test/a11y.spec.ts @@ -571,7 +571,7 @@ describe('accessibility: components', () => { wrapper.unmount() }) - it('cliInstallChip has no violations and names what its copy button copies', async () => { + it('cliInstallChip has no violations, leads with the native install, and switches platform', async () => { const container = createIsolatedContainer() const wrapper = await mountSuspended( await loadComponent('CliInstallChip'), @@ -579,8 +579,18 @@ describe('accessibility: components', () => { ) const results = await runAxe(container) expect(results.violations, formatViolations(results)).toHaveLength(0) + expect(container.querySelector('code')?.textContent).toBe('curl -fsSL https://skilld.dev/install.sh | sh') + expect(container.querySelector('button[aria-label]')?.getAttribute('aria-label')).toBe('Copy CLI install command') + + const option = (label: string) => wrapper.findAll('button[aria-pressed]').find(button => button.text() === label)! + await option('Windows').trigger('click') + await nextTick() + expect(container.querySelector('code')?.textContent).toBe('irm https://skilld.dev/install.ps1 | iex') + + await option('npm').trigger('click') + await nextTick() expect(container.querySelector('code')?.textContent).toBe('npm install --global skilld') - expect(container.querySelector('button')?.getAttribute('aria-label')).toBe('Copy CLI install command') + expect(container.textContent).toContain('Needs Node.js.') wrapper.unmount() }) @@ -642,6 +652,7 @@ describe('accessibility: component coverage', () => { 'StatsHBar', // Decorative chart, tested at page level 'StatsLeaderboard', // Tested at page level 'UiTooltip', // Wrapper around UTooltip, exercised by parent components + '_ChipSwitch', // The switch above RunChip and CliInstallChip, axe-scanned and clicked through both ] /**