@swarmify/agents-cli 1.5.7 → 1.5.8

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) {
@@ -1609,10 +1689,12 @@ cliCmd
1609
1689
  console.log(chalk.yellow(`${agentConfig.name} has no npm package. Install manually.`));
1610
1690
  }
1611
1691
  else {
1612
- const { execSync } = await import('child_process');
1692
+ const { exec } = await import('child_process');
1693
+ const { promisify } = await import('util');
1694
+ const execAsync = promisify(exec);
1613
1695
  const spinner = ora(`Installing ${agentConfig.name}@${version}...`).start();
1614
1696
  try {
1615
- execSync(`npm install -g ${pkg}@${version}`, { stdio: 'pipe' });
1697
+ await execAsync(`npm install -g ${pkg}@${version}`);
1616
1698
  spinner.succeed(`Installed ${agentConfig.name}@${version}`);
1617
1699
  }
1618
1700
  catch (err) {
@@ -1652,14 +1734,16 @@ cliCmd
1652
1734
  if (!pkg) {
1653
1735
  console.log(chalk.yellow(`${agentConfig.name} has no npm package.`));
1654
1736
  }
1655
- else if (!isCliInstalled(agentId)) {
1737
+ else if (!(await isCliInstalled(agentId))) {
1656
1738
  console.log(chalk.gray(`${agentConfig.name} is not installed`));
1657
1739
  }
1658
1740
  else {
1659
- const { execSync } = await import('child_process');
1741
+ const { exec } = await import('child_process');
1742
+ const { promisify } = await import('util');
1743
+ const execAsync = promisify(exec);
1660
1744
  const spinner = ora(`Uninstalling ${agentConfig.name}...`).start();
1661
1745
  try {
1662
- execSync(`npm uninstall -g ${pkg}`, { stdio: 'pipe' });
1746
+ await execAsync(`npm uninstall -g ${pkg}`);
1663
1747
  spinner.succeed(`Uninstalled ${agentConfig.name}`);
1664
1748
  }
1665
1749
  catch (err) {
@@ -1695,7 +1779,9 @@ cliCmd
1695
1779
  console.log(chalk.yellow('No CLIs to upgrade. Add CLIs to manifest or use --latest'));
1696
1780
  return;
1697
1781
  }
1698
- const { execSync } = await import('child_process');
1782
+ const { exec } = await import('child_process');
1783
+ const { promisify } = await import('util');
1784
+ const execAsync = promisify(exec);
1699
1785
  for (const agentId of agentsToUpgrade) {
1700
1786
  const agentConfig = AGENTS[agentId];
1701
1787
  if (!agentConfig) {
@@ -1707,7 +1793,7 @@ cliCmd
1707
1793
  const pkg = cliConfig?.package || agentConfig.npmPackage;
1708
1794
  const spinner = ora(`Upgrading ${agentConfig.name} to ${version}...`).start();
1709
1795
  try {
1710
- execSync(`npm install -g ${pkg}@${version}`, { stdio: 'pipe' });
1796
+ await execAsync(`npm install -g ${pkg}@${version}`);
1711
1797
  spinner.succeed(`${agentConfig.name} upgraded to ${version}`);
1712
1798
  }
1713
1799
  catch (err) {
@@ -2122,18 +2208,19 @@ program
2122
2208
  else {
2123
2209
  command = pkg.name || pkg.registry_name;
2124
2210
  }
2211
+ const cliStates = await getAllCliStates();
2125
2212
  const agents = options.agents
2126
2213
  ? options.agents.split(',')
2127
- : MCP_CAPABLE_AGENTS.filter((id) => isCliInstalled(id));
2214
+ : MCP_CAPABLE_AGENTS.filter((id) => cliStates[id]?.installed);
2128
2215
  if (agents.length === 0) {
2129
2216
  console.log(chalk.yellow('\nNo MCP-capable agents installed.'));
2130
2217
  process.exit(1);
2131
2218
  }
2132
2219
  console.log(chalk.bold('\nInstalling to agents...'));
2133
2220
  for (const agentId of agents) {
2134
- if (!isCliInstalled(agentId))
2221
+ if (!cliStates[agentId]?.installed)
2135
2222
  continue;
2136
- const result = registerMcp(agentId, entry.name, command, 'user');
2223
+ const result = await registerMcp(agentId, entry.name, command, 'user');
2137
2224
  if (result.success) {
2138
2225
  console.log(` ${chalk.green('+')} ${AGENTS[agentId].name}`);
2139
2226
  }
@@ -2168,13 +2255,14 @@ program
2168
2255
  const agents = options.agents
2169
2256
  ? options.agents.split(',')
2170
2257
  : ['claude', 'codex', 'gemini'];
2258
+ const gitCliStates = await getAllCliStates();
2171
2259
  // Install commands
2172
2260
  if (hasCommands) {
2173
2261
  console.log(chalk.bold('\nInstalling commands...'));
2174
2262
  let installed = 0;
2175
2263
  for (const command of commands) {
2176
2264
  for (const agentId of agents) {
2177
- if (!isCliInstalled(agentId) && agentId !== 'cursor')
2265
+ if (!gitCliStates[agentId]?.installed && agentId !== 'cursor')
2178
2266
  continue;
2179
2267
  const sourcePath = resolveCommandSource(localPath, command.name, agentId);
2180
2268
  if (sourcePath) {
@@ -2236,7 +2324,9 @@ program
2236
2324
  }
2237
2325
  spinner.text = `Upgrading to ${latestVersion}...`;
2238
2326
  // Detect package manager
2239
- const { execSync } = await import('child_process');
2327
+ const { execSync, exec } = await import('child_process');
2328
+ const { promisify } = await import('util');
2329
+ const execAsync = promisify(exec);
2240
2330
  let cmd;
2241
2331
  // Check if installed globally via npm, bun, or other
2242
2332
  try {
@@ -2263,8 +2353,8 @@ program
2263
2353
  cmd = 'npm install -g @swarmify/agents-cli@latest';
2264
2354
  }
2265
2355
  }
2266
- // Run silently (suppress npm/bun output)
2267
- execSync(cmd, { stdio: 'pipe' });
2356
+ // Run silently (suppress npm/bun output) - use async to allow spinner to animate
2357
+ await execAsync(cmd);
2268
2358
  spinner.succeed(`Upgraded to ${latestVersion}`);
2269
2359
  // Show what's new from changelog
2270
2360
  await showWhatsNew(currentVersion, latestVersion);