@swarmify/agents-cli 1.5.12 → 1.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,8 @@
1
1
  # @swarmify/agents-cli
2
2
 
3
- Your virtual environment manager for AI coding agents.
3
+ **One config for all your AI coding agents.** Sync CLIs, MCP servers, commands, hooks, and skills across Claude, Codex, Gemini, and Cursor.
4
4
 
5
- Homepage: https://swarmify.co/#agents-cli
6
- NPM: https://www.npmjs.com/package/@swarmify/agents-cli
7
- VS Code Extension: [Agents](https://marketplace.visualstudio.com/items?itemName=swarmify.swarm-ext) - full-screen agent terminals with sub-agent spawning
5
+ [Homepage](https://swarmify.co/#agents-cli) | [NPM](https://www.npmjs.com/package/@swarmify/agents-cli) | [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=swarmify.swarm-ext)
8
6
 
9
7
  ```bash
10
8
  npm install -g @swarmify/agents-cli
@@ -12,11 +10,39 @@ npm install -g @swarmify/agents-cli
12
10
 
13
11
  ## The Problem
14
12
 
15
- You spend hours configuring Claude Code: MCP servers, slash commands, hooks, skills. Then you switch to Codex or Gemini and start from scratch. Or you get a new machine and lose everything.
13
+ Each agent stores config differently. Different paths, different formats:
14
+
15
+ | What | Claude | Codex | Gemini |
16
+ |------|--------|-------|--------|
17
+ | Commands | `~/.claude/commands/` (md) | `~/.codex/prompts/` (md) | `~/.gemini/commands/` (TOML) |
18
+ | MCP config | `~/.claude/settings.json` | `~/.codex/config.json` | `~/.gemini/settings.json` |
19
+ | Hooks | `~/.claude/hooks/` | - | `~/.gemini/hooks/` |
20
+
21
+ You spend hours configuring Claude Code - MCP servers, slash commands, hooks, skills. Then you switch to Codex and start from scratch. Get a new machine and lose everything.
16
22
 
17
23
  ## The Solution
18
24
 
19
- One command to configure all your agents.
25
+ ```
26
+ .agents repo (GitHub)
27
+ |
28
+ +----------------+----------------+
29
+ | | |
30
+ agents.yaml commands/ hooks/
31
+ (CLIs, MCPs) (slash cmds) (scripts)
32
+ | | |
33
+ +----------------+----------------+
34
+ |
35
+ agents pull
36
+ |
37
+ +-------------------+-------------------+
38
+ | | |
39
+ ~/.claude/ ~/.codex/ ~/.gemini/
40
+ - commands/ (md) - prompts/ (md) - commands/ (TOML)
41
+ - hooks/ - config.json - hooks/
42
+ - settings.json - settings.json
43
+ ```
44
+
45
+ One repo. One command. All agents configured.
20
46
 
21
47
  ```bash
22
48
  # New machine? One command.
@@ -30,25 +56,26 @@ agents status
30
56
  Agent CLIs
31
57
 
32
58
  Claude Code 2.0.65
59
+ Codex 1.0.3
60
+ Gemini CLI 0.1.15
33
61
 
34
62
  Installed Commands
35
63
 
36
- Claude Code:
37
- User: clean, debug, plan, recap, ship, spawn, test, verify
38
- Project: eval
64
+ Claude Code: clean, debug, plan, ship, test
65
+ Codex: clean, debug, plan, ship, test
66
+ Gemini CLI: clean, debug, plan, ship, test
39
67
 
40
- Installed Skills
68
+ Installed MCP Servers
41
69
 
42
- Claude Code:
43
- User: remotion-best-practices, vercel-react-best-practices
70
+ All agents: Swarm, filesystem, memory
44
71
 
45
- Installed MCP Servers
72
+ Installed Hooks
46
73
 
47
- Claude Code:
48
- User: Swarm@latest, GoDaddy
74
+ Claude Code: pre-commit, post-tool
75
+ Gemini CLI: pre-commit, post-tool
49
76
  ```
50
77
 
51
- Your `.agents` repo becomes the source of truth for all your AI coding tools.
78
+ Write commands once in markdown - auto-converts to TOML for Gemini. Define MCP servers once - installs to all agents. Your `.agents` repo becomes the single source of truth.
52
79
 
53
80
  ## What Gets Synced
54
81
 
@@ -266,6 +293,64 @@ agents registry config mcp myregistry --api-key KEY
266
293
 
267
294
  Format conversion is automatic. Write commands in markdown, they're converted to TOML for Gemini.
268
295
 
296
+ ## Roadmap: Context Drives
297
+
298
+ Sync your docs, research, and chat history across machines and teams.
299
+
300
+ ```
301
+ ~/.agents/
302
+ drives/ # Context drives (synced)
303
+ work/
304
+ .context # Per-drive settings
305
+ docs/
306
+ research/
307
+ specs/
308
+ personal/
309
+ .context
310
+ notes/
311
+
312
+ sessions/ # Agent chat history (synced)
313
+ claude/
314
+ codex/
315
+ gemini/
316
+ ```
317
+
318
+ **Why not just Google Drive?**
319
+
320
+ | Feature | Google Drive | Context Drives |
321
+ |---------|--------------|----------------|
322
+ | Sync files | Yes | Yes |
323
+ | Real-time collab | Yes (Docs only) | Yes (CRDT for all files) |
324
+ | Agent session sync | No | Yes |
325
+ | Checkpointing | No | Yes (snapshot & rollback) |
326
+ | Per-directory conflict strategy | No | Yes (`.context` file) |
327
+ | Designed for AI agents | No | Yes |
328
+
329
+ **Conflict resolution strategies** (configurable per directory):
330
+
331
+ ```yaml
332
+ # .context file
333
+ strategy: crdt # Auto-merge (like Google Docs)
334
+ # strategy: git # Branch/PR/merge (for code)
335
+ # strategy: lock # Exclusive access
336
+ # strategy: last-write-wins # Don't care about conflicts
337
+
338
+ sync: realtime # or: on-demand, ignore
339
+ ```
340
+
341
+ **Planned commands:**
342
+
343
+ ```bash
344
+ agents drive create <name>
345
+ agents drive list
346
+ agents drive use <name>
347
+ agents drive sync
348
+ agents drive checkpoint "before refactor"
349
+ agents drive rollback <checkpoint>
350
+ ```
351
+
352
+ **Multi-agent coordination:** When multiple agents (or developers) work on the same drive, the drive acts as a coordination layer - checkout files, see who's working on what, avoid conflicts.
353
+
269
354
  ## Related
270
355
 
271
356
  - [@swarmify/agents-mcp](https://www.npmjs.com/package/@swarmify/agents-mcp) - MCP server for sub-agent spawning
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ import { cloneRepo, parseSource } from './lib/git.js';
24
24
  import { discoverCommands, resolveCommandSource, installCommand, uninstallCommand, listInstalledCommandsWithScope, promoteCommandToUser, commandExists, commandContentMatches, } from './lib/commands.js';
25
25
  import { discoverHooksFromRepo, installHooks, listInstalledHooksWithScope, promoteHookToUser, removeHook, hookExists, hookContentMatches, getSourceHookEntry, } from './lib/hooks.js';
26
26
  import { discoverSkillsFromRepo, installSkill, uninstallSkill, listInstalledSkillsWithScope, promoteSkillToUser, getSkillInfo, getSkillRules, skillExists, skillContentMatches, } from './lib/skills.js';
27
+ import { discoverInstructionsFromRepo, resolveInstructionsSource, installInstructions, uninstallInstructions, listInstalledInstructionsWithScope, promoteInstructionsToUser, instructionsExists, instructionsContentMatches, getInstructionsContent, } from './lib/instructions.js';
27
28
  import { DEFAULT_REGISTRIES } from './lib/types.js';
28
29
  import { search as searchRegistries, getRegistries, setRegistry, removeRegistry, resolvePackage, } from './lib/registry.js';
29
30
  const program = new Command();
@@ -179,6 +180,10 @@ program
179
180
  agent: AGENTS[agentId],
180
181
  mcps: listInstalledMcpsWithScope(agentId, cwd),
181
182
  }));
183
+ const instructionsData = agentsToShow.map((agentId) => ({
184
+ agent: AGENTS[agentId],
185
+ instructions: listInstalledInstructionsWithScope(agentId, cwd),
186
+ }));
182
187
  const scopes = filterAgentId ? [] : getScopesByPriority();
183
188
  spinner.stop();
184
189
  // Helper to format MCP with version
@@ -249,6 +254,23 @@ program
249
254
  }
250
255
  }
251
256
  }
257
+ console.log(chalk.bold('\nInstalled Instructions\n'));
258
+ for (const { agent, instructions } of instructionsData) {
259
+ const userInstr = instructions.find((i) => i.scope === 'user' && i.exists);
260
+ const projectInstr = instructions.find((i) => i.scope === 'project' && i.exists);
261
+ if (!userInstr && !projectInstr) {
262
+ console.log(` ${chalk.bold(agent.name)}: ${chalk.gray('none')}`);
263
+ }
264
+ else {
265
+ console.log(` ${chalk.bold(agent.name)}:`);
266
+ if (userInstr) {
267
+ console.log(` ${chalk.gray('User:')} ${chalk.cyan(agent.instructionsFile)}`);
268
+ }
269
+ if (projectInstr) {
270
+ console.log(` ${chalk.gray('Project:')} ${chalk.yellow(agent.instructionsFile)}`);
271
+ }
272
+ }
273
+ }
252
274
  // Only show scopes when not filtering by agent
253
275
  if (!filterAgentId) {
254
276
  if (scopes.length > 0) {
@@ -364,6 +386,7 @@ program
364
386
  const allCommands = discoverCommands(localPath);
365
387
  const allSkills = discoverSkillsFromRepo(localPath);
366
388
  const discoveredHooks = discoverHooksFromRepo(localPath);
389
+ const allInstructions = discoverInstructionsFromRepo(localPath);
367
390
  // Determine which agents to sync
368
391
  const cliStates = await getAllCliStates();
369
392
  let selectedAgents;
@@ -506,12 +529,27 @@ program
506
529
  }
507
530
  }
508
531
  }
532
+ // Process instructions
533
+ for (const instr of allInstructions) {
534
+ if (!selectedAgents.includes(instr.agentId))
535
+ continue;
536
+ const hasExisting = instructionsExists(instr.agentId, 'user');
537
+ if (!hasExisting) {
538
+ newItems.push({ type: 'instructions', name: AGENTS[instr.agentId].instructionsFile, agents: [instr.agentId], isNew: true });
539
+ }
540
+ else if (instructionsContentMatches(instr.agentId, instr.sourcePath, 'user')) {
541
+ upToDateItems.push({ type: 'instructions', name: AGENTS[instr.agentId].instructionsFile, agents: [instr.agentId], isNew: false });
542
+ }
543
+ else {
544
+ existingItems.push({ type: 'instructions', name: AGENTS[instr.agentId].instructionsFile, agents: [instr.agentId], isNew: false });
545
+ }
546
+ }
509
547
  // Display overview
510
548
  console.log(chalk.bold('\nOverview\n'));
511
549
  const formatAgentList = (agents) => agents.map((id) => AGENTS[id].name).join(', ');
512
550
  if (newItems.length > 0) {
513
551
  console.log(chalk.green(' NEW (will install):\n'));
514
- const byType = { command: [], skill: [], hook: [], mcp: [] };
552
+ const byType = { command: [], skill: [], hook: [], mcp: [], instructions: [] };
515
553
  for (const item of newItems)
516
554
  byType[item.type].push(item);
517
555
  if (byType.command.length > 0) {
@@ -538,11 +576,17 @@ program
538
576
  console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
539
577
  }
540
578
  }
579
+ if (byType.instructions.length > 0) {
580
+ console.log(` Instructions:`);
581
+ for (const item of byType.instructions) {
582
+ console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
583
+ }
584
+ }
541
585
  console.log();
542
586
  }
543
587
  if (upToDateItems.length > 0) {
544
588
  console.log(chalk.gray(' UP TO DATE (no changes):\n'));
545
- const byType = { command: [], skill: [], hook: [], mcp: [] };
589
+ const byType = { command: [], skill: [], hook: [], mcp: [], instructions: [] };
546
590
  for (const item of upToDateItems)
547
591
  byType[item.type].push(item);
548
592
  if (byType.command.length > 0) {
@@ -563,11 +607,17 @@ program
563
607
  console.log(` ${chalk.dim(item.name.padEnd(20))} ${chalk.dim(formatAgentList(item.agents))}`);
564
608
  }
565
609
  }
610
+ if (byType.instructions.length > 0) {
611
+ console.log(` Instructions:`);
612
+ for (const item of byType.instructions) {
613
+ console.log(` ${chalk.dim(item.name.padEnd(20))} ${chalk.dim(formatAgentList(item.agents))}`);
614
+ }
615
+ }
566
616
  console.log();
567
617
  }
568
618
  if (existingItems.length > 0) {
569
619
  console.log(chalk.yellow(' EXISTING (conflicts):\n'));
570
- const byType = { command: [], skill: [], hook: [], mcp: [] };
620
+ const byType = { command: [], skill: [], hook: [], mcp: [], instructions: [] };
571
621
  for (const item of existingItems)
572
622
  byType[item.type].push(item);
573
623
  if (byType.command.length > 0) {
@@ -594,6 +644,12 @@ program
594
644
  console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
595
645
  }
596
646
  }
647
+ if (byType.instructions.length > 0) {
648
+ console.log(` Instructions:`);
649
+ for (const item of byType.instructions) {
650
+ console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
651
+ }
652
+ }
597
653
  console.log();
598
654
  }
599
655
  if (newItems.length === 0 && existingItems.length === 0) {
@@ -666,8 +722,8 @@ program
666
722
  }
667
723
  // Install new items (no conflicts)
668
724
  console.log();
669
- let installed = { commands: 0, skills: 0, hooks: 0, mcps: 0 };
670
- let skipped = { commands: 0, skills: 0, hooks: 0, mcps: 0 };
725
+ let installed = { commands: 0, skills: 0, hooks: 0, mcps: 0, instructions: 0 };
726
+ let skipped = { commands: 0, skills: 0, hooks: 0, mcps: 0, instructions: 0 };
671
727
  // Install commands
672
728
  const cmdSpinner = ora('Installing commands...').start();
673
729
  for (const item of [...newItems, ...existingItems].filter((i) => i.type === 'command')) {
@@ -679,8 +735,13 @@ program
679
735
  for (const agentId of item.agents) {
680
736
  const sourcePath = resolveCommandSource(localPath, item.name, agentId);
681
737
  if (sourcePath) {
682
- installCommand(sourcePath, agentId, item.name, method);
683
- installed.commands++;
738
+ const result = installCommand(sourcePath, agentId, item.name, method);
739
+ if (result.error) {
740
+ console.log(chalk.yellow(`\n Warning: ${item.name} (${AGENTS[agentId].name}): ${result.error}`));
741
+ }
742
+ else {
743
+ installed.commands++;
744
+ }
684
745
  }
685
746
  }
686
747
  }
@@ -759,6 +820,39 @@ program
759
820
  mcpSpinner.info('No MCP servers to register');
760
821
  }
761
822
  }
823
+ // Install instructions
824
+ const instructionItems = [...newItems, ...existingItems].filter((i) => i.type === 'instructions');
825
+ if (instructionItems.length > 0) {
826
+ const instrSpinner = ora('Installing instructions...').start();
827
+ for (const item of instructionItems) {
828
+ const decision = item.isNew ? 'overwrite' : decisions.get(`instructions:${item.name}`);
829
+ if (decision === 'skip') {
830
+ skipped.instructions++;
831
+ continue;
832
+ }
833
+ for (const agentId of item.agents) {
834
+ const sourcePath = resolveInstructionsSource(localPath, agentId);
835
+ if (sourcePath) {
836
+ const result = installInstructions(sourcePath, agentId, method);
837
+ if (result.error) {
838
+ console.log(chalk.yellow(`\n Warning: ${item.name} (${AGENTS[agentId].name}): ${result.error}`));
839
+ }
840
+ else {
841
+ installed.instructions++;
842
+ }
843
+ }
844
+ }
845
+ }
846
+ if (skipped.instructions > 0) {
847
+ instrSpinner.succeed(`Installed ${installed.instructions} instructions (skipped ${skipped.instructions})`);
848
+ }
849
+ else if (installed.instructions > 0) {
850
+ instrSpinner.succeed(`Installed ${installed.instructions} instructions`);
851
+ }
852
+ else {
853
+ instrSpinner.info('No instructions to install');
854
+ }
855
+ }
762
856
  // Sync CLI versions (user scope only)
763
857
  if (isUserScope && !options.skipClis && manifest?.clis) {
764
858
  const cliSpinner = ora('Checking CLI versions...').start();
@@ -852,6 +946,44 @@ program
852
946
  exported++;
853
947
  }
854
948
  }
949
+ // Export MCP servers from installed agents
950
+ console.log();
951
+ let mcpExported = 0;
952
+ const mcpByName = new Map();
953
+ for (const agentId of MCP_CAPABLE_AGENTS) {
954
+ if (!cliStates[agentId]?.installed)
955
+ continue;
956
+ const mcps = listInstalledMcpsWithScope(agentId);
957
+ for (const mcp of mcps) {
958
+ if (mcp.scope !== 'user')
959
+ continue; // Only export user-scoped MCPs
960
+ const existing = mcpByName.get(mcp.name);
961
+ if (existing) {
962
+ if (!existing.agents.includes(agentId)) {
963
+ existing.agents.push(agentId);
964
+ }
965
+ }
966
+ else {
967
+ mcpByName.set(mcp.name, {
968
+ command: mcp.command || '',
969
+ agents: [agentId],
970
+ });
971
+ }
972
+ }
973
+ }
974
+ if (mcpByName.size > 0) {
975
+ manifest.mcp = manifest.mcp || {};
976
+ for (const [name, config] of mcpByName) {
977
+ manifest.mcp[name] = {
978
+ command: config.command,
979
+ transport: 'stdio',
980
+ agents: config.agents,
981
+ scope: 'user',
982
+ };
983
+ console.log(` ${chalk.green('+')} MCP: ${name} (${config.agents.map(id => AGENTS[id].name).join(', ')})`);
984
+ mcpExported++;
985
+ }
986
+ }
855
987
  if (!options.exportOnly) {
856
988
  writeManifest(localPath, manifest);
857
989
  console.log(chalk.bold(`\nUpdated ${MANIFEST_FILENAME}`));
@@ -952,8 +1084,13 @@ commandsCmd
952
1084
  continue;
953
1085
  const sourcePath = resolveCommandSource(localPath, command.name, agentId);
954
1086
  if (sourcePath) {
955
- installCommand(sourcePath, agentId, command.name, 'symlink');
956
- console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
1087
+ const result = installCommand(sourcePath, agentId, command.name, 'symlink');
1088
+ if (result.error) {
1089
+ console.log(` ${chalk.yellow('!')} ${AGENTS[agentId].name}: ${result.error}`);
1090
+ }
1091
+ else {
1092
+ console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
1093
+ }
957
1094
  }
958
1095
  }
959
1096
  }
@@ -1427,6 +1564,152 @@ skillsCmd
1427
1564
  }
1428
1565
  });
1429
1566
  // =============================================================================
1567
+ // INSTRUCTIONS COMMANDS
1568
+ // =============================================================================
1569
+ const instructionsCmd = program
1570
+ .command('instructions')
1571
+ .alias('instr')
1572
+ .description('Manage agent instructions (CLAUDE.md, GEMINI.md, etc.)');
1573
+ instructionsCmd
1574
+ .command('list')
1575
+ .description('List installed instructions files')
1576
+ .option('-a, --agent <agent>', 'Filter by agent')
1577
+ .action(async (options) => {
1578
+ const cwd = process.cwd();
1579
+ const agents = options.agent
1580
+ ? [resolveAgentName(options.agent)].filter(Boolean)
1581
+ : ALL_AGENT_IDS;
1582
+ console.log(chalk.bold('Installed Instructions\n'));
1583
+ for (const agentId of agents) {
1584
+ const agent = AGENTS[agentId];
1585
+ const installed = listInstalledInstructionsWithScope(agentId, cwd);
1586
+ const userInstr = installed.find((i) => i.scope === 'user');
1587
+ const projectInstr = installed.find((i) => i.scope === 'project');
1588
+ const userStatus = userInstr?.exists ? chalk.green(agent.instructionsFile) : chalk.gray('none');
1589
+ const projectStatus = projectInstr?.exists ? chalk.yellow(agent.instructionsFile) : chalk.gray('none');
1590
+ console.log(` ${chalk.bold(agent.name)}:`);
1591
+ console.log(` ${chalk.gray('User:')} ${userStatus}`);
1592
+ console.log(` ${chalk.gray('Project:')} ${projectStatus}`);
1593
+ console.log();
1594
+ }
1595
+ });
1596
+ instructionsCmd
1597
+ .command('view [agent]')
1598
+ .alias('show')
1599
+ .description('View instructions content for an agent')
1600
+ .option('-s, --scope <scope>', 'Scope: user or project', 'user')
1601
+ .action(async (agentArg, options) => {
1602
+ const cwd = process.cwd();
1603
+ let agentId;
1604
+ if (agentArg) {
1605
+ agentId = resolveAgentName(agentArg) || undefined;
1606
+ if (!agentId) {
1607
+ console.log(chalk.red(`Unknown agent: ${agentArg}`));
1608
+ process.exit(1);
1609
+ }
1610
+ }
1611
+ else {
1612
+ const choices = ALL_AGENT_IDS.filter((id) => instructionsExists(id, 'user', cwd) || instructionsExists(id, 'project', cwd));
1613
+ if (choices.length === 0) {
1614
+ console.log(chalk.yellow('No instructions files found.'));
1615
+ return;
1616
+ }
1617
+ agentId = await select({
1618
+ message: 'Select agent:',
1619
+ choices: choices.map((id) => ({ name: AGENTS[id].name, value: id })),
1620
+ });
1621
+ }
1622
+ const scope = (options?.scope || 'user');
1623
+ const content = getInstructionsContent(agentId, scope, cwd);
1624
+ if (!content) {
1625
+ console.log(chalk.yellow(`No ${scope} instructions found for ${AGENTS[agentId].name}`));
1626
+ return;
1627
+ }
1628
+ console.log(chalk.bold(`\n${AGENTS[agentId].name} Instructions (${scope}):\n`));
1629
+ console.log(content);
1630
+ });
1631
+ instructionsCmd
1632
+ .command('diff [agent]')
1633
+ .description('Show differences between local and repo instructions')
1634
+ .action(async (agentArg) => {
1635
+ const cwd = process.cwd();
1636
+ const meta = readState();
1637
+ const scopes = getScopesByPriority();
1638
+ if (scopes.length === 0) {
1639
+ console.log(chalk.yellow('No repo configured. Run: agents repo add <source>'));
1640
+ return;
1641
+ }
1642
+ const agents = agentArg
1643
+ ? [resolveAgentName(agentArg)].filter(Boolean)
1644
+ : ALL_AGENT_IDS;
1645
+ const diff = await import('diff');
1646
+ for (const { name: scopeName, config } of scopes) {
1647
+ const localPath = getRepoLocalPath(config.source);
1648
+ const repoInstructions = discoverInstructionsFromRepo(localPath);
1649
+ for (const agentId of agents) {
1650
+ const repoInstr = repoInstructions.find((i) => i.agentId === agentId);
1651
+ if (!repoInstr)
1652
+ continue;
1653
+ const installedContent = getInstructionsContent(agentId, 'user', cwd);
1654
+ if (!installedContent) {
1655
+ console.log(`${chalk.bold(AGENTS[agentId].name)}: ${chalk.green('NEW')} (not installed)`);
1656
+ continue;
1657
+ }
1658
+ const repoContent = fs.readFileSync(repoInstr.sourcePath, 'utf-8');
1659
+ if (installedContent.trim() === repoContent.trim()) {
1660
+ console.log(`${chalk.bold(AGENTS[agentId].name)}: ${chalk.gray('up to date')}`);
1661
+ continue;
1662
+ }
1663
+ console.log(`${chalk.bold(AGENTS[agentId].name)}:`);
1664
+ const changes = diff.diffLines(installedContent, repoContent);
1665
+ for (const change of changes) {
1666
+ if (change.added) {
1667
+ process.stdout.write(chalk.green(change.value));
1668
+ }
1669
+ else if (change.removed) {
1670
+ process.stdout.write(chalk.red(change.value));
1671
+ }
1672
+ }
1673
+ console.log();
1674
+ }
1675
+ }
1676
+ });
1677
+ instructionsCmd
1678
+ .command('push <agent>')
1679
+ .description('Save project-scoped instructions to user scope')
1680
+ .action((agentArg) => {
1681
+ const cwd = process.cwd();
1682
+ const agentId = resolveAgentName(agentArg);
1683
+ if (!agentId) {
1684
+ console.log(chalk.red(`Unknown agent: ${agentArg}`));
1685
+ process.exit(1);
1686
+ }
1687
+ const result = promoteInstructionsToUser(agentId, cwd);
1688
+ if (result.success) {
1689
+ console.log(chalk.green(`Pushed ${AGENTS[agentId].instructionsFile} to user scope`));
1690
+ }
1691
+ else {
1692
+ console.log(chalk.red(result.error || 'Failed to push instructions'));
1693
+ }
1694
+ });
1695
+ instructionsCmd
1696
+ .command('remove <agent>')
1697
+ .description('Remove user-scoped instructions for an agent')
1698
+ .action((agentArg) => {
1699
+ const agentId = resolveAgentName(agentArg);
1700
+ if (!agentId) {
1701
+ console.log(chalk.red(`Unknown agent: ${agentArg}`));
1702
+ process.exit(1);
1703
+ }
1704
+ const result = uninstallInstructions(agentId);
1705
+ if (result) {
1706
+ console.log(chalk.green(`Removed ${AGENTS[agentId].instructionsFile}`));
1707
+ }
1708
+ else {
1709
+ console.log(chalk.yellow(`No instructions file found for ${AGENTS[agentId].name}`));
1710
+ }
1711
+ });
1712
+ // =============================================================================
1430
1713
  // MCP COMMANDS
1431
1714
  // =============================================================================
1432
1715
  const mcpCmd = program
@@ -2300,18 +2583,29 @@ program
2300
2583
  if (hasCommands) {
2301
2584
  console.log(chalk.bold('\nInstalling commands...'));
2302
2585
  let installed = 0;
2586
+ let failed = 0;
2303
2587
  for (const command of commands) {
2304
2588
  for (const agentId of agents) {
2305
2589
  if (!gitCliStates[agentId]?.installed && agentId !== 'cursor')
2306
2590
  continue;
2307
2591
  const sourcePath = resolveCommandSource(localPath, command.name, agentId);
2308
2592
  if (sourcePath) {
2309
- installCommand(sourcePath, agentId, command.name, 'symlink');
2310
- installed++;
2593
+ const result = installCommand(sourcePath, agentId, command.name, 'symlink');
2594
+ if (result.error) {
2595
+ failed++;
2596
+ }
2597
+ else {
2598
+ installed++;
2599
+ }
2311
2600
  }
2312
2601
  }
2313
2602
  }
2314
- console.log(` Installed ${installed} command instances`);
2603
+ if (failed > 0) {
2604
+ console.log(` Installed ${installed} command instances (${failed} failed)`);
2605
+ }
2606
+ else {
2607
+ console.log(` Installed ${installed} command instances`);
2608
+ }
2315
2609
  }
2316
2610
  // Install skills
2317
2611
  if (hasSkills) {