@swarmify/agents-cli 1.5.7 → 1.5.9

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
@@ -21,9 +21,9 @@ import { readManifest, writeManifest, createDefaultManifest, MANIFEST_FILENAME,
21
21
  import { readState, ensureAgentsDir, getRepoLocalPath, getScope, setScope, removeScope, getScopesByPriority, getScopePriority, } from './lib/state.js';
22
22
  import { SCOPE_PRIORITIES, DEFAULT_SYSTEM_REPO } from './lib/types.js';
23
23
  import { cloneRepo, parseSource } from './lib/git.js';
24
- import { discoverCommands, resolveCommandSource, installCommand, uninstallCommand, listInstalledCommandsWithScope, promoteCommandToUser, commandExists, } from './lib/commands.js';
25
- import { discoverHooksFromRepo, installHooks, listInstalledHooksWithScope, promoteHookToUser, removeHook, hookExists, } from './lib/hooks.js';
26
- import { discoverSkillsFromRepo, installSkill, uninstallSkill, listInstalledSkillsWithScope, promoteSkillToUser, getSkillInfo, getSkillRules, skillExists, } from './lib/skills.js';
24
+ import { discoverCommands, resolveCommandSource, installCommand, uninstallCommand, listInstalledCommandsWithScope, promoteCommandToUser, commandExists, commandContentMatches, } from './lib/commands.js';
25
+ import { discoverHooksFromRepo, installHooks, listInstalledHooksWithScope, promoteHookToUser, removeHook, hookExists, hookContentMatches, getSourceHookEntry, } from './lib/hooks.js';
26
+ import { discoverSkillsFromRepo, installSkill, uninstallSkill, listInstalledSkillsWithScope, promoteSkillToUser, getSkillInfo, getSkillRules, skillExists, skillContentMatches, } from './lib/skills.js';
27
27
  import { DEFAULT_REGISTRIES } from './lib/types.js';
28
28
  import { search as searchRegistries, getRegistries, setRegistry, removeRegistry, resolvePackage, } from './lib/registry.js';
29
29
  const program = new Command();
@@ -95,10 +95,12 @@ async function checkForUpdates() {
95
95
  });
96
96
  if (answer === 'now') {
97
97
  // Run upgrade
98
- const { execSync } = await import('child_process');
98
+ const { exec } = await import('child_process');
99
+ const { promisify } = await import('util');
100
+ const execAsync = promisify(exec);
99
101
  const spinner = ora('Upgrading...').start();
100
102
  try {
101
- execSync('npm install -g @swarmify/agents-cli@latest', { stdio: 'pipe' });
103
+ await execAsync('npm install -g @swarmify/agents-cli@latest');
102
104
  spinner.succeed(`Upgraded to ${latestVersion}`);
103
105
  await showWhatsNew(VERSION, latestVersion);
104
106
  }
@@ -155,7 +157,7 @@ program
155
157
  }
156
158
  }
157
159
  const cwd = process.cwd();
158
- const cliStates = getAllCliStates();
160
+ const cliStates = await getAllCliStates();
159
161
  const agentsToShow = filterAgentId ? [filterAgentId] : ALL_AGENT_IDS;
160
162
  const skillAgentsToShow = filterAgentId
161
163
  ? SKILLS_CAPABLE_AGENTS.filter((id) => id === filterAgentId)
@@ -172,9 +174,8 @@ program
172
174
  agent: AGENTS[agentId],
173
175
  skills: listInstalledSkillsWithScope(agentId, cwd),
174
176
  }));
175
- const mcpsData = mcpAgentsToShow
176
- .filter((agentId) => isCliInstalled(agentId))
177
- .map((agentId) => ({
177
+ const installedMcpAgents = mcpAgentsToShow.filter((agentId) => cliStates[agentId]?.installed);
178
+ const mcpsData = installedMcpAgents.map((agentId) => ({
178
179
  agent: AGENTS[agentId],
179
180
  mcps: listInstalledMcpsWithScope(agentId, cwd),
180
181
  }));
@@ -364,6 +365,7 @@ program
364
365
  const allSkills = discoverSkillsFromRepo(localPath);
365
366
  const discoveredHooks = discoverHooksFromRepo(localPath);
366
367
  // Determine which agents to sync
368
+ const cliStates = await getAllCliStates();
367
369
  let selectedAgents;
368
370
  if (agentFilter) {
369
371
  // Single agent filter
@@ -374,7 +376,7 @@ program
374
376
  selectedAgents = (manifest?.defaults?.agents || ['claude', 'codex', 'gemini']);
375
377
  }
376
378
  else {
377
- const installedAgents = ALL_AGENT_IDS.filter((id) => isCliInstalled(id) || id === 'cursor');
379
+ const installedAgents = ALL_AGENT_IDS.filter((id) => cliStates[id]?.installed || id === 'cursor');
378
380
  selectedAgents = await checkbox({
379
381
  message: 'Select agents to sync:',
380
382
  choices: installedAgents.map((id) => ({
@@ -385,7 +387,7 @@ program
385
387
  });
386
388
  }
387
389
  // Filter agents to only installed ones (plus cursor which doesn't need CLI)
388
- selectedAgents = selectedAgents.filter((id) => isCliInstalled(id) || id === 'cursor');
390
+ selectedAgents = selectedAgents.filter((id) => cliStates[id]?.installed || id === 'cursor');
389
391
  if (selectedAgents.length === 0) {
390
392
  console.log(chalk.yellow('\nNo agents selected or installed. Nothing to sync.'));
391
393
  return;
@@ -393,6 +395,7 @@ program
393
395
  // Build resource items with conflict detection
394
396
  const newItems = [];
395
397
  const existingItems = [];
398
+ const upToDateItems = [];
396
399
  // Process commands
397
400
  for (const command of allCommands) {
398
401
  const applicableAgents = selectedAgents.filter((agentId) => {
@@ -401,29 +404,55 @@ program
401
404
  });
402
405
  if (applicableAgents.length === 0)
403
406
  continue;
404
- const conflictingAgents = applicableAgents.filter((agentId) => commandExists(agentId, command.name));
405
407
  const newAgents = applicableAgents.filter((agentId) => !commandExists(agentId, command.name));
406
- if (conflictingAgents.length > 0) {
407
- existingItems.push({ type: 'command', name: command.name, agents: conflictingAgents, isNew: false });
408
- }
408
+ const upToDateAgents = applicableAgents.filter((agentId) => {
409
+ if (!commandExists(agentId, command.name))
410
+ return false;
411
+ const sourcePath = resolveCommandSource(localPath, command.name, agentId);
412
+ return sourcePath && commandContentMatches(agentId, command.name, sourcePath);
413
+ });
414
+ const conflictingAgents = applicableAgents.filter((agentId) => {
415
+ if (!commandExists(agentId, command.name))
416
+ return false;
417
+ const sourcePath = resolveCommandSource(localPath, command.name, agentId);
418
+ return sourcePath && !commandContentMatches(agentId, command.name, sourcePath);
419
+ });
409
420
  if (newAgents.length > 0) {
410
421
  newItems.push({ type: 'command', name: command.name, agents: newAgents, isNew: true });
411
422
  }
423
+ if (upToDateAgents.length > 0) {
424
+ upToDateItems.push({ type: 'command', name: command.name, agents: upToDateAgents, isNew: false });
425
+ }
426
+ if (conflictingAgents.length > 0) {
427
+ existingItems.push({ type: 'command', name: command.name, agents: conflictingAgents, isNew: false });
428
+ }
412
429
  }
413
430
  // Process skills
414
431
  const skillAgents = SKILLS_CAPABLE_AGENTS.filter((id) => selectedAgents.includes(id));
415
432
  for (const skill of allSkills) {
416
- const conflictingAgents = skillAgents.filter((agentId) => skillExists(agentId, skill.name));
417
433
  const newAgents = skillAgents.filter((agentId) => !skillExists(agentId, skill.name));
418
- if (conflictingAgents.length > 0) {
419
- existingItems.push({ type: 'skill', name: skill.name, agents: conflictingAgents, isNew: false });
420
- }
434
+ const upToDateAgents = skillAgents.filter((agentId) => {
435
+ if (!skillExists(agentId, skill.name))
436
+ return false;
437
+ return skillContentMatches(agentId, skill.name, skill.path);
438
+ });
439
+ const conflictingAgents = skillAgents.filter((agentId) => {
440
+ if (!skillExists(agentId, skill.name))
441
+ return false;
442
+ return !skillContentMatches(agentId, skill.name, skill.path);
443
+ });
421
444
  if (newAgents.length > 0) {
422
445
  newItems.push({ type: 'skill', name: skill.name, agents: newAgents, isNew: true });
423
446
  }
447
+ if (upToDateAgents.length > 0) {
448
+ upToDateItems.push({ type: 'skill', name: skill.name, agents: upToDateAgents, isNew: false });
449
+ }
450
+ if (conflictingAgents.length > 0) {
451
+ existingItems.push({ type: 'skill', name: skill.name, agents: conflictingAgents, isNew: false });
452
+ }
424
453
  }
425
454
  // Process hooks
426
- const hookAgents = selectedAgents.filter((id) => HOOKS_CAPABLE_AGENTS.includes(id) && isCliInstalled(id));
455
+ const hookAgents = selectedAgents.filter((id) => HOOKS_CAPABLE_AGENTS.includes(id) && cliStates[id]?.installed);
427
456
  const allHookNames = [
428
457
  ...discoveredHooks.shared,
429
458
  ...Object.entries(discoveredHooks.agentSpecific)
@@ -432,25 +461,43 @@ program
432
461
  ];
433
462
  const uniqueHookNames = [...new Set(allHookNames)];
434
463
  for (const hookName of uniqueHookNames) {
435
- const conflictingAgents = hookAgents.filter((agentId) => hookExists(agentId, hookName));
436
464
  const newAgents = hookAgents.filter((agentId) => !hookExists(agentId, hookName));
437
- if (conflictingAgents.length > 0) {
438
- existingItems.push({ type: 'hook', name: hookName, agents: conflictingAgents, isNew: false });
439
- }
465
+ const upToDateAgents = hookAgents.filter((agentId) => {
466
+ if (!hookExists(agentId, hookName))
467
+ return false;
468
+ const sourceEntry = getSourceHookEntry(localPath, agentId, hookName);
469
+ return sourceEntry && hookContentMatches(agentId, hookName, sourceEntry);
470
+ });
471
+ const conflictingAgents = hookAgents.filter((agentId) => {
472
+ if (!hookExists(agentId, hookName))
473
+ return false;
474
+ const sourceEntry = getSourceHookEntry(localPath, agentId, hookName);
475
+ return !sourceEntry || !hookContentMatches(agentId, hookName, sourceEntry);
476
+ });
440
477
  if (newAgents.length > 0) {
441
478
  newItems.push({ type: 'hook', name: hookName, agents: newAgents, isNew: true });
442
479
  }
480
+ if (upToDateAgents.length > 0) {
481
+ upToDateItems.push({ type: 'hook', name: hookName, agents: upToDateAgents, isNew: false });
482
+ }
483
+ if (conflictingAgents.length > 0) {
484
+ existingItems.push({ type: 'hook', name: hookName, agents: conflictingAgents, isNew: false });
485
+ }
443
486
  }
444
- // Process MCPs
487
+ // Process MCPs (no content comparison - just existence check)
445
488
  if (!options.skipMcp && manifest?.mcp) {
446
489
  for (const [name, config] of Object.entries(manifest.mcp)) {
447
490
  if (config.transport === 'http' || !config.command)
448
491
  continue;
449
- const mcpAgents = config.agents.filter((agentId) => selectedAgents.includes(agentId) && isCliInstalled(agentId));
492
+ const mcpAgents = config.agents.filter((agentId) => selectedAgents.includes(agentId) && cliStates[agentId]?.installed);
450
493
  if (mcpAgents.length === 0)
451
494
  continue;
452
- const conflictingAgents = mcpAgents.filter((agentId) => isMcpRegistered(agentId, name));
453
- const newAgents = mcpAgents.filter((agentId) => !isMcpRegistered(agentId, name));
495
+ const registrationChecks = await Promise.all(mcpAgents.map(async (agentId) => ({
496
+ agentId,
497
+ isRegistered: await isMcpRegistered(agentId, name),
498
+ })));
499
+ const conflictingAgents = registrationChecks.filter((r) => r.isRegistered).map((r) => r.agentId);
500
+ const newAgents = registrationChecks.filter((r) => !r.isRegistered).map((r) => r.agentId);
454
501
  if (conflictingAgents.length > 0) {
455
502
  existingItems.push({ type: 'mcp', name, agents: conflictingAgents, isNew: false });
456
503
  }
@@ -493,6 +540,31 @@ program
493
540
  }
494
541
  console.log();
495
542
  }
543
+ if (upToDateItems.length > 0) {
544
+ console.log(chalk.gray(' UP TO DATE (no changes):\n'));
545
+ const byType = { command: [], skill: [], hook: [], mcp: [] };
546
+ for (const item of upToDateItems)
547
+ byType[item.type].push(item);
548
+ if (byType.command.length > 0) {
549
+ console.log(` Commands:`);
550
+ for (const item of byType.command) {
551
+ console.log(` ${chalk.dim(item.name.padEnd(20))} ${chalk.dim(formatAgentList(item.agents))}`);
552
+ }
553
+ }
554
+ if (byType.skill.length > 0) {
555
+ console.log(` Skills:`);
556
+ for (const item of byType.skill) {
557
+ console.log(` ${chalk.dim(item.name.padEnd(20))} ${chalk.dim(formatAgentList(item.agents))}`);
558
+ }
559
+ }
560
+ if (byType.hook.length > 0) {
561
+ console.log(` Hooks:`);
562
+ for (const item of byType.hook) {
563
+ console.log(` ${chalk.dim(item.name.padEnd(20))} ${chalk.dim(formatAgentList(item.agents))}`);
564
+ }
565
+ }
566
+ console.log();
567
+ }
496
568
  if (existingItems.length > 0) {
497
569
  console.log(chalk.yellow(' EXISTING (conflicts):\n'));
498
570
  const byType = { command: [], skill: [], hook: [], mcp: [] };
@@ -525,7 +597,7 @@ program
525
597
  console.log();
526
598
  }
527
599
  if (newItems.length === 0 && existingItems.length === 0) {
528
- console.log(chalk.gray(' Nothing to sync.\n'));
600
+ console.log(chalk.gray(' Already up to date.\n'));
529
601
  return;
530
602
  }
531
603
  if (options.dryRun) {
@@ -670,9 +742,9 @@ program
670
742
  continue;
671
743
  for (const agentId of item.agents) {
672
744
  if (!item.isNew) {
673
- unregisterMcp(agentId, item.name);
745
+ await unregisterMcp(agentId, item.name);
674
746
  }
675
- const result = registerMcp(agentId, item.name, config.command, config.scope);
747
+ const result = await registerMcp(agentId, item.name, config.command, config.scope);
676
748
  if (result.success)
677
749
  installed.mcps++;
678
750
  }
@@ -698,7 +770,7 @@ program
698
770
  const agent = AGENTS[agentId];
699
771
  if (!agent || !cliConfig.package)
700
772
  continue;
701
- const currentVersion = getCliVersion(agentId);
773
+ const currentVersion = await getCliVersion(agentId);
702
774
  const targetVersion = cliConfig.version;
703
775
  if (currentVersion === targetVersion)
704
776
  continue;
@@ -765,7 +837,7 @@ program
765
837
  const localPath = getRepoLocalPath(scope.source);
766
838
  const manifest = readManifest(localPath) || createDefaultManifest();
767
839
  console.log(chalk.bold('\nExporting local configuration...\n'));
768
- const cliStates = getAllCliStates();
840
+ const cliStates = await getAllCliStates();
769
841
  let exported = 0;
770
842
  for (const agentId of ALL_AGENT_IDS) {
771
843
  const agent = AGENTS[agentId];
@@ -872,10 +944,11 @@ commandsCmd
872
944
  const agents = options.agents
873
945
  ? options.agents.split(',')
874
946
  : ['claude', 'codex', 'gemini'];
947
+ const cliStates = await getAllCliStates();
875
948
  for (const command of commands) {
876
949
  console.log(`\n ${chalk.cyan(command.name)}: ${command.description}`);
877
950
  for (const agentId of agents) {
878
- if (!isCliInstalled(agentId) && agentId !== 'cursor')
951
+ if (!cliStates[agentId]?.installed && agentId !== 'cursor')
879
952
  continue;
880
953
  const sourcePath = resolveCommandSource(localPath, command.name, agentId);
881
954
  if (sourcePath) {
@@ -918,14 +991,15 @@ commandsCmd
918
991
  .command('push <name>')
919
992
  .description('Save project-scoped command to user scope')
920
993
  .option('-a, --agents <list>', 'Comma-separated agents to push for')
921
- .action((name, options) => {
994
+ .action(async (name, options) => {
922
995
  const cwd = process.cwd();
923
996
  const agents = options.agents
924
997
  ? options.agents.split(',')
925
998
  : ALL_AGENT_IDS;
999
+ const cliStates = await getAllCliStates();
926
1000
  let pushed = 0;
927
1001
  for (const agentId of agents) {
928
- if (!isCliInstalled(agentId) && agentId !== 'cursor')
1002
+ if (!cliStates[agentId]?.installed && agentId !== 'cursor')
929
1003
  continue;
930
1004
  const result = promoteCommandToUser(agentId, name, cwd);
931
1005
  if (result.success) {
@@ -1185,11 +1259,12 @@ skillsCmd
1185
1259
  console.log(` ${chalk.gray(`${skill.ruleCount} rules`)}`);
1186
1260
  }
1187
1261
  }
1262
+ const cliStates = await getAllCliStates();
1188
1263
  const agents = options.agents
1189
1264
  ? options.agents.split(',')
1190
1265
  : await checkbox({
1191
1266
  message: 'Select agents to install skills to:',
1192
- choices: SKILLS_CAPABLE_AGENTS.filter((id) => isCliInstalled(id) || id === 'cursor').map((id) => ({
1267
+ choices: SKILLS_CAPABLE_AGENTS.filter((id) => cliStates[id]?.installed || id === 'cursor').map((id) => ({
1193
1268
  name: AGENTS[id].name,
1194
1269
  value: id,
1195
1270
  checked: ['claude', 'codex', 'gemini'].includes(id),
@@ -1368,12 +1443,14 @@ mcpCmd
1368
1443
  const agents = options.agent
1369
1444
  ? [options.agent]
1370
1445
  : MCP_CAPABLE_AGENTS;
1446
+ // Collect all data while spinner is active
1447
+ const cliStates = await getAllCliStates();
1371
1448
  const agentMcps = agents.map((agentId) => {
1372
1449
  const agent = AGENTS[agentId];
1373
1450
  if (!agent.capabilities.mcp) {
1374
1451
  return { agent, mcps: null };
1375
1452
  }
1376
- if (!isCliInstalled(agentId)) {
1453
+ if (!cliStates[agentId]?.installed) {
1377
1454
  return { agent, mcps: null, notInstalled: true };
1378
1455
  }
1379
1456
  return {
@@ -1474,15 +1551,16 @@ mcpCmd
1474
1551
  .command('remove <name>')
1475
1552
  .description('Remove MCP server from agents')
1476
1553
  .option('-a, --agents <list>', 'Comma-separated agents')
1477
- .action((name, options) => {
1554
+ .action(async (name, options) => {
1478
1555
  const agents = options.agents
1479
1556
  ? options.agents.split(',')
1480
1557
  : MCP_CAPABLE_AGENTS;
1558
+ const cliStates = await getAllCliStates();
1481
1559
  let removed = 0;
1482
1560
  for (const agentId of agents) {
1483
- if (!isCliInstalled(agentId))
1561
+ if (!cliStates[agentId]?.installed)
1484
1562
  continue;
1485
- const result = unregisterMcp(agentId, name);
1563
+ const result = await unregisterMcp(agentId, name);
1486
1564
  if (result.success) {
1487
1565
  console.log(` ${chalk.red('-')} ${AGENTS[agentId].name}`);
1488
1566
  removed++;
@@ -1508,6 +1586,7 @@ mcpCmd
1508
1586
  console.log(chalk.yellow('No MCP servers in manifest'));
1509
1587
  return;
1510
1588
  }
1589
+ const cliStates = await getAllCliStates();
1511
1590
  for (const [mcpName, config] of Object.entries(manifest.mcp)) {
1512
1591
  // Skip HTTP transport MCPs for now (need different registration)
1513
1592
  if (config.transport === 'http' || !config.command) {
@@ -1516,9 +1595,9 @@ mcpCmd
1516
1595
  }
1517
1596
  console.log(`\n ${chalk.cyan(mcpName)}:`);
1518
1597
  for (const agentId of config.agents) {
1519
- if (!isCliInstalled(agentId))
1598
+ if (!cliStates[agentId]?.installed)
1520
1599
  continue;
1521
- const result = registerMcp(agentId, mcpName, config.command, config.scope);
1600
+ const result = await registerMcp(agentId, mcpName, config.command, config.scope);
1522
1601
  if (result.success) {
1523
1602
  console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
1524
1603
  }
@@ -1535,16 +1614,17 @@ mcpCmd
1535
1614
  .command('push <name>')
1536
1615
  .description('Save project-scoped MCP to user scope')
1537
1616
  .option('-a, --agents <list>', 'Comma-separated agents to push for')
1538
- .action((name, options) => {
1617
+ .action(async (name, options) => {
1539
1618
  const cwd = process.cwd();
1540
1619
  const agents = options.agents
1541
1620
  ? options.agents.split(',')
1542
1621
  : MCP_CAPABLE_AGENTS;
1622
+ const cliStates = await getAllCliStates();
1543
1623
  let pushed = 0;
1544
1624
  for (const agentId of agents) {
1545
- if (!isCliInstalled(agentId))
1625
+ if (!cliStates[agentId]?.installed)
1546
1626
  continue;
1547
- const result = promoteMcpToUser(agentId, name, cwd);
1627
+ const result = await promoteMcpToUser(agentId, name, cwd);
1548
1628
  if (result.success) {
1549
1629
  console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
1550
1630
  pushed++;
@@ -1571,7 +1651,7 @@ cliCmd
1571
1651
  .description('List installed agent CLIs')
1572
1652
  .action(async () => {
1573
1653
  const spinner = ora('Checking installed CLIs...').start();
1574
- const states = getAllCliStates();
1654
+ const states = await getAllCliStates();
1575
1655
  spinner.stop();
1576
1656
  console.log(chalk.bold('Agent CLIs\n'));
1577
1657
  for (const agentId of ALL_AGENT_IDS) {
@@ -1589,93 +1669,124 @@ cliCmd
1589
1669
  }
1590
1670
  });
1591
1671
  cliCmd
1592
- .command('add <agent>')
1593
- .description('Install agent CLI and add to manifest')
1672
+ .command('add <agents...>')
1673
+ .description('Install agent CLI(s)')
1594
1674
  .option('-v, --version <version>', 'Version to install', 'latest')
1595
1675
  .option('--manifest-only', 'Only add to manifest, do not install')
1596
- .action(async (agent, options) => {
1597
- const agentId = agent.toLowerCase();
1598
- if (!AGENTS[agentId]) {
1599
- console.log(chalk.red(`Unknown agent: ${agent}`));
1600
- console.log(chalk.gray(`Available: ${ALL_AGENT_IDS.join(', ')}`));
1601
- return;
1676
+ .action(async (agents, options) => {
1677
+ const validAgents = [];
1678
+ for (const agent of agents) {
1679
+ const agentId = agent.toLowerCase();
1680
+ if (!AGENTS[agentId]) {
1681
+ console.log(chalk.red(`Unknown agent: ${agent}`));
1682
+ console.log(chalk.gray(`Available: ${ALL_AGENT_IDS.join(', ')}`));
1683
+ return;
1684
+ }
1685
+ validAgents.push(agentId);
1602
1686
  }
1603
- const agentConfig = AGENTS[agentId];
1604
- const pkg = agentConfig.npmPackage;
1687
+ const { exec } = await import('child_process');
1688
+ const { promisify } = await import('util');
1689
+ const execAsync = promisify(exec);
1605
1690
  const version = options.version;
1606
- // Install the CLI
1607
- if (!options.manifestOnly) {
1608
- if (!pkg) {
1609
- console.log(chalk.yellow(`${agentConfig.name} has no npm package. Install manually.`));
1610
- }
1611
- else {
1612
- const { execSync } = await import('child_process');
1613
- const spinner = ora(`Installing ${agentConfig.name}@${version}...`).start();
1614
- try {
1615
- execSync(`npm install -g ${pkg}@${version}`, { stdio: 'pipe' });
1616
- spinner.succeed(`Installed ${agentConfig.name}@${version}`);
1691
+ for (const agentId of validAgents) {
1692
+ const agentConfig = AGENTS[agentId];
1693
+ const pkg = agentConfig.npmPackage;
1694
+ if (!options.manifestOnly) {
1695
+ if (!pkg) {
1696
+ console.log(chalk.yellow(`${agentConfig.name} has no npm package. Install manually.`));
1617
1697
  }
1618
- catch (err) {
1619
- spinner.fail(`Failed to install ${agentConfig.name}`);
1620
- console.error(chalk.gray(err.message));
1621
- return;
1698
+ else {
1699
+ const spinner = ora(`Installing ${agentConfig.name}@${version}...`).start();
1700
+ try {
1701
+ await execAsync(`npm install -g ${pkg}@${version}`);
1702
+ spinner.succeed(`Installed ${agentConfig.name}@${version}`);
1703
+ }
1704
+ catch (err) {
1705
+ spinner.fail(`Failed to install ${agentConfig.name}`);
1706
+ console.error(chalk.gray(err.message));
1707
+ continue;
1708
+ }
1622
1709
  }
1623
1710
  }
1624
1711
  }
1625
- // Add to manifest
1626
1712
  const source = await ensureSource();
1627
1713
  const localPath = getRepoLocalPath(source);
1628
1714
  const manifest = readManifest(localPath) || createDefaultManifest();
1629
1715
  manifest.clis = manifest.clis || {};
1630
- manifest.clis[agentId] = {
1631
- package: pkg,
1632
- version: version,
1633
- };
1716
+ for (const agentId of validAgents) {
1717
+ const agentConfig = AGENTS[agentId];
1718
+ manifest.clis[agentId] = {
1719
+ package: agentConfig.npmPackage,
1720
+ version: version,
1721
+ };
1722
+ }
1634
1723
  writeManifest(localPath, manifest);
1635
- console.log(chalk.green(`Added ${agentConfig.name} to manifest`));
1724
+ if (validAgents.length === 1) {
1725
+ console.log(chalk.green(`Added ${AGENTS[validAgents[0]].name} to manifest`));
1726
+ }
1727
+ else {
1728
+ console.log(chalk.green(`Added ${validAgents.length} agents to manifest`));
1729
+ }
1636
1730
  });
1637
1731
  cliCmd
1638
- .command('remove <agent>')
1639
- .description('Uninstall agent CLI and remove from manifest')
1732
+ .command('remove <agents...>')
1733
+ .description('Uninstall agent CLI(s)')
1640
1734
  .option('--manifest-only', 'Only remove from manifest, do not uninstall')
1641
- .action(async (agent, options) => {
1642
- const agentId = agent.toLowerCase();
1643
- if (!AGENTS[agentId]) {
1644
- console.log(chalk.red(`Unknown agent: ${agent}`));
1645
- console.log(chalk.gray(`Available: ${ALL_AGENT_IDS.join(', ')}`));
1646
- return;
1647
- }
1648
- const agentConfig = AGENTS[agentId];
1649
- const pkg = agentConfig.npmPackage;
1650
- // Uninstall the CLI
1651
- if (!options.manifestOnly) {
1652
- if (!pkg) {
1653
- console.log(chalk.yellow(`${agentConfig.name} has no npm package.`));
1654
- }
1655
- else if (!isCliInstalled(agentId)) {
1656
- console.log(chalk.gray(`${agentConfig.name} is not installed`));
1735
+ .action(async (agents, options) => {
1736
+ const validAgents = [];
1737
+ for (const agent of agents) {
1738
+ const agentId = agent.toLowerCase();
1739
+ if (!AGENTS[agentId]) {
1740
+ console.log(chalk.red(`Unknown agent: ${agent}`));
1741
+ console.log(chalk.gray(`Available: ${ALL_AGENT_IDS.join(', ')}`));
1742
+ return;
1657
1743
  }
1658
- else {
1659
- const { execSync } = await import('child_process');
1660
- const spinner = ora(`Uninstalling ${agentConfig.name}...`).start();
1661
- try {
1662
- execSync(`npm uninstall -g ${pkg}`, { stdio: 'pipe' });
1663
- spinner.succeed(`Uninstalled ${agentConfig.name}`);
1744
+ validAgents.push(agentId);
1745
+ }
1746
+ const { exec } = await import('child_process');
1747
+ const { promisify } = await import('util');
1748
+ const execAsync = promisify(exec);
1749
+ for (const agentId of validAgents) {
1750
+ const agentConfig = AGENTS[agentId];
1751
+ const pkg = agentConfig.npmPackage;
1752
+ if (!options.manifestOnly) {
1753
+ if (!pkg) {
1754
+ console.log(chalk.yellow(`${agentConfig.name} has no npm package.`));
1664
1755
  }
1665
- catch (err) {
1666
- spinner.fail(`Failed to uninstall ${agentConfig.name}`);
1667
- console.error(chalk.gray(err.message));
1756
+ else if (!(await isCliInstalled(agentId))) {
1757
+ console.log(chalk.gray(`${agentConfig.name} is not installed`));
1758
+ }
1759
+ else {
1760
+ const spinner = ora(`Uninstalling ${agentConfig.name}...`).start();
1761
+ try {
1762
+ await execAsync(`npm uninstall -g ${pkg}`);
1763
+ spinner.succeed(`Uninstalled ${agentConfig.name}`);
1764
+ }
1765
+ catch (err) {
1766
+ spinner.fail(`Failed to uninstall ${agentConfig.name}`);
1767
+ console.error(chalk.gray(err.message));
1768
+ }
1668
1769
  }
1669
1770
  }
1670
1771
  }
1671
- // Remove from manifest
1672
1772
  const source = await ensureSource();
1673
1773
  const localPath = getRepoLocalPath(source);
1674
1774
  const manifest = readManifest(localPath);
1675
- if (manifest?.clis?.[agentId]) {
1676
- delete manifest.clis[agentId];
1775
+ let removed = 0;
1776
+ for (const agentId of validAgents) {
1777
+ if (manifest?.clis?.[agentId]) {
1778
+ delete manifest.clis[agentId];
1779
+ removed++;
1780
+ }
1781
+ }
1782
+ if (removed > 0 && manifest) {
1677
1783
  writeManifest(localPath, manifest);
1678
- console.log(chalk.green(`Removed ${agentConfig.name} from manifest`));
1784
+ if (removed === 1) {
1785
+ console.log(chalk.green(`Removed ${AGENTS[validAgents[0]].name} from manifest`));
1786
+ }
1787
+ else {
1788
+ console.log(chalk.green(`Removed ${removed} agents from manifest`));
1789
+ }
1679
1790
  }
1680
1791
  });
1681
1792
  cliCmd
@@ -1695,7 +1806,9 @@ cliCmd
1695
1806
  console.log(chalk.yellow('No CLIs to upgrade. Add CLIs to manifest or use --latest'));
1696
1807
  return;
1697
1808
  }
1698
- const { execSync } = await import('child_process');
1809
+ const { exec } = await import('child_process');
1810
+ const { promisify } = await import('util');
1811
+ const execAsync = promisify(exec);
1699
1812
  for (const agentId of agentsToUpgrade) {
1700
1813
  const agentConfig = AGENTS[agentId];
1701
1814
  if (!agentConfig) {
@@ -1707,7 +1820,7 @@ cliCmd
1707
1820
  const pkg = cliConfig?.package || agentConfig.npmPackage;
1708
1821
  const spinner = ora(`Upgrading ${agentConfig.name} to ${version}...`).start();
1709
1822
  try {
1710
- execSync(`npm install -g ${pkg}@${version}`, { stdio: 'pipe' });
1823
+ await execAsync(`npm install -g ${pkg}@${version}`);
1711
1824
  spinner.succeed(`${agentConfig.name} upgraded to ${version}`);
1712
1825
  }
1713
1826
  catch (err) {
@@ -2122,18 +2235,19 @@ program
2122
2235
  else {
2123
2236
  command = pkg.name || pkg.registry_name;
2124
2237
  }
2238
+ const cliStates = await getAllCliStates();
2125
2239
  const agents = options.agents
2126
2240
  ? options.agents.split(',')
2127
- : MCP_CAPABLE_AGENTS.filter((id) => isCliInstalled(id));
2241
+ : MCP_CAPABLE_AGENTS.filter((id) => cliStates[id]?.installed);
2128
2242
  if (agents.length === 0) {
2129
2243
  console.log(chalk.yellow('\nNo MCP-capable agents installed.'));
2130
2244
  process.exit(1);
2131
2245
  }
2132
2246
  console.log(chalk.bold('\nInstalling to agents...'));
2133
2247
  for (const agentId of agents) {
2134
- if (!isCliInstalled(agentId))
2248
+ if (!cliStates[agentId]?.installed)
2135
2249
  continue;
2136
- const result = registerMcp(agentId, entry.name, command, 'user');
2250
+ const result = await registerMcp(agentId, entry.name, command, 'user');
2137
2251
  if (result.success) {
2138
2252
  console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
2139
2253
  }
@@ -2168,13 +2282,14 @@ program
2168
2282
  const agents = options.agents
2169
2283
  ? options.agents.split(',')
2170
2284
  : ['claude', 'codex', 'gemini'];
2285
+ const gitCliStates = await getAllCliStates();
2171
2286
  // Install commands
2172
2287
  if (hasCommands) {
2173
2288
  console.log(chalk.bold('\nInstalling commands...'));
2174
2289
  let installed = 0;
2175
2290
  for (const command of commands) {
2176
2291
  for (const agentId of agents) {
2177
- if (!isCliInstalled(agentId) && agentId !== 'cursor')
2292
+ if (!gitCliStates[agentId]?.installed && agentId !== 'cursor')
2178
2293
  continue;
2179
2294
  const sourcePath = resolveCommandSource(localPath, command.name, agentId);
2180
2295
  if (sourcePath) {
@@ -2236,7 +2351,9 @@ program
2236
2351
  }
2237
2352
  spinner.text = `Upgrading to ${latestVersion}...`;
2238
2353
  // Detect package manager
2239
- const { execSync } = await import('child_process');
2354
+ const { execSync, exec } = await import('child_process');
2355
+ const { promisify } = await import('util');
2356
+ const execAsync = promisify(exec);
2240
2357
  let cmd;
2241
2358
  // Check if installed globally via npm, bun, or other
2242
2359
  try {
@@ -2263,8 +2380,8 @@ program
2263
2380
  cmd = 'npm install -g @swarmify/agents-cli@latest';
2264
2381
  }
2265
2382
  }
2266
- // Run silently (suppress npm/bun output)
2267
- execSync(cmd, { stdio: 'pipe' });
2383
+ // Run silently (suppress npm/bun output) - use async to allow spinner to animate
2384
+ await execAsync(cmd);
2268
2385
  spinner.succeed(`Upgraded to ${latestVersion}`);
2269
2386
  // Show what's new from changelog
2270
2387
  await showWhatsNew(currentVersion, latestVersion);