@swarmify/agents-cli 1.3.13 → 1.5.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/dist/index.js CHANGED
@@ -3,14 +3,19 @@ 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
+ function isPromptCancelled(err) {
7
+ return err instanceof Error && (err.name === 'ExitPromptError' ||
8
+ err.message.includes('force closed') ||
9
+ err.message.includes('User force closed'));
10
+ }
11
+ 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
12
  import { readManifest, writeManifest, createDefaultManifest, MANIFEST_FILENAME, } from './lib/manifest.js';
8
13
  import { readState, ensureAgentsDir, getRepoLocalPath, getScope, setScope, removeScope, getScopesByPriority, getScopePriority, } from './lib/state.js';
9
14
  import { SCOPE_PRIORITIES, DEFAULT_SYSTEM_REPO } from './lib/types.js';
10
15
  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';
16
+ import { discoverCommands, resolveCommandSource, installCommand, uninstallCommand, listInstalledCommandsWithScope, promoteCommandToUser, commandExists, } from './lib/commands.js';
17
+ import { discoverHooksFromRepo, installHooks, listInstalledHooksWithScope, promoteHookToUser, removeHook, hookExists, } from './lib/hooks.js';
18
+ import { discoverSkillsFromRepo, installSkill, uninstallSkill, listInstalledSkillsWithScope, promoteSkillToUser, getSkillInfo, getSkillRules, skillExists, } from './lib/skills.js';
14
19
  import { DEFAULT_REGISTRIES } from './lib/types.js';
15
20
  import { search as searchRegistries, getRegistries, setRegistry, removeRegistry, resolvePackage, } from './lib/registry.js';
16
21
  const program = new Command();
@@ -198,24 +203,62 @@ program
198
203
  // =============================================================================
199
204
  // PULL COMMAND
200
205
  // =============================================================================
206
+ // Agent name aliases for flexible input
207
+ const AGENT_NAME_ALIASES = {
208
+ claude: 'claude',
209
+ 'claude-code': 'claude',
210
+ cc: 'claude',
211
+ codex: 'codex',
212
+ 'openai-codex': 'codex',
213
+ cx: 'codex',
214
+ gemini: 'gemini',
215
+ 'gemini-cli': 'gemini',
216
+ gx: 'gemini',
217
+ cursor: 'cursor',
218
+ 'cursor-agent': 'cursor',
219
+ cr: 'cursor',
220
+ opencode: 'opencode',
221
+ oc: 'opencode',
222
+ };
223
+ function resolveAgentName(input) {
224
+ return AGENT_NAME_ALIASES[input.toLowerCase()] || null;
225
+ }
226
+ function isAgentName(input) {
227
+ return resolveAgentName(input) !== null;
228
+ }
201
229
  program
202
- .command('pull [source]')
230
+ .command('pull [source] [agent]')
203
231
  .description('Pull and sync from remote .agents repo')
204
- .option('-y, --yes', 'Skip interactive prompts')
205
- .option('-f, --force', 'Overwrite local changes')
232
+ .option('-y, --yes', 'Auto-confirm and skip all conflicts')
233
+ .option('-f, --force', 'Auto-confirm and overwrite all conflicts')
206
234
  .option('-s, --scope <scope>', 'Target scope (default: user)', 'user')
207
235
  .option('--dry-run', 'Show what would change')
208
- .option('--skip-clis', 'Skip CLI installation')
236
+ .option('--skip-clis', 'Skip CLI version sync')
209
237
  .option('--skip-mcp', 'Skip MCP registration')
210
- .action(async (source, options) => {
238
+ .action(async (arg1, arg2, options) => {
239
+ // Parse source and agent filter from positional args
240
+ let targetSource;
241
+ let agentFilter;
242
+ if (arg1) {
243
+ if (isAgentName(arg1)) {
244
+ // agents pull claude
245
+ agentFilter = resolveAgentName(arg1);
246
+ }
247
+ else {
248
+ // agents pull gh:user/repo [agent]
249
+ targetSource = arg1;
250
+ if (arg2 && isAgentName(arg2)) {
251
+ agentFilter = resolveAgentName(arg2);
252
+ }
253
+ }
254
+ }
211
255
  const scopeName = options.scope;
212
256
  const meta = readState();
213
257
  const existingScope = meta.scopes[scopeName];
214
258
  // Try: 1) provided source, 2) existing scope source, 3) fall back to system scope
215
- let targetSource = source || existingScope?.source;
259
+ targetSource = targetSource || existingScope?.source;
216
260
  let effectiveScope = scopeName;
217
261
  if (!targetSource && scopeName === 'user') {
218
- // Fall back to system scope if user scope has no source
219
262
  const systemScope = meta.scopes['system'];
220
263
  if (systemScope?.source) {
221
264
  targetSource = systemScope.source;
@@ -224,15 +267,22 @@ program
224
267
  }
225
268
  }
226
269
  if (!targetSource) {
227
- console.log(chalk.red(`No source specified for scope '${scopeName}'.`));
228
- const scopeHint = scopeName === 'user' ? '' : ` --scope ${scopeName}`;
229
- console.log(chalk.gray(` Usage: agents pull <source>${scopeHint}`));
230
- console.log(chalk.gray(' Example: agents pull gh:username/.agents'));
231
- process.exit(1);
270
+ if (scopeName === 'user' && Object.keys(meta.scopes).length === 0) {
271
+ console.log(chalk.gray(`First run detected. Initializing from ${DEFAULT_SYSTEM_REPO}...\n`));
272
+ targetSource = DEFAULT_SYSTEM_REPO;
273
+ effectiveScope = 'system';
274
+ }
275
+ else {
276
+ console.log(chalk.red(`No source specified for scope '${scopeName}'.`));
277
+ const scopeHint = scopeName === 'user' ? '' : ` --scope ${scopeName}`;
278
+ console.log(chalk.gray(` Usage: agents pull <source>${scopeHint}`));
279
+ console.log(chalk.gray(' Example: agents pull gh:username/.agents'));
280
+ process.exit(1);
281
+ }
232
282
  }
233
- // Prevent modification of readonly scopes (but allow syncing from them)
234
283
  const targetScopeConfig = meta.scopes[effectiveScope];
235
284
  const isReadonly = targetScopeConfig?.readonly || effectiveScope === 'system';
285
+ const isUserScope = effectiveScope === 'user';
236
286
  const parsed = parseSource(targetSource);
237
287
  const spinner = ora(`Syncing from ${effectiveScope} scope...`).start();
238
288
  try {
@@ -243,75 +293,195 @@ program
243
293
  console.log(chalk.yellow(`No ${MANIFEST_FILENAME} found in repository`));
244
294
  }
245
295
  // Discover all assets
246
- const commands = discoverCommands(localPath);
247
- const skills = discoverSkillsFromRepo(localPath);
296
+ const allCommands = discoverCommands(localPath);
297
+ const allSkills = discoverSkillsFromRepo(localPath);
248
298
  const discoveredHooks = discoverHooksFromRepo(localPath);
249
- const totalHooks = discoveredHooks.shared.length +
250
- Object.values(discoveredHooks.agentSpecific).reduce((sum, arr) => sum + arr.length, 0);
251
- console.log(chalk.bold(`\nDiscovered assets:\n`));
252
- if (commands.length > 0) {
253
- console.log(` Commands: ${commands.length}`);
254
- for (const command of commands.slice(0, 5)) {
255
- const src = command.isShared ? 'shared' : command.agentSpecific;
256
- console.log(` ${chalk.cyan(command.name.padEnd(18))} ${chalk.gray(src)}`);
257
- }
258
- if (commands.length > 5) {
259
- console.log(chalk.gray(` ... and ${commands.length - 5} more`));
260
- }
299
+ // Determine which agents to sync
300
+ let selectedAgents;
301
+ if (agentFilter) {
302
+ // Single agent filter
303
+ selectedAgents = [agentFilter];
304
+ console.log(chalk.gray(`\nFiltering for ${AGENTS[agentFilter].name} only\n`));
261
305
  }
262
- if (skills.length > 0) {
263
- console.log(` Skills: ${skills.length}`);
264
- for (const skill of skills.slice(0, 5)) {
265
- console.log(` ${chalk.cyan(skill.name.padEnd(18))} ${chalk.gray(skill.metadata.description || '')}`);
266
- }
267
- if (skills.length > 5) {
268
- console.log(chalk.gray(` ... and ${skills.length - 5} more`));
306
+ else if (options.yes || options.force) {
307
+ selectedAgents = (manifest?.defaults?.agents || ['claude', 'codex', 'gemini']);
308
+ }
309
+ else {
310
+ const installedAgents = ALL_AGENT_IDS.filter((id) => isCliInstalled(id) || id === 'cursor');
311
+ selectedAgents = await checkbox({
312
+ message: 'Select agents to sync:',
313
+ choices: installedAgents.map((id) => ({
314
+ name: AGENTS[id].name,
315
+ value: id,
316
+ checked: (manifest?.defaults?.agents || ['claude', 'codex', 'gemini']).includes(id),
317
+ })),
318
+ });
319
+ }
320
+ // Filter agents to only installed ones (plus cursor which doesn't need CLI)
321
+ selectedAgents = selectedAgents.filter((id) => isCliInstalled(id) || id === 'cursor');
322
+ if (selectedAgents.length === 0) {
323
+ console.log(chalk.yellow('\nNo agents selected or installed. Nothing to sync.'));
324
+ return;
325
+ }
326
+ // Build resource items with conflict detection
327
+ const newItems = [];
328
+ const existingItems = [];
329
+ // Process commands
330
+ for (const command of allCommands) {
331
+ const applicableAgents = selectedAgents.filter((agentId) => {
332
+ const sourcePath = resolveCommandSource(localPath, command.name, agentId);
333
+ return sourcePath !== null;
334
+ });
335
+ if (applicableAgents.length === 0)
336
+ continue;
337
+ const conflictingAgents = applicableAgents.filter((agentId) => commandExists(agentId, command.name));
338
+ const newAgents = applicableAgents.filter((agentId) => !commandExists(agentId, command.name));
339
+ if (conflictingAgents.length > 0) {
340
+ existingItems.push({ type: 'command', name: command.name, agents: conflictingAgents, isNew: false });
341
+ }
342
+ if (newAgents.length > 0) {
343
+ newItems.push({ type: 'command', name: command.name, agents: newAgents, isNew: true });
344
+ }
345
+ }
346
+ // Process skills
347
+ const skillAgents = SKILLS_CAPABLE_AGENTS.filter((id) => selectedAgents.includes(id));
348
+ for (const skill of allSkills) {
349
+ const conflictingAgents = skillAgents.filter((agentId) => skillExists(agentId, skill.name));
350
+ const newAgents = skillAgents.filter((agentId) => !skillExists(agentId, skill.name));
351
+ if (conflictingAgents.length > 0) {
352
+ existingItems.push({ type: 'skill', name: skill.name, agents: conflictingAgents, isNew: false });
353
+ }
354
+ if (newAgents.length > 0) {
355
+ newItems.push({ type: 'skill', name: skill.name, agents: newAgents, isNew: true });
356
+ }
357
+ }
358
+ // Process hooks
359
+ const hookAgents = selectedAgents.filter((id) => HOOKS_CAPABLE_AGENTS.includes(id) && isCliInstalled(id));
360
+ const allHookNames = [
361
+ ...discoveredHooks.shared,
362
+ ...Object.entries(discoveredHooks.agentSpecific)
363
+ .filter(([agentId]) => hookAgents.includes(agentId))
364
+ .flatMap(([_, hooks]) => hooks),
365
+ ];
366
+ const uniqueHookNames = [...new Set(allHookNames)];
367
+ for (const hookName of uniqueHookNames) {
368
+ const conflictingAgents = hookAgents.filter((agentId) => hookExists(agentId, hookName));
369
+ const newAgents = hookAgents.filter((agentId) => !hookExists(agentId, hookName));
370
+ if (conflictingAgents.length > 0) {
371
+ existingItems.push({ type: 'hook', name: hookName, agents: conflictingAgents, isNew: false });
372
+ }
373
+ if (newAgents.length > 0) {
374
+ newItems.push({ type: 'hook', name: hookName, agents: newAgents, isNew: true });
375
+ }
376
+ }
377
+ // Process MCPs
378
+ if (!options.skipMcp && manifest?.mcp) {
379
+ for (const [name, config] of Object.entries(manifest.mcp)) {
380
+ if (config.transport === 'http' || !config.command)
381
+ continue;
382
+ const mcpAgents = config.agents.filter((agentId) => selectedAgents.includes(agentId) && isCliInstalled(agentId));
383
+ if (mcpAgents.length === 0)
384
+ continue;
385
+ const conflictingAgents = mcpAgents.filter((agentId) => isMcpRegistered(agentId, name));
386
+ const newAgents = mcpAgents.filter((agentId) => !isMcpRegistered(agentId, name));
387
+ if (conflictingAgents.length > 0) {
388
+ existingItems.push({ type: 'mcp', name, agents: conflictingAgents, isNew: false });
389
+ }
390
+ if (newAgents.length > 0) {
391
+ newItems.push({ type: 'mcp', name, agents: newAgents, isNew: true });
392
+ }
269
393
  }
270
394
  }
271
- if (totalHooks > 0) {
272
- console.log(` Hooks: ${totalHooks}`);
273
- for (const name of discoveredHooks.shared.slice(0, 3)) {
274
- console.log(` ${chalk.cyan(name.padEnd(18))} ${chalk.gray('shared')}`);
395
+ // Display overview
396
+ console.log(chalk.bold('\nOverview\n'));
397
+ const formatAgentList = (agents) => agents.map((id) => AGENTS[id].name).join(', ');
398
+ if (newItems.length > 0) {
399
+ console.log(chalk.green(' NEW (will install):\n'));
400
+ const byType = { command: [], skill: [], hook: [], mcp: [] };
401
+ for (const item of newItems)
402
+ byType[item.type].push(item);
403
+ if (byType.command.length > 0) {
404
+ console.log(` Commands:`);
405
+ for (const item of byType.command) {
406
+ console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
407
+ }
275
408
  }
276
- for (const [agentId, hooks] of Object.entries(discoveredHooks.agentSpecific)) {
277
- for (const name of hooks.slice(0, 2)) {
278
- console.log(` ${chalk.cyan(name.padEnd(18))} ${chalk.gray(agentId)}`);
409
+ if (byType.skill.length > 0) {
410
+ console.log(` Skills:`);
411
+ for (const item of byType.skill) {
412
+ console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
279
413
  }
280
414
  }
281
- if (totalHooks > 5) {
282
- console.log(chalk.gray(` ... and more`));
415
+ if (byType.hook.length > 0) {
416
+ console.log(` Hooks:`);
417
+ for (const item of byType.hook) {
418
+ console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
419
+ }
420
+ }
421
+ if (byType.mcp.length > 0) {
422
+ console.log(` MCP Servers:`);
423
+ for (const item of byType.mcp) {
424
+ console.log(` ${chalk.cyan(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
425
+ }
283
426
  }
427
+ console.log();
284
428
  }
285
- const mcpCount = manifest?.mcp ? Object.keys(manifest.mcp).length : 0;
286
- if (mcpCount > 0) {
287
- console.log(` MCP Servers: ${mcpCount}`);
288
- for (const name of Object.keys(manifest.mcp).slice(0, 5)) {
289
- console.log(` ${chalk.cyan(name)}`);
429
+ if (existingItems.length > 0) {
430
+ console.log(chalk.yellow(' EXISTING (conflicts):\n'));
431
+ const byType = { command: [], skill: [], hook: [], mcp: [] };
432
+ for (const item of existingItems)
433
+ byType[item.type].push(item);
434
+ if (byType.command.length > 0) {
435
+ console.log(` Commands:`);
436
+ for (const item of byType.command) {
437
+ console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
438
+ }
290
439
  }
291
- if (mcpCount > 5) {
292
- console.log(chalk.gray(` ... and ${mcpCount - 5} more`));
440
+ if (byType.skill.length > 0) {
441
+ console.log(` Skills:`);
442
+ for (const item of byType.skill) {
443
+ console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
444
+ }
293
445
  }
446
+ if (byType.hook.length > 0) {
447
+ console.log(` Hooks:`);
448
+ for (const item of byType.hook) {
449
+ console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
450
+ }
451
+ }
452
+ if (byType.mcp.length > 0) {
453
+ console.log(` MCP Servers:`);
454
+ for (const item of byType.mcp) {
455
+ console.log(` ${chalk.yellow(item.name.padEnd(20))} ${chalk.gray(formatAgentList(item.agents))}`);
456
+ }
457
+ }
458
+ console.log();
459
+ }
460
+ if (newItems.length === 0 && existingItems.length === 0) {
461
+ console.log(chalk.gray(' Nothing to sync.\n'));
462
+ return;
294
463
  }
295
464
  if (options.dryRun) {
296
- console.log(chalk.yellow('\nDry run - no changes made'));
465
+ console.log(chalk.yellow('Dry run - no changes made'));
297
466
  return;
298
467
  }
299
- let selectedAgents;
468
+ // Confirmation prompt
469
+ if (!options.yes && !options.force) {
470
+ const proceed = await confirm({
471
+ message: 'Proceed with installation?',
472
+ default: true,
473
+ });
474
+ if (!proceed) {
475
+ console.log(chalk.yellow('\nSync cancelled'));
476
+ return;
477
+ }
478
+ }
479
+ // Determine installation method
300
480
  let method;
301
- if (options.yes) {
302
- selectedAgents = (manifest?.defaults?.agents || ['claude', 'codex', 'gemini']);
481
+ if (options.yes || options.force) {
303
482
  method = manifest?.defaults?.method || 'symlink';
304
483
  }
305
484
  else {
306
- const installedAgents = ALL_AGENT_IDS.filter((id) => isCliInstalled(id) || id === 'cursor');
307
- selectedAgents = await checkbox({
308
- message: 'Select agents to sync:',
309
- choices: installedAgents.map((id) => ({
310
- name: AGENTS[id].name,
311
- value: id,
312
- checked: (manifest?.defaults?.agents || ['claude', 'codex', 'gemini']).includes(id),
313
- })),
314
- });
315
485
  method = await select({
316
486
  message: 'Installation method:',
317
487
  choices: [
@@ -321,64 +491,166 @@ program
321
491
  default: manifest?.defaults?.method || 'symlink',
322
492
  });
323
493
  }
324
- const defaultAgents = selectedAgents;
325
- const installSpinner = ora('Installing commands...').start();
326
- let installed = 0;
327
- for (const command of commands) {
328
- for (const agentId of defaultAgents) {
329
- if (!isCliInstalled(agentId) && agentId !== 'cursor')
330
- continue;
331
- const sourcePath = resolveCommandSource(localPath, command.name, agentId);
494
+ // Per-resource conflict decisions
495
+ const decisions = new Map();
496
+ if (existingItems.length > 0 && !options.force && !options.yes) {
497
+ console.log(chalk.bold('\nResolve conflicts:\n'));
498
+ for (const item of existingItems) {
499
+ const typeLabel = item.type.charAt(0).toUpperCase() + item.type.slice(1);
500
+ const agentList = formatAgentList(item.agents);
501
+ const decision = await select({
502
+ message: `${typeLabel} '${item.name}' exists (${agentList})`,
503
+ choices: [
504
+ { name: 'Overwrite', value: 'overwrite' },
505
+ { name: 'Skip', value: 'skip' },
506
+ { name: 'Cancel all', value: 'cancel' },
507
+ ],
508
+ });
509
+ if (decision === 'cancel') {
510
+ console.log(chalk.yellow('\nSync cancelled'));
511
+ return;
512
+ }
513
+ decisions.set(`${item.type}:${item.name}`, decision);
514
+ }
515
+ }
516
+ else if (options.force) {
517
+ // Force mode: overwrite all
518
+ for (const item of existingItems) {
519
+ decisions.set(`${item.type}:${item.name}`, 'overwrite');
520
+ }
521
+ }
522
+ else if (options.yes) {
523
+ // Yes mode: skip all conflicts
524
+ for (const item of existingItems) {
525
+ decisions.set(`${item.type}:${item.name}`, 'skip');
526
+ }
527
+ }
528
+ // Install new items (no conflicts)
529
+ console.log();
530
+ let installed = { commands: 0, skills: 0, hooks: 0, mcps: 0 };
531
+ let skipped = { commands: 0, skills: 0, hooks: 0, mcps: 0 };
532
+ // Install commands
533
+ const cmdSpinner = ora('Installing commands...').start();
534
+ for (const item of [...newItems, ...existingItems].filter((i) => i.type === 'command')) {
535
+ const decision = item.isNew ? 'overwrite' : decisions.get(`command:${item.name}`);
536
+ if (decision === 'skip') {
537
+ skipped.commands++;
538
+ continue;
539
+ }
540
+ for (const agentId of item.agents) {
541
+ const sourcePath = resolveCommandSource(localPath, item.name, agentId);
332
542
  if (sourcePath) {
333
- installCommand(sourcePath, agentId, command.name, method);
334
- installed++;
543
+ installCommand(sourcePath, agentId, item.name, method);
544
+ installed.commands++;
335
545
  }
336
546
  }
337
547
  }
338
- installSpinner.succeed(`Installed ${installed} command instances`);
548
+ if (skipped.commands > 0) {
549
+ cmdSpinner.succeed(`Installed ${installed.commands} commands (skipped ${skipped.commands})`);
550
+ }
551
+ else if (installed.commands > 0) {
552
+ cmdSpinner.succeed(`Installed ${installed.commands} commands`);
553
+ }
554
+ else {
555
+ cmdSpinner.info('No commands to install');
556
+ }
339
557
  // Install skills
340
- if (skills.length > 0) {
558
+ const skillItems = [...newItems, ...existingItems].filter((i) => i.type === 'skill');
559
+ if (skillItems.length > 0) {
341
560
  const skillSpinner = ora('Installing skills...').start();
342
- let skillsInstalled = 0;
343
- for (const skill of skills) {
344
- const skillAgents = SKILLS_CAPABLE_AGENTS.filter((id) => selectedAgents.includes(id) && (isCliInstalled(id) || id === 'cursor'));
345
- if (skillAgents.length > 0) {
346
- const result = installSkill(skill.path, skill.name, skillAgents);
561
+ for (const item of skillItems) {
562
+ const decision = item.isNew ? 'overwrite' : decisions.get(`skill:${item.name}`);
563
+ if (decision === 'skip') {
564
+ skipped.skills++;
565
+ continue;
566
+ }
567
+ const skill = allSkills.find((s) => s.name === item.name);
568
+ if (skill) {
569
+ const result = installSkill(skill.path, skill.name, item.agents);
347
570
  if (result.success)
348
- skillsInstalled++;
571
+ installed.skills++;
349
572
  }
350
573
  }
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`);
574
+ if (skipped.skills > 0) {
575
+ skillSpinner.succeed(`Installed ${installed.skills} skills (skipped ${skipped.skills})`);
576
+ }
577
+ else if (installed.skills > 0) {
578
+ skillSpinner.succeed(`Installed ${installed.skills} skills`);
579
+ }
580
+ else {
581
+ skillSpinner.info('No skills to install');
360
582
  }
361
583
  }
362
- if (!options.skipMcp && manifest?.mcp) {
584
+ // Install hooks
585
+ const hookItems = [...newItems, ...existingItems].filter((i) => i.type === 'hook');
586
+ if (hookItems.length > 0 && hookAgents.length > 0) {
587
+ const hookSpinner = ora('Installing hooks...').start();
588
+ const result = await installHooks(localPath, hookAgents, { scope: 'user' });
589
+ hookSpinner.succeed(`Installed ${result.installed.length} hooks`);
590
+ }
591
+ // Register MCP servers
592
+ const mcpItems = [...newItems, ...existingItems].filter((i) => i.type === 'mcp');
593
+ if (mcpItems.length > 0 && manifest?.mcp) {
363
594
  const mcpSpinner = ora('Registering MCP servers...').start();
364
- let registered = 0;
365
- for (const [name, config] of Object.entries(manifest.mcp)) {
366
- // Skip HTTP transport MCPs for now (need different registration)
367
- if (config.transport === 'http' || !config.command)
595
+ for (const item of mcpItems) {
596
+ const decision = item.isNew ? 'overwrite' : decisions.get(`mcp:${item.name}`);
597
+ if (decision === 'skip') {
598
+ skipped.mcps++;
599
+ continue;
600
+ }
601
+ const config = manifest.mcp[item.name];
602
+ if (!config || !config.command)
368
603
  continue;
369
- for (const agentId of config.agents) {
370
- if (!isCliInstalled(agentId))
371
- continue;
372
- if (isMcpRegistered(agentId, name))
373
- continue;
374
- const result = registerMcp(agentId, name, config.command, config.scope);
604
+ for (const agentId of item.agents) {
605
+ if (!item.isNew) {
606
+ unregisterMcp(agentId, item.name);
607
+ }
608
+ const result = registerMcp(agentId, item.name, config.command, config.scope);
375
609
  if (result.success)
376
- registered++;
610
+ installed.mcps++;
611
+ }
612
+ }
613
+ if (skipped.mcps > 0) {
614
+ mcpSpinner.succeed(`Registered ${installed.mcps} MCP servers (skipped ${skipped.mcps})`);
615
+ }
616
+ else if (installed.mcps > 0) {
617
+ mcpSpinner.succeed(`Registered ${installed.mcps} MCP servers`);
618
+ }
619
+ else {
620
+ mcpSpinner.info('No MCP servers to register');
621
+ }
622
+ }
623
+ // Sync CLI versions (user scope only)
624
+ if (isUserScope && !options.skipClis && manifest?.clis) {
625
+ const cliSpinner = ora('Checking CLI versions...').start();
626
+ const cliUpdates = [];
627
+ for (const [agentIdStr, cliConfig] of Object.entries(manifest.clis)) {
628
+ const agentId = agentIdStr;
629
+ if (agentFilter && agentId !== agentFilter)
630
+ continue;
631
+ const agent = AGENTS[agentId];
632
+ if (!agent || !cliConfig.package)
633
+ continue;
634
+ const currentVersion = getCliVersion(agentId);
635
+ const targetVersion = cliConfig.version;
636
+ if (currentVersion === targetVersion)
637
+ continue;
638
+ if (targetVersion === 'latest' && currentVersion)
639
+ continue;
640
+ cliUpdates.push(`${agent.name}: ${currentVersion || 'not installed'} -> ${targetVersion}`);
641
+ }
642
+ if (cliUpdates.length > 0) {
643
+ cliSpinner.info('CLI version differences detected');
644
+ console.log(chalk.gray(' Run `agents cli upgrade` to update CLIs'));
645
+ for (const update of cliUpdates) {
646
+ console.log(chalk.gray(` ${update}`));
377
647
  }
378
648
  }
379
- mcpSpinner.succeed(`Registered ${registered} MCP servers`);
649
+ else {
650
+ cliSpinner.succeed('CLI versions match');
651
+ }
380
652
  }
381
- // Update scope config (only if not readonly)
653
+ // Update scope config
382
654
  if (!isReadonly) {
383
655
  const priority = getScopePriority(effectiveScope);
384
656
  setScope(effectiveScope, {
@@ -393,6 +665,10 @@ program
393
665
  console.log(chalk.green(`\nSync complete from ${effectiveScope} scope`));
394
666
  }
395
667
  catch (err) {
668
+ if (isPromptCancelled(err)) {
669
+ console.log(chalk.yellow('\nCancelled'));
670
+ process.exit(0);
671
+ }
396
672
  spinner.fail('Failed to sync');
397
673
  console.error(chalk.red(err.message));
398
674
  process.exit(1);
@@ -1800,7 +2076,7 @@ program
1800
2076
  const spinner = ora('Checking for updates...').start();
1801
2077
  try {
1802
2078
  // Get current version from package.json
1803
- const currentVersion = program.version();
2079
+ const currentVersion = program.version() || '0.0.0';
1804
2080
  // Fetch latest version from npm
1805
2081
  const response = await fetch('https://registry.npmjs.org/@swarmify/agents-cli/latest');
1806
2082
  if (!response.ok) {
@@ -1812,7 +2088,7 @@ program
1812
2088
  spinner.succeed(`Already on latest version (${currentVersion})`);
1813
2089
  return;
1814
2090
  }
1815
- spinner.text = `Upgrading from ${currentVersion} to ${latestVersion}...`;
2091
+ spinner.text = `Upgrading to ${latestVersion}...`;
1816
2092
  // Detect package manager
1817
2093
  const { execSync } = await import('child_process');
1818
2094
  let cmd;
@@ -1841,8 +2117,11 @@ program
1841
2117
  cmd = 'npm install -g @swarmify/agents-cli@latest';
1842
2118
  }
1843
2119
  }
1844
- execSync(cmd, { stdio: 'inherit' });
2120
+ // Run silently (suppress npm/bun output)
2121
+ execSync(cmd, { stdio: 'pipe' });
1845
2122
  spinner.succeed(`Upgraded to ${latestVersion}`);
2123
+ // Show what's new from changelog
2124
+ await showWhatsNew(currentVersion, latestVersion);
1846
2125
  }
1847
2126
  catch (err) {
1848
2127
  spinner.fail('Upgrade failed');
@@ -1851,5 +2130,67 @@ program
1851
2130
  process.exit(1);
1852
2131
  }
1853
2132
  });
2133
+ async function showWhatsNew(fromVersion, toVersion) {
2134
+ try {
2135
+ // Fetch changelog from npm package
2136
+ const response = await fetch(`https://unpkg.com/@swarmify/agents-cli@${toVersion}/CHANGELOG.md`);
2137
+ if (!response.ok)
2138
+ return;
2139
+ const changelog = await response.text();
2140
+ const lines = changelog.split('\n');
2141
+ // Parse changelog to find relevant sections
2142
+ const relevantChanges = [];
2143
+ let inRelevantSection = false;
2144
+ let currentVersion = '';
2145
+ for (const line of lines) {
2146
+ // Check for version header (## 1.5.0)
2147
+ const versionMatch = line.match(/^## (\d+\.\d+\.\d+)/);
2148
+ if (versionMatch) {
2149
+ currentVersion = versionMatch[1];
2150
+ // Include versions newer than fromVersion
2151
+ const isNewer = currentVersion !== fromVersion &&
2152
+ compareVersions(currentVersion, fromVersion) > 0;
2153
+ inRelevantSection = isNewer;
2154
+ if (inRelevantSection) {
2155
+ relevantChanges.push('');
2156
+ relevantChanges.push(chalk.bold(`v${currentVersion}`));
2157
+ }
2158
+ continue;
2159
+ }
2160
+ if (inRelevantSection && line.trim()) {
2161
+ // Format the line
2162
+ if (line.startsWith('**') && line.endsWith('**')) {
2163
+ // Section header like **Pull command redesign**
2164
+ relevantChanges.push(chalk.cyan(line.replace(/\*\*/g, '')));
2165
+ }
2166
+ else if (line.startsWith('- ')) {
2167
+ // Bullet point
2168
+ relevantChanges.push(chalk.gray(` ${line}`));
2169
+ }
2170
+ }
2171
+ }
2172
+ if (relevantChanges.length > 0) {
2173
+ console.log(chalk.bold("\nWhat's new:\n"));
2174
+ for (const line of relevantChanges) {
2175
+ console.log(line);
2176
+ }
2177
+ console.log();
2178
+ }
2179
+ }
2180
+ catch {
2181
+ // Silently ignore changelog fetch errors
2182
+ }
2183
+ }
2184
+ function compareVersions(a, b) {
2185
+ const partsA = a.split('.').map(Number);
2186
+ const partsB = b.split('.').map(Number);
2187
+ for (let i = 0; i < 3; i++) {
2188
+ if (partsA[i] > partsB[i])
2189
+ return 1;
2190
+ if (partsA[i] < partsB[i])
2191
+ return -1;
2192
+ }
2193
+ return 0;
2194
+ }
1854
2195
  program.parse();
1855
2196
  //# sourceMappingURL=index.js.map