From 6f229cbf1f38951bca57b8191770b896a881a851 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Wed, 7 Oct 2026 15:29:45 +1100 Subject: [PATCH] fix(install): make bulk handoff commands explicit --- layers/mcp/shared/mcp-install-command.ts | 6 +++--- shared/skill-commands.ts | 6 +++--- test/unit/api-v1-collections.test.ts | 6 +++--- test/unit/api-v1-registry.test.ts | 2 +- test/unit/install-cmd-bare-checkout.test.ts | 2 +- test/unit/install-cmd.test.ts | 6 +++--- test/unit/mcp-discovery.test.ts | 10 +++++----- test/unit/skill-commands.test.ts | 11 +++++++++++ 8 files changed, 30 insertions(+), 19 deletions(-) diff --git a/layers/mcp/shared/mcp-install-command.ts b/layers/mcp/shared/mcp-install-command.ts index 777eaeb2..9ace8e7e 100644 --- a/layers/mcp/shared/mcp-install-command.ts +++ b/layers/mcp/shared/mcp-install-command.ts @@ -30,15 +30,15 @@ export function skillInstallCommand(owner: string, repo: string, skill: string): /** Whole-repository install. `add` takes every ref that names several Skills. */ export function repoInstallCommand(owner: string, repo: string): string { - return `${PREFIX} ${owner}/${repo}` + return `${PREFIX} ${owner}/${repo} --all` } export function curatorInstallCommand(login: string): string { - return `${PREFIX} @${login}` + return `${PREFIX} @${login} --all` } export function collectionInstallCommand(login: string, slug: string): string { - return `${PREFIX} @${login}/${slug}` + return `${PREFIX} @${login}/${slug} --all` } export type InstallRef diff --git a/shared/skill-commands.ts b/shared/skill-commands.ts index a980d5fc..0793dc81 100644 --- a/shared/skill-commands.ts +++ b/shared/skill-commands.ts @@ -15,17 +15,17 @@ const ADD_PREFIX = `${CLI_PREFIX} add` /** Every Skill one GitHub Repository carries. */ export function gitInstallCmd(owner: string, repo: string): string { - return `${ADD_PREFIX} ${owner}/${repo}` + return `${ADD_PREFIX} ${owner}/${repo} --all` } /** Every Skill one curator's collections name. */ export function curatorInstallCmd(handle: string): string { - return `${ADD_PREFIX} @${handle}` + return `${ADD_PREFIX} @${handle} --all` } /** Every Skill one collection names. */ export function collectionInstallCmd(handle: string, slug: string): string { - return `${ADD_PREFIX} @${handle}/${slug}` + return `${ADD_PREFIX} @${handle}/${slug} --all` } function skillRef(owner: string, repo: string, skill: string): string { diff --git a/test/unit/api-v1-collections.test.ts b/test/unit/api-v1-collections.test.ts index 35740747..0087e954 100644 --- a/test/unit/api-v1-collections.test.ts +++ b/test/unit/api-v1-collections.test.ts @@ -100,7 +100,7 @@ describe('presentCollection', () => { it('answers the collection address and a null avatar for an empty one', () => { expect(answer.pageUrl).toBe('https://skilld.dev/@harlan-zw/design-engineering-essentials') - expect(answer.installCommand).toBe('npx skilld add @harlan-zw/design-engineering-essentials') + expect(answer.installCommand).toBe('npx skilld add @harlan-zw/design-engineering-essentials --all') expect(answer.curator).toEqual({ login: 'harlan-zw', name: 'Harlan Wilton', avatarUrl: null }) }) }) @@ -113,13 +113,13 @@ describe('presentCurator', () => { it('answers the curator with an install command for each collection', () => { const answer = presentCurator({ login: 'harlan-zw', name: null, avatar: 'https://avatars.githubusercontent.com/u/1' }, collections) expect(curatorsV1.operations.get.response.body.producer.safeParse(answer).success).toBe(true) - expect(answer.installCommand).toBe('npx skilld add @harlan-zw') + expect(answer.installCommand).toBe('npx skilld add @harlan-zw --all') expect(answer.collections).toEqual([{ slug: 'stack', title: 'Stack', description: 'Intro', pageUrl: 'https://skilld.dev/@harlan-zw/stack', - installCommand: 'npx skilld add @harlan-zw/stack', + installCommand: 'npx skilld add @harlan-zw/stack --all', skillCount: 3, }]) }) diff --git a/test/unit/api-v1-registry.test.ts b/test/unit/api-v1-registry.test.ts index 9a514524..f96b3be1 100644 --- a/test/unit/api-v1-registry.test.ts +++ b/test/unit/api-v1-registry.test.ts @@ -141,7 +141,7 @@ describe('repositories.get', () => { pushedAt: '2026-09-28T14:02:11.000Z', repositoryUrl: 'https://github.com/vercel/skills', pageUrl: 'https://skilld.dev/gh/vercel-labs/agent-skills', - installCommand: 'npx skilld add vercel-labs/agent-skills', + installCommand: 'npx skilld add vercel-labs/agent-skills --all', }) expect(answer.skills.map(skill => skill.name)).toEqual(['beta', 'alpha']) }) diff --git a/test/unit/install-cmd-bare-checkout.test.ts b/test/unit/install-cmd-bare-checkout.test.ts index 600dd78b..b2a0cc4e 100644 --- a/test/unit/install-cmd-bare-checkout.test.ts +++ b/test/unit/install-cmd-bare-checkout.test.ts @@ -46,7 +46,7 @@ it('prints the install command from a checkout with no .nuxt', { timeout: 15_000 const reason = message.split('\n').find(line => line.includes('Cannot find module')) ?? message.split('\n')[0] throw new Error(`gate entry failed in a bare checkout: ${reason}`) } - expect(stdout.trim()).toBe('npx skilld add nuxt/nuxt') + expect(stdout.trim()).toBe('npx skilld add nuxt/nuxt --all') } finally { await rm(dir, { recursive: true, force: true }) diff --git a/test/unit/install-cmd.test.ts b/test/unit/install-cmd.test.ts index 82d3d07e..dedd83f1 100644 --- a/test/unit/install-cmd.test.ts +++ b/test/unit/install-cmd.test.ts @@ -8,9 +8,9 @@ import { describe('install commands', () => { it.each([ - [gitInstallCmd('nuxt', 'nuxt'), 'npx skilld add nuxt/nuxt'], - [curatorInstallCmd('harlan-zw'), 'npx skilld add @harlan-zw'], - [collectionInstallCmd('harlan-zw', 'nuxt'), 'npx skilld add @harlan-zw/nuxt'], + [gitInstallCmd('nuxt', 'nuxt'), 'npx skilld add nuxt/nuxt --all'], + [curatorInstallCmd('harlan-zw'), 'npx skilld add @harlan-zw --all'], + [collectionInstallCmd('harlan-zw', 'nuxt'), 'npx skilld add @harlan-zw/nuxt --all'], ])('returns %s', (command, expected) => { expect(command).toBe(expected) }) diff --git a/test/unit/mcp-discovery.test.ts b/test/unit/mcp-discovery.test.ts index 3f294bc3..ebdbd6bd 100644 --- a/test/unit/mcp-discovery.test.ts +++ b/test/unit/mcp-discovery.test.ts @@ -161,13 +161,13 @@ describe('mCP public SDK discovery', () => { describe('install_command', () => { it.each([ - ['gh:nuxt/nuxt', 'npx skilld add nuxt/nuxt'], - ['nuxt/nuxt', 'npx skilld add nuxt/nuxt'], + ['gh:nuxt/nuxt', 'npx skilld add nuxt/nuxt --all'], + ['nuxt/nuxt', 'npx skilld add nuxt/nuxt --all'], ['anthropics/skills/skill-creator', 'npx skilld install anthropics/skills/skill-creator'], ['skilld:anthropics/skills/skill-creator', 'npx skilld install anthropics/skills/skill-creator'], ['gh:anthropics/skills/skill-creator', 'npx skilld install anthropics/skills/skill-creator'], - ['@harlan-zw', 'npx skilld add @harlan-zw'], - ['@harlan-zw/nuxt-stack', 'npx skilld add @harlan-zw/nuxt-stack'], + ['@harlan-zw', 'npx skilld add @harlan-zw --all'], + ['@harlan-zw/nuxt-stack', 'npx skilld add @harlan-zw/nuxt-stack --all'], ])('%s -> %s', async (ref, command) => { const result = await runTool('install_command', { ref }) expect((result.structuredContent as any).command).toBe(command) @@ -200,7 +200,7 @@ describe('install_command', () => { describe('parseInstallRef', () => { it('round-trips every ref kind through installCommandFor', () => { - expect(installCommandFor(parseInstallRef('a/b')!)).toBe('npx skilld add a/b') + expect(installCommandFor(parseInstallRef('a/b')!)).toBe('npx skilld add a/b --all') expect(parseInstallRef('a/b/c/d')).toBeNull() expect(parseInstallRef('@')).toBeNull() expect(parseInstallRef('')).toBeNull() diff --git a/test/unit/skill-commands.test.ts b/test/unit/skill-commands.test.ts index 98bb22e9..31525d48 100644 --- a/test/unit/skill-commands.test.ts +++ b/test/unit/skill-commands.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from 'vitest' import { + collectionInstallCmd, + curatorInstallCmd, + gitInstallCmd, skillInstallCmd, skillOutdatedCmd, skillPageUrl, @@ -12,6 +15,14 @@ import { } from '../../shared/skill-commands' describe('skill commands', () => { + it.each([ + [gitInstallCmd('antfu', 'skills'), 'npx skilld add antfu/skills --all'], + [curatorInstallCmd('harlan-zw'), 'npx skilld add @harlan-zw --all'], + [collectionInstallCmd('harlan-zw', 'stack'), 'npx skilld add @harlan-zw/stack --all'], + ])('makes bulk installation explicit: %s', (command, expected) => { + expect(command).toBe(expected) + }) + it.each([ [skillRunCmd('nuxt', 'nuxt', 'seo'), 'npx skilld run nuxt/nuxt/seo'], [skillInstallCmd('nuxt', 'nuxt', 'seo'), 'npx skilld install nuxt/nuxt/seo'],