diff --git a/src/cli/agent-help.ts b/src/cli/agent-help.ts index 20ba9dc..a818d25 100644 --- a/src/cli/agent-help.ts +++ b/src/cli/agent-help.ts @@ -18,7 +18,6 @@ import { skillsAddMeta, skillsRemoveMeta, skillsSearchMeta, - skillsUpdateMeta, } from './metadata/plugin-skills.js'; const allCommands: AgentCommandMeta[] = [ @@ -38,7 +37,6 @@ const allCommands: AgentCommandMeta[] = [ skillsAddMeta, skillsRemoveMeta, skillsSearchMeta, - skillsUpdateMeta, updateMeta, ]; diff --git a/src/cli/commands/plugin-skills.ts b/src/cli/commands/plugin-skills.ts index 2d4caaf..10e705a 100644 --- a/src/cli/commands/plugin-skills.ts +++ b/src/cli/commands/plugin-skills.ts @@ -29,7 +29,6 @@ import { skillsRemoveMeta, skillsAddMeta, skillsSearchMeta, - skillsUpdateMeta, } from '../metadata/plugin-skills.js'; import { searchSkills, @@ -41,7 +40,6 @@ import { isGitHubUrl, parseGitHubUrl, stripGitRef } from '../../utils/plugin-pat import { fetchPlugin, getPluginName, seedFetchCache } from '../../core/plugin.js'; import { computeSkillFolderHash, - loadSyncState, upsertSyncStateSource, upsertSyncStateSkill, } from '../../core/sync-state.js'; @@ -433,10 +431,8 @@ const removeCmd = command({ process.exit(1); } - // Run sync if (!isJsonMode()) { console.log(`\u2713 Disabled skill: ${skill} (${targetSkill.pluginName})`); - console.log('\nSyncing workspace...\n'); } const syncResult = isUser ? await syncUserWorkspace() : await syncWorkspace(workspacePath); @@ -457,8 +453,6 @@ const removeCmd = command({ if (!syncResult.success) process.exit(1); return; } - - console.log('Sync complete.'); } catch (error) { if (error instanceof Error) { if (isJsonMode()) { @@ -692,10 +686,8 @@ async function applySkillAllowlist(opts: { } } - // Sync to apply the allowlist if (!isJsonMode()) { console.log(`\u2713 Enabled skill: ${skill} (${pluginName})`); - console.log('\nSyncing workspace...\n'); } const syncResult = isUser ? await syncUserWorkspace() : await syncWorkspace(workspacePath); @@ -857,7 +849,6 @@ async function installAllSkillsFromSource(opts: { if (!isJsonMode()) { console.log(`✓ Enabled ${skillNames.length} skill(s) from ${pluginName}: ${skillNames.join(', ')}`); - console.log('\nSyncing workspace...\n'); } const syncResult = isUser ? await syncUserWorkspace() : await syncWorkspace(workspacePath); @@ -967,7 +958,6 @@ async function installAllViaMarketplace(opts: { if (!isJsonMode()) { const total = installed.reduce((sum, i) => sum + i.skills.length, 0); console.log(`✓ Enabled ${total} skill(s) across ${installed.length} plugin(s)`); - console.log('\nSyncing workspace...\n'); } const syncResult = isUser ? await syncUserWorkspace() : await syncWorkspace(workspacePath); @@ -1304,7 +1294,6 @@ const addCmd = command({ if (pinnedRef) { console.log(`Pinned to ${pinnedRef}.`); } - console.log('Sync complete.'); return; } @@ -1375,10 +1364,8 @@ const addCmd = command({ process.exit(1); } - // Run sync if (!isJsonMode()) { console.log(`\u2713 Enabled skill: ${skill} (${targetSkill.pluginName})`); - console.log('\nSyncing workspace...\n'); } const syncResult = isUser ? await syncUserWorkspace() : await syncWorkspace(workspacePath); @@ -1399,8 +1386,6 @@ const addCmd = command({ if (!syncResult.success) process.exit(1); return; } - - console.log('Sync complete.'); } catch (error) { if (error instanceof Error) { if (isJsonMode()) { @@ -1517,221 +1502,6 @@ const searchCmd = command({ }, }); -// ============================================================================= -// skill update -// ============================================================================= - -/** - * Output row for skill update (per skill, both for human print and JSON). - */ -type SkillUpdateRow = { - skill: string; - source: string; - from: string; - to: string; - status: 'up-to-date' | 'available' | 'updated' | 'pinned' | 'skipped'; -}; - -const updateCmd = command({ - name: 'update', - description: buildDescription(skillsUpdateMeta), - args: { - skill: positional({ type: optional(string), displayName: 'skill' }), - all: flag({ long: 'all', description: 'Apply updates without prompting (default in non-TTY).' }), - force: flag({ long: 'force', description: 'Re-download even when content hashes match.' }), - dryRun: flag({ long: 'dry-run', description: 'Report drift without writing any files.' }), - unpin: flag({ long: 'unpin', description: 'Clear pinnedRef before resolving (move to latest).' }), - scope: option({ - type: optional(string), - long: 'scope', - short: 's', - description: 'Scope: "project" (default) or "user"', - }), - }, - handler: async ({ skill: skillFilter, all, force, dryRun, unpin, scope }) => { - try { - const isUser = scope === 'user' || (!scope && resolveScope(process.cwd()) === 'user'); - const workspacePath = isUser ? getHomeDir() : process.cwd(); - - const state = await loadSyncState(workspacePath); - const sources = state?.sources ?? {}; - - // Build the list of (source, skill) tuples to consider. When a skill - // name is supplied, only entries matching that name are processed. - type Candidate = { - sourceKey: string; - source: import('../../models/sync-state.js').SyncStateSource; - skillName: string; - recordedHash: string; - }; - const candidates: Candidate[] = []; - for (const [key, source] of Object.entries(sources)) { - if (!source.skills) continue; - for (const [skillName, entry] of Object.entries(source.skills)) { - if (skillFilter && skillName !== skillFilter) continue; - candidates.push({ - sourceKey: key, - source, - skillName, - recordedHash: entry.contentHash, - }); - } - } - - const updates: SkillUpdateRow[] = []; - const upToDate: string[] = []; - const pinned: string[] = []; - - // Non-TTY default: skip skills that would otherwise prompt; equivalent - // to running with --all from the user's perspective. - const applyImplicitly = all || !process.stdin.isTTY; - - for (const cand of candidates) { - const currentlyPinned = Boolean(cand.source.pinnedRef); - if (currentlyPinned && !unpin) { - pinned.push(cand.skillName); - continue; - } - - // Resolve upstream (cached fetch). When --unpin, drop the pin from the - // spec so we resolve against the default branch. - const spec = cand.sourceKey; - const fetchOpts = unpin - ? {} - : cand.source.pinnedRef - ? { branch: cand.source.pinnedRef } - : {}; - const fetchResult = await fetchPlugin(spec, fetchOpts); - if (!fetchResult.success || !fetchResult.resolvedSha) { - updates.push({ - skill: cand.skillName, - source: cand.sourceKey, - from: cand.source.resolvedSha.slice(0, 7), - to: '?', - status: 'skipped', - }); - continue; - } - - const pluginRoot = fetchResult.cachePath; - const folder = resolveSkillFolder(pluginRoot, cand.skillName); - const upstreamHash = folder ? await computeSkillFolderHash(folder) : null; - const matches = upstreamHash !== null && upstreamHash === cand.recordedHash; - - if (matches && !force) { - upToDate.push(cand.skillName); - continue; - } - - const fromShort = cand.source.resolvedSha.slice(0, 7); - const toShort = fetchResult.resolvedSha.slice(0, 7); - - if (dryRun) { - updates.push({ - skill: cand.skillName, - source: cand.sourceKey, - from: fromShort, - to: toShort, - status: 'available', - }); - continue; - } - - // Apply: refresh the source provenance entry and re-hash the skill. - if (applyImplicitly) { - const now = new Date().toISOString(); - await upsertSyncStateSource(workspacePath, cand.sourceKey, { - pluginSpec: cand.sourceKey, - resolvedRef: fetchResult.resolvedRef ?? cand.source.resolvedRef, - resolvedSha: fetchResult.resolvedSha, - ...(unpin - ? {} - : cand.source.pinnedRef - ? { pinnedRef: cand.source.pinnedRef } - : {}), - }); - if (upstreamHash) { - await upsertSyncStateSkill(workspacePath, cand.sourceKey, cand.skillName, { - contentHash: upstreamHash, - installedAt: cand.source.skills?.[cand.skillName]?.installedAt ?? now, - updatedAt: now, - }); - } - updates.push({ - skill: cand.skillName, - source: cand.sourceKey, - from: fromShort, - to: toShort, - status: 'updated', - }); - } else { - // No --all in TTY: report rather than apply, leaving the choice to a follow-up. - updates.push({ - skill: cand.skillName, - source: cand.sourceKey, - from: fromShort, - to: toShort, - status: 'available', - }); - } - } - - const checked = candidates.length; - if (isJsonMode()) { - jsonOutput({ - success: true, - command: 'skill update', - data: { - checked, - updates, - upToDate, - pinned, - ...(dryRun && { dryRun: true }), - }, - }); - return; - } - - if (checked === 0) { - console.log('No installed skills with recorded content hashes found.'); - return; - } - - console.log(`Checking ${checked} skill(s)...`); - for (const s of upToDate) { - console.log(` ${chalk.green('✓')} ${s.padEnd(28)} up to date`); - } - for (const u of updates) { - const marker = - u.status === 'updated' ? chalk.cyan('↑') - : u.status === 'available' ? chalk.yellow('*') - : chalk.dim('-'); - const tail = - u.status === 'updated' ? `${u.from} → ${u.to} (updated)` - : u.status === 'available' ? `${u.from} → ${u.to} (would update)` - : u.status; - console.log(` ${marker} ${u.skill.padEnd(28)} ${tail}`); - } - for (const p of pinned) { - console.log(` ${chalk.dim('-')} ${p.padEnd(28)} pinned, skipping (--unpin to override)`); - } - console.log( - `\n${upToDate.length} up to date, ${updates.length} update${updates.length === 1 ? '' : 's'} ${dryRun ? 'available' : 'reported'}, ${pinned.length} pinned`, - ); - } catch (error) { - if (error instanceof Error) { - if (isJsonMode()) { - jsonOutput({ success: false, command: 'skill update', error: error.message }); - process.exit(1); - } - console.error(`Error: ${error.message}`); - process.exit(1); - } - throw error; - } - }, -}); - // ============================================================================= // skill subcommands group (canonical singular; `skills` is a CLI alias) // ============================================================================= @@ -1744,6 +1514,5 @@ export const skillsCmd = conciseSubcommands({ remove: removeCmd, add: addCmd, search: searchCmd, - update: updateCmd, }, }); diff --git a/src/cli/metadata/plugin-skills.ts b/src/cli/metadata/plugin-skills.ts index 7065b14..fa62736 100644 --- a/src/cli/metadata/plugin-skills.ts +++ b/src/cli/metadata/plugin-skills.ts @@ -71,51 +71,6 @@ export const skillsSearchMeta: AgentCommandMeta = { }, }; -export const skillsUpdateMeta: AgentCommandMeta = { - command: 'skill update', - description: 'Refresh installed skills against their source (per-skill, separate from workspace sync)', - whenToUse: - 'To check or apply updates for installed skills without re-syncing the entire workspace. Pinned skills are skipped unless --unpin is passed.', - examples: [ - 'allagents skill update', - 'allagents skill update brainstorming', - 'allagents skill update --all', - 'allagents skill update --dry-run', - 'allagents skill update --force --all', - 'allagents skill update git-commit --unpin', - ], - expectedOutput: 'Per-skill report of up-to-date / updates available / pinned', - positionals: [ - { - name: 'skill', - type: 'string', - required: false, - description: 'Optional skill name. Omit to update across every installed skill.', - }, - ], - options: [ - { flag: '--all', type: 'boolean', description: 'Apply updates without prompting (default in non-TTY).' }, - { flag: '--force', type: 'boolean', description: 'Re-download even when content hashes match.' }, - { flag: '--dry-run', type: 'boolean', description: 'Report drift without writing any files.' }, - { flag: '--unpin', type: 'boolean', description: 'Clear any pinnedRef before resolving (move to latest).' }, - { flag: '--scope', short: '-s', type: 'string', description: 'Scope: "project" (default) or "user".' }, - ], - outputSchema: { - checked: 'number', - updates: [ - { - skill: 'string', - source: 'string', - from: 'string', - to: 'string', - status: 'string', - }, - ], - upToDate: ['string'], - pinned: ['string'], - }, -}; - export const skillsAddMeta: AgentCommandMeta = { command: 'skill add', description: 'Add a skill from a plugin, or re-enable a previously disabled skill', diff --git a/tests/unit/cli/agent-help.test.ts b/tests/unit/cli/agent-help.test.ts index cf76f45..a2b6c22 100644 --- a/tests/unit/cli/agent-help.test.ts +++ b/tests/unit/cli/agent-help.test.ts @@ -18,7 +18,6 @@ import { skillsAddMeta, skillsRemoveMeta, skillsSearchMeta, - skillsUpdateMeta, } from '../../../src/cli/metadata/plugin-skills.js'; import type { AgentCommandMeta } from '../../../src/cli/help.js'; @@ -39,7 +38,6 @@ const allCommands: AgentCommandMeta[] = [ skillsAddMeta, skillsRemoveMeta, skillsSearchMeta, - skillsUpdateMeta, updateMeta, ]; @@ -70,8 +68,8 @@ describe('extractAgentHelpFlag', () => { }); describe('agent command metadata', () => { - test('contains exactly 18 commands', () => { - expect(allCommands.length).toBe(18); + test('contains exactly 17 commands', () => { + expect(allCommands.length).toBe(17); }); test('all expected commands are present', () => { @@ -91,7 +89,6 @@ describe('agent command metadata', () => { 'skill list', 'skill remove', 'skill search', - 'skill update', 'update', 'workspace init', 'workspace status',