@swarmify/agents-cli 1.3.13 → 1.4.0

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,20 +1,24 @@
1
1
  # @swarmify/agents-cli
2
2
 
3
- Dotfiles for AI coding agents. Sync prompts, MCP servers, hooks, and skills across Claude, Codex, Gemini, Cursor, and more.
3
+ Your virtual environment manager for AI coding agents.
4
4
 
5
5
  ```bash
6
6
  npm install -g @swarmify/agents-cli
7
7
  ```
8
8
 
9
- ## The Idea
9
+ ## The Problem
10
10
 
11
- You configure Claude Code manually. Then you switch to Codex or Gemini and start from scratch. This CLI syncs your setup across all agents:
11
+ 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.
12
+
13
+ ## The Solution
14
+
15
+ Keep your agent configuration in a git repo. Pull it anywhere.
12
16
 
13
17
  ```bash
14
- # Pull your config on a new machine
18
+ # New machine? One command.
15
19
  agents pull gh:username/.agents
16
20
 
17
- # See what you have installed
21
+ # See what's installed
18
22
  agents status claude
19
23
  ```
20
24
 
@@ -40,145 +44,166 @@ Installed MCP Servers
40
44
  User: Swarm@latest, GoDaddy
41
45
  ```
42
46
 
43
- ## What Gets Synced
47
+ Your `.agents` repo becomes the source of truth for all your AI coding tools.
44
48
 
45
- | Resource | Location | Synced To |
46
- |----------|----------|-----------|
47
- | Slash commands | `~/.claude/commands/` | Claude, Codex, Gemini |
48
- | MCP servers | `~/.claude/settings.json` | Claude, Codex, Gemini |
49
- | Hooks | `~/.claude/hooks.json` | Claude, Gemini |
50
- | Agent Skills | `~/.claude/skills/` | Claude, Codex, Gemini |
51
- | CLI versions | npm/brew | All agents |
49
+ ## What Gets Synced
52
50
 
53
- ## Commands
51
+ | Resource | Description | Agents |
52
+ |----------|-------------|--------|
53
+ | Slash commands | `/debug`, `/plan`, custom prompts | Claude, Codex, Gemini, Cursor, OpenCode |
54
+ | MCP servers | Tools your agents can use | Claude, Codex, Gemini |
55
+ | Hooks | Pre/post execution scripts | Claude, Gemini |
56
+ | Skills | Reusable agent capabilities | Claude, Codex, Gemini |
57
+ | CLI versions | Which version of each agent | All |
54
58
 
55
- ### Prompts (Slash Commands)
59
+ ## Quick Start
56
60
 
57
61
  ```bash
58
- # List what's installed
59
- agents commands list
62
+ # 1. Install
63
+ npm install -g @swarmify/agents-cli
60
64
 
61
- # Install from a git repo
62
- agents commands add gh:user/my-prompts
65
+ # 2. Set your config repo
66
+ agents repo add gh:username/.agents
63
67
 
64
- # Remove
65
- agents commands remove my-command
68
+ # 3. Pull everything
69
+ agents pull
70
+
71
+ # 4. Check what's installed
72
+ agents status
66
73
  ```
67
74
 
68
- ### MCP Servers
75
+ ## Your .agents Repo
76
+
77
+ ```
78
+ .agents/
79
+ agents.yaml # CLI versions, MCP servers, defaults
80
+ shared/commands/ # Slash commands for all agents
81
+ claude/commands/ # Claude-specific commands
82
+ claude/hooks/ # Claude hooks
83
+ codex/prompts/ # Codex-specific prompts
84
+ gemini/commands/ # Gemini commands (auto-converted to TOML)
85
+ skills/ # Agent Skills (SKILL.md + rules/)
86
+ ```
87
+
88
+ Example `agents.yaml`:
89
+
90
+ ```yaml
91
+ clis:
92
+ claude:
93
+ package: "@anthropic-ai/claude-code"
94
+ version: "latest"
95
+ codex:
96
+ package: "@openai/codex"
97
+ version: "latest"
98
+
99
+ mcp:
100
+ filesystem:
101
+ command: "npx -y @anthropic-ai/mcp-filesystem"
102
+ transport: stdio
103
+ scope: user
104
+ agents: [claude, codex, gemini]
105
+
106
+ memory:
107
+ command: "npx -y @anthropic-ai/mcp-memory"
108
+ transport: stdio
109
+ scope: user
110
+ agents: [claude, codex, gemini]
111
+
112
+ defaults:
113
+ method: symlink
114
+ scope: user
115
+ agents: [claude, codex, gemini]
116
+ ```
117
+
118
+ ## Commands
119
+
120
+ ### Status
69
121
 
70
122
  ```bash
71
- # List servers across all agents
72
- agents mcp list
123
+ agents status # Full overview
124
+ agents status --agent claude
125
+ ```
73
126
 
74
- # Add stdio server (use -- before the command)
75
- agents mcp add swarm -- npx @swarmify/agents-mcp
76
- agents mcp add memory -- npx -y @anthropic-ai/mcp-memory
127
+ ### Pull & Push
128
+
129
+ ```bash
130
+ agents pull # Sync from your repo
131
+ agents pull --dry-run # Preview changes
132
+ agents push # Push local changes back
133
+ ```
77
134
 
78
- # Add HTTP server with headers
79
- agents mcp add api https://api.example.com/mcp --transport http -H "Authorization:Bearer token"
135
+ ### Slash Commands
80
136
 
81
- # Remove from all agents
82
- agents mcp remove swarm
137
+ ```bash
138
+ agents commands list
139
+ agents commands add gh:user/my-commands
140
+ agents commands remove my-command
141
+ agents commands push my-command # Promote project -> user scope
83
142
  ```
84
143
 
85
- ### Hooks
144
+ ### MCP Servers
86
145
 
87
146
  ```bash
88
- # List hooks
89
- agents hooks list
147
+ # List across all agents
148
+ agents mcp list
90
149
 
91
- # Install from repo
92
- agents hooks add gh:user/my-hooks
150
+ # Add (use -- before the command)
151
+ agents mcp add memory -- npx -y @anthropic-ai/mcp-memory
152
+ agents mcp add api https://api.example.com --transport http
153
+
154
+ # Search registries
155
+ agents search filesystem
156
+ agents add mcp:@anthropic-ai/mcp-filesystem
93
157
 
94
158
  # Remove
95
- agents hooks remove my-hook
159
+ agents mcp remove memory
96
160
  ```
97
161
 
98
162
  ### Skills
99
163
 
100
- Agent Skills are reusable capabilities (SKILL.md + rules/) that agents can invoke.
101
-
102
164
  ```bash
103
- # List installed skills
104
165
  agents skills list
105
-
106
- # Install from repo
107
166
  agents skills add gh:user/my-skills
108
-
109
- # Show details
110
167
  agents skills info my-skill
111
168
  ```
112
169
 
113
- ### Search & Install from Registries
114
-
115
- Search public registries for MCP servers and skills:
170
+ ### Hooks
116
171
 
117
172
  ```bash
118
- # Search all registries
119
- agents search github
120
- agents search filesystem --limit 10
121
-
122
- # Filter by type
123
- agents search github --type mcp
124
-
125
- # Install from registry (auto-detected)
126
- agents add mcp:io.github.bytedance/mcp-server-filesystem
127
-
128
- # Or use identifiers
129
- agents add skill:user/my-skill # Skill from git
130
- agents add gh:user/my-repo # Git repo directly
173
+ agents hooks list
174
+ agents hooks add gh:user/my-hooks
175
+ agents hooks remove my-hook
131
176
  ```
132
177
 
133
- ### Registry Management
178
+ ### CLI Management
134
179
 
135
180
  ```bash
136
- # List configured registries
137
- agents registry list
138
-
139
- # Add custom registry
140
- agents registry add mcp myregistry https://api.example.com/v1
141
-
142
- # Configure API key
143
- agents registry config mcp myregistry --api-key YOUR_KEY
144
-
145
- # Disable/enable
146
- agents registry disable mcp myregistry
147
- agents registry enable mcp myregistry
148
-
149
- # Remove
150
- agents registry remove mcp myregistry
181
+ agents cli list # Show installed versions
182
+ agents cli add claude # Install agent CLI
183
+ agents cli remove codex # Uninstall agent CLI
184
+ agents cli upgrade # Upgrade all to latest
151
185
  ```
152
186
 
153
- Default registries:
154
- - `official` (MCP): https://registry.modelcontextprotocol.io
155
-
156
- ### Sync
157
-
158
- ```bash
159
- # Pull config from your .agents repo
160
- agents pull gh:username/.agents
187
+ ## Scopes
161
188
 
162
- # Push local changes back
163
- agents push
189
+ Resources can exist at two levels:
164
190
 
165
- # Full status
166
- agents status
167
- ```
191
+ | Scope | Location | Use |
192
+ |-------|----------|-----|
193
+ | User | `~/.{agent}/` | Available everywhere |
194
+ | Project | `./.{agent}/` | This repo only, committed |
168
195
 
169
- ### CLI Management
196
+ Promote project-scoped items to user scope:
170
197
 
171
198
  ```bash
172
- # Show installed versions
173
- agents cli list
174
-
175
- # Upgrade all to latest
176
- agents cli upgrade --latest
199
+ agents commands push my-command
200
+ agents mcp push my-server
201
+ agents skills push my-skill
177
202
  ```
178
203
 
179
204
  ## Filtering
180
205
 
181
- All commands support `--agent` and `--scope` filters:
206
+ All list commands support filters:
182
207
 
183
208
  ```bash
184
209
  agents commands list --agent claude
@@ -186,44 +211,38 @@ agents mcp list --scope project
186
211
  agents skills list --agent codex --scope user
187
212
  ```
188
213
 
189
- ## Scopes
214
+ ## Registries
190
215
 
191
- | Scope | Location | Use Case |
192
- |-------|----------|----------|
193
- | User | `~/.{agent}/` | Available everywhere |
194
- | Project | `./.{agent}/` | This repo only |
195
-
196
- Promote project-scoped items to user scope:
216
+ Search and install from public registries:
197
217
 
198
218
  ```bash
199
- agents commands push my-command
200
- agents mcp push my-server
219
+ # Search
220
+ agents search github --type mcp
221
+
222
+ # Install from registry
223
+ agents add mcp:@anthropic-ai/mcp-filesystem
224
+
225
+ # Manage registries
226
+ agents registry list
227
+ agents registry add mcp myregistry https://api.example.com
228
+ agents registry config mcp myregistry --api-key KEY
201
229
  ```
202
230
 
203
231
  ## Supported Agents
204
232
 
205
- | Agent | Prompts | MCP | Hooks | Skills |
206
- |-------|---------|-----|-------|--------|
233
+ | Agent | Commands | MCP | Hooks | Skills |
234
+ |-------|----------|-----|-------|--------|
207
235
  | Claude Code | Yes | Yes | Yes | Yes |
208
236
  | Codex | Yes | Yes | - | Yes |
209
- | Gemini CLI | Yes (TOML) | Yes | Yes | Yes |
210
- | Cursor | Yes | Yes | - | Yes |
211
- | OpenCode | Yes | Yes | - | Yes |
237
+ | Gemini CLI | Yes | Yes | Yes | Yes |
238
+ | Cursor | Yes | Yes | - | - |
239
+ | OpenCode | Yes | Yes | - | - |
212
240
 
213
- ## Your .agents Repo
214
-
215
- ```
216
- .agents/
217
- agents.yaml # CLIs, MCP servers, defaults
218
- shared/commands/ # Prompts for all agents
219
- claude/commands/ # Claude-specific prompts
220
- claude/hooks/ # Claude hooks
221
- skills/ # Agent Skills
222
- ```
241
+ Format conversion is automatic. Write commands in markdown, they're converted to TOML for Gemini.
223
242
 
224
243
  ## Related
225
244
 
226
- - [@swarmify/agents-mcp](https://www.npmjs.com/package/@swarmify/agents-mcp) - Multi-agent orchestration MCP server
245
+ - [@swarmify/agents-mcp](https://www.npmjs.com/package/@swarmify/agents-mcp) - MCP server for multi-agent orchestration
227
246
 
228
247
  ## License
229
248
 
package/dist/index.js CHANGED
@@ -3,14 +3,14 @@ import { Command } from 'commander';
3
3
  import chalk from 'chalk';
4
4
  import ora from 'ora';
5
5
  import { checkbox, confirm, select } from '@inquirer/prompts';
6
- import { AGENTS, ALL_AGENT_IDS, MCP_CAPABLE_AGENTS, SKILLS_CAPABLE_AGENTS, HOOKS_CAPABLE_AGENTS, getAllCliStates, isCliInstalled, isMcpRegistered, registerMcp, unregisterMcp, listInstalledMcpsWithScope, promoteMcpToUser, } from './lib/agents.js';
6
+ import { AGENTS, ALL_AGENT_IDS, MCP_CAPABLE_AGENTS, SKILLS_CAPABLE_AGENTS, HOOKS_CAPABLE_AGENTS, getAllCliStates, isCliInstalled, getCliVersion, isMcpRegistered, registerMcp, unregisterMcp, listInstalledMcpsWithScope, promoteMcpToUser, } from './lib/agents.js';
7
7
  import { readManifest, writeManifest, createDefaultManifest, MANIFEST_FILENAME, } from './lib/manifest.js';
8
8
  import { readState, ensureAgentsDir, getRepoLocalPath, getScope, setScope, removeScope, getScopesByPriority, getScopePriority, } from './lib/state.js';
9
9
  import { SCOPE_PRIORITIES, DEFAULT_SYSTEM_REPO } from './lib/types.js';
10
10
  import { cloneRepo, parseSource } from './lib/git.js';
11
- import { discoverCommands, resolveCommandSource, installCommand, uninstallCommand, listInstalledCommandsWithScope, promoteCommandToUser, } from './lib/commands.js';
12
- import { discoverHooksFromRepo, installHooks, listInstalledHooksWithScope, promoteHookToUser, removeHook, } from './lib/hooks.js';
13
- import { discoverSkillsFromRepo, installSkill, uninstallSkill, listInstalledSkillsWithScope, promoteSkillToUser, getSkillInfo, getSkillRules, } from './lib/skills.js';
11
+ import { discoverCommands, resolveCommandSource, installCommand, uninstallCommand, listInstalledCommandsWithScope, promoteCommandToUser, commandExists, } from './lib/commands.js';
12
+ import { discoverHooksFromRepo, installHooks, listInstalledHooksWithScope, promoteHookToUser, removeHook, hookExists, } from './lib/hooks.js';
13
+ import { discoverSkillsFromRepo, installSkill, uninstallSkill, listInstalledSkillsWithScope, promoteSkillToUser, getSkillInfo, getSkillRules, skillExists, } from './lib/skills.js';
14
14
  import { DEFAULT_REGISTRIES } from './lib/types.js';
15
15
  import { search as searchRegistries, getRegistries, setRegistry, removeRegistry, resolvePackage, } from './lib/registry.js';
16
16
  const program = new Command();
@@ -195,17 +195,14 @@ program
195
195
  }
196
196
  console.log();
197
197
  });
198
- // =============================================================================
199
- // PULL COMMAND
200
- // =============================================================================
201
198
  program
202
199
  .command('pull [source]')
203
200
  .description('Pull and sync from remote .agents repo')
204
201
  .option('-y, --yes', 'Skip interactive prompts')
205
- .option('-f, --force', 'Overwrite local changes')
202
+ .option('-f, --force', 'Overwrite existing resources without asking')
206
203
  .option('-s, --scope <scope>', 'Target scope (default: user)', 'user')
207
204
  .option('--dry-run', 'Show what would change')
208
- .option('--skip-clis', 'Skip CLI installation')
205
+ .option('--skip-clis', 'Skip CLI version sync')
209
206
  .option('--skip-mcp', 'Skip MCP registration')
210
207
  .action(async (source, options) => {
211
208
  const scopeName = options.scope;
@@ -233,6 +230,7 @@ program
233
230
  // Prevent modification of readonly scopes (but allow syncing from them)
234
231
  const targetScopeConfig = meta.scopes[effectiveScope];
235
232
  const isReadonly = targetScopeConfig?.readonly || effectiveScope === 'system';
233
+ const isUserScope = effectiveScope === 'user';
236
234
  const parsed = parseSource(targetSource);
237
235
  const spinner = ora(`Syncing from ${effectiveScope} scope...`).start();
238
236
  try {
@@ -321,47 +319,176 @@ program
321
319
  default: manifest?.defaults?.method || 'symlink',
322
320
  });
323
321
  }
324
- const defaultAgents = selectedAgents;
322
+ // Detect conflicts
323
+ const conflicts = [];
324
+ // Check command conflicts
325
+ for (const command of commands) {
326
+ for (const agentId of selectedAgents) {
327
+ if (!isCliInstalled(agentId) && agentId !== 'cursor')
328
+ continue;
329
+ const sourcePath = resolveCommandSource(localPath, command.name, agentId);
330
+ if (sourcePath && commandExists(agentId, command.name)) {
331
+ conflicts.push({ type: 'command', name: command.name, agent: agentId });
332
+ }
333
+ }
334
+ }
335
+ // Check skill conflicts
336
+ for (const skill of skills) {
337
+ const skillAgents = SKILLS_CAPABLE_AGENTS.filter((id) => selectedAgents.includes(id) && (isCliInstalled(id) || id === 'cursor'));
338
+ for (const agentId of skillAgents) {
339
+ if (skillExists(agentId, skill.name)) {
340
+ conflicts.push({ type: 'skill', name: skill.name, agent: agentId });
341
+ }
342
+ }
343
+ }
344
+ // Check hook conflicts
345
+ const hookAgents = selectedAgents.filter((id) => HOOKS_CAPABLE_AGENTS.includes(id) && isCliInstalled(id));
346
+ for (const hookName of discoveredHooks.shared) {
347
+ for (const agentId of hookAgents) {
348
+ if (hookExists(agentId, hookName)) {
349
+ conflicts.push({ type: 'hook', name: hookName, agent: agentId });
350
+ }
351
+ }
352
+ }
353
+ for (const [agentIdStr, hookNames] of Object.entries(discoveredHooks.agentSpecific)) {
354
+ const agentId = agentIdStr;
355
+ if (hookAgents.includes(agentId)) {
356
+ for (const hookName of hookNames) {
357
+ if (hookExists(agentId, hookName)) {
358
+ conflicts.push({ type: 'hook', name: hookName, agent: agentId });
359
+ }
360
+ }
361
+ }
362
+ }
363
+ // Check MCP conflicts
364
+ if (!options.skipMcp && manifest?.mcp) {
365
+ for (const [name, config] of Object.entries(manifest.mcp)) {
366
+ if (config.transport === 'http' || !config.command)
367
+ continue;
368
+ for (const agentId of config.agents) {
369
+ if (!isCliInstalled(agentId))
370
+ continue;
371
+ if (isMcpRegistered(agentId, name)) {
372
+ conflicts.push({ type: 'mcp', name, agent: agentId });
373
+ }
374
+ }
375
+ }
376
+ }
377
+ // Handle conflicts
378
+ let conflictAction = 'overwrite';
379
+ if (conflicts.length > 0 && !options.force) {
380
+ // Deduplicate conflicts by name (show each resource once, not per-agent)
381
+ const uniqueConflicts = new Map();
382
+ for (const c of conflicts) {
383
+ const key = `${c.type}:${c.name}`;
384
+ if (!uniqueConflicts.has(key)) {
385
+ uniqueConflicts.set(key, c);
386
+ }
387
+ }
388
+ console.log(chalk.yellow(`\nFound ${uniqueConflicts.size} existing resources that would be overwritten:\n`));
389
+ const byType = {
390
+ command: [],
391
+ skill: [],
392
+ hook: [],
393
+ mcp: [],
394
+ };
395
+ for (const c of uniqueConflicts.values()) {
396
+ byType[c.type].push(c.name);
397
+ }
398
+ if (byType.command.length > 0) {
399
+ console.log(` Commands: ${byType.command.join(', ')}`);
400
+ }
401
+ if (byType.skill.length > 0) {
402
+ console.log(` Skills: ${byType.skill.join(', ')}`);
403
+ }
404
+ if (byType.hook.length > 0) {
405
+ console.log(` Hooks: ${byType.hook.join(', ')}`);
406
+ }
407
+ if (byType.mcp.length > 0) {
408
+ console.log(` MCP Servers: ${byType.mcp.join(', ')}`);
409
+ }
410
+ if (!options.yes) {
411
+ conflictAction = await select({
412
+ message: 'How do you want to handle existing resources?',
413
+ choices: [
414
+ { name: 'Overwrite all', value: 'overwrite' },
415
+ { name: 'Skip existing (only install new)', value: 'skip' },
416
+ { name: 'Cancel', value: 'cancel' },
417
+ ],
418
+ });
419
+ if (conflictAction === 'cancel') {
420
+ console.log(chalk.yellow('\nSync cancelled'));
421
+ return;
422
+ }
423
+ }
424
+ }
425
+ const skipExisting = conflictAction === 'skip';
426
+ const conflictNames = new Set(conflicts.map((c) => `${c.type}:${c.name}:${c.agent}`));
427
+ // Install commands
325
428
  const installSpinner = ora('Installing commands...').start();
326
429
  let installed = 0;
430
+ let skipped = 0;
327
431
  for (const command of commands) {
328
- for (const agentId of defaultAgents) {
432
+ for (const agentId of selectedAgents) {
329
433
  if (!isCliInstalled(agentId) && agentId !== 'cursor')
330
434
  continue;
331
435
  const sourcePath = resolveCommandSource(localPath, command.name, agentId);
332
- if (sourcePath) {
333
- installCommand(sourcePath, agentId, command.name, method);
334
- installed++;
436
+ if (!sourcePath)
437
+ continue;
438
+ const conflictKey = `command:${command.name}:${agentId}`;
439
+ if (skipExisting && conflictNames.has(conflictKey)) {
440
+ skipped++;
441
+ continue;
335
442
  }
443
+ installCommand(sourcePath, agentId, command.name, method);
444
+ installed++;
336
445
  }
337
446
  }
338
- installSpinner.succeed(`Installed ${installed} command instances`);
447
+ if (skipped > 0) {
448
+ installSpinner.succeed(`Installed ${installed} commands (skipped ${skipped} existing)`);
449
+ }
450
+ else {
451
+ installSpinner.succeed(`Installed ${installed} command instances`);
452
+ }
339
453
  // Install skills
340
454
  if (skills.length > 0) {
341
455
  const skillSpinner = ora('Installing skills...').start();
342
456
  let skillsInstalled = 0;
457
+ let skillsSkipped = 0;
343
458
  for (const skill of skills) {
344
459
  const skillAgents = SKILLS_CAPABLE_AGENTS.filter((id) => selectedAgents.includes(id) && (isCliInstalled(id) || id === 'cursor'));
460
+ // Check if should skip
461
+ const shouldSkip = skipExisting && skillAgents.some((agentId) => conflictNames.has(`skill:${skill.name}:${agentId}`));
462
+ if (shouldSkip) {
463
+ skillsSkipped++;
464
+ continue;
465
+ }
345
466
  if (skillAgents.length > 0) {
346
467
  const result = installSkill(skill.path, skill.name, skillAgents);
347
468
  if (result.success)
348
469
  skillsInstalled++;
349
470
  }
350
471
  }
351
- skillSpinner.succeed(`Installed ${skillsInstalled} skills`);
352
- }
353
- // Install hooks
354
- if (totalHooks > 0) {
355
- const hookAgents = selectedAgents.filter((id) => HOOKS_CAPABLE_AGENTS.includes(id) && isCliInstalled(id));
356
- if (hookAgents.length > 0) {
357
- const hookSpinner = ora('Installing hooks...').start();
358
- const result = await installHooks(localPath, hookAgents, { scope: 'user' });
359
- hookSpinner.succeed(`Installed ${result.installed.length} hooks`);
472
+ if (skillsSkipped > 0) {
473
+ skillSpinner.succeed(`Installed ${skillsInstalled} skills (skipped ${skillsSkipped} existing)`);
474
+ }
475
+ else {
476
+ skillSpinner.succeed(`Installed ${skillsInstalled} skills`);
360
477
  }
361
478
  }
479
+ // Install hooks
480
+ if (totalHooks > 0 && hookAgents.length > 0) {
481
+ const hookSpinner = ora('Installing hooks...').start();
482
+ // Note: installHooks handles its own conflict detection internally
483
+ // For now, pass the full list - in the future we could filter
484
+ const result = await installHooks(localPath, hookAgents, { scope: 'user' });
485
+ hookSpinner.succeed(`Installed ${result.installed.length} hooks`);
486
+ }
487
+ // Register MCP servers
362
488
  if (!options.skipMcp && manifest?.mcp) {
363
489
  const mcpSpinner = ora('Registering MCP servers...').start();
364
490
  let registered = 0;
491
+ let mcpSkipped = 0;
365
492
  for (const [name, config] of Object.entries(manifest.mcp)) {
366
493
  // Skip HTTP transport MCPs for now (need different registration)
367
494
  if (config.transport === 'http' || !config.command)
@@ -369,14 +496,55 @@ program
369
496
  for (const agentId of config.agents) {
370
497
  if (!isCliInstalled(agentId))
371
498
  continue;
372
- if (isMcpRegistered(agentId, name))
373
- continue;
499
+ const conflictKey = `mcp:${name}:${agentId}`;
500
+ if (conflictNames.has(conflictKey)) {
501
+ if (skipExisting) {
502
+ mcpSkipped++;
503
+ continue;
504
+ }
505
+ // If overwriting, unregister first then re-register
506
+ unregisterMcp(agentId, name);
507
+ }
374
508
  const result = registerMcp(agentId, name, config.command, config.scope);
375
509
  if (result.success)
376
510
  registered++;
377
511
  }
378
512
  }
379
- mcpSpinner.succeed(`Registered ${registered} MCP servers`);
513
+ if (mcpSkipped > 0) {
514
+ mcpSpinner.succeed(`Registered ${registered} MCP servers (skipped ${mcpSkipped} existing)`);
515
+ }
516
+ else {
517
+ mcpSpinner.succeed(`Registered ${registered} MCP servers`);
518
+ }
519
+ }
520
+ // Sync CLI versions (user scope only, unless --skip-clis)
521
+ if (isUserScope && !options.skipClis && manifest?.clis) {
522
+ const cliSpinner = ora('Checking CLI versions...').start();
523
+ const cliUpdates = [];
524
+ for (const [agentIdStr, cliConfig] of Object.entries(manifest.clis)) {
525
+ const agentId = agentIdStr;
526
+ const agent = AGENTS[agentId];
527
+ if (!agent || !cliConfig.package)
528
+ continue;
529
+ const currentVersion = getCliVersion(agentId);
530
+ const targetVersion = cliConfig.version;
531
+ // Skip if same version or if target is "latest" and CLI is installed
532
+ if (currentVersion === targetVersion)
533
+ continue;
534
+ if (targetVersion === 'latest' && currentVersion)
535
+ continue;
536
+ cliUpdates.push(`${agent.name}: ${currentVersion || 'not installed'} -> ${targetVersion}`);
537
+ }
538
+ if (cliUpdates.length > 0) {
539
+ cliSpinner.info('CLI version differences detected');
540
+ console.log(chalk.gray(' Run `agents cli upgrade` to update CLIs'));
541
+ for (const update of cliUpdates) {
542
+ console.log(chalk.gray(` ${update}`));
543
+ }
544
+ }
545
+ else {
546
+ cliSpinner.succeed('CLI versions match');
547
+ }
380
548
  }
381
549
  // Update scope config (only if not readonly)
382
550
  if (!isReadonly) {