diff --git a/cli/bin/commands/skills.mjs b/cli/bin/commands/skills.mjs index e15904104..db1c13633 100644 --- a/cli/bin/commands/skills.mjs +++ b/cli/bin/commands/skills.mjs @@ -2362,7 +2362,25 @@ async function downloadFile(url, dest, { fetchImpl = globalThis.fetch } = {}) { } } +function printUpdateUsage() { + console.log(`Usage: impeccable update [options] + +Update installed impeccable skills to the latest version. + +Options: + --project, --user Update the project-level or user-level install + -y, --yes Skip confirmation prompts + --force Overwrite existing hook manifests + --no-hooks Refresh skills without installing or repairing hooks + -h, --help Show this help message`); +} + async function update(flags = []) { + if (flags.includes('--help') || flags.includes('-h')) { + printUpdateUsage(); + return; + } + const yes = flags.includes('-y') || flags.includes('--yes'); const force = flags.includes('--force'); const installHooks = !flags.includes('--no-hooks'); diff --git a/tests/skills-cli.test.js b/tests/skills-cli.test.js index 6524cea5a..5887258f8 100644 --- a/tests/skills-cli.test.js +++ b/tests/skills-cli.test.js @@ -864,6 +864,38 @@ describe('skills install/update: local universal bundle e2e', () => { expect(output).not.toContain('skills install Install impeccable skills'); }); + test('update --help is read-only and never downloads (#699)', () => { + const missingBundle = join(tmpdir(), 'imp-missing-bundle-699-does-not-exist'); + const envBase = { ...process.env, IMPECCABLE_BUNDLE_PATH: missingBundle }; + + const tmp = mkdtempSync(join(tmpdir(), 'imp-test-update-help-699-')); + const home = mkdtempSync(join(tmpdir(), 'imp-home-update-help-699-')); + createFakeSkills(tmp, ['impeccable'], ['.claude']); + const skillPath = join(tmp, '.claude', 'skills', 'impeccable', 'SKILL.md'); + const beforeContent = readFileSync(skillPath, 'utf8'); + + for (const args of ['update --help', 'update -h', 'skills update --help']) { + const output = run(args, { cwd: tmp, env: { ...envBase, HOME: home } }); + expect(output).toContain('Usage: impeccable update'); + expect(output).not.toContain('Checking for updates'); + expect(output).not.toContain('Updating the'); + } + expect(readFileSync(skillPath, 'utf8')).toBe(beforeContent); + + rmSync(tmp, { recursive: true, force: true }); + rmSync(home, { recursive: true, force: true }); + + const emptyTmp = mkdtempSync(join(tmpdir(), 'imp-test-update-help-empty-699-')); + const emptyHome = mkdtempSync(join(tmpdir(), 'imp-home-update-help-empty-699-')); + const output = run('update --help', { cwd: emptyTmp, env: { ...envBase, HOME: emptyHome } }); + expect(output).toContain('Usage: impeccable update'); + expect(output).not.toContain('Run `npx impeccable install` to install first.'); + expect(output).not.toContain('Checking for updates'); + + rmSync(emptyTmp, { recursive: true, force: true }); + rmSync(emptyHome, { recursive: true, force: true }); + }); + test('top-level install aliases the legacy skills install command', () => { const tmp = mkdtempSync(join(tmpdir(), 'imp-test-top-level-install-')); const home = mkdtempSync(join(tmpdir(), 'imp-home-top-level-install-'));