@phnx-labs/agents-cli 1.20.0 → 1.20.4

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.
Files changed (111) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +4 -4
  3. package/dist/commands/cli.js +3 -3
  4. package/dist/commands/cloud.js +1 -1
  5. package/dist/commands/commands.js +24 -7
  6. package/dist/commands/exec.js +36 -16
  7. package/dist/commands/feedback.d.ts +7 -0
  8. package/dist/commands/feedback.js +89 -0
  9. package/dist/commands/helper.d.ts +12 -0
  10. package/dist/commands/helper.js +87 -0
  11. package/dist/commands/hooks.js +86 -7
  12. package/dist/commands/import.js +90 -37
  13. package/dist/commands/mcp.js +166 -10
  14. package/dist/commands/packages.js +196 -27
  15. package/dist/commands/permissions.js +21 -6
  16. package/dist/commands/profiles.d.ts +8 -0
  17. package/dist/commands/profiles.js +117 -4
  18. package/dist/commands/pull.js +4 -4
  19. package/dist/commands/routines.js +6 -6
  20. package/dist/commands/rules.js +8 -4
  21. package/dist/commands/secrets-migrate.d.ts +24 -0
  22. package/dist/commands/secrets-migrate.js +198 -0
  23. package/dist/commands/secrets-sync.d.ts +11 -0
  24. package/dist/commands/secrets-sync.js +155 -0
  25. package/dist/commands/secrets.js +74 -39
  26. package/dist/commands/skills.js +22 -5
  27. package/dist/commands/subagents.js +69 -49
  28. package/dist/commands/teams.js +48 -10
  29. package/dist/commands/utils.d.ts +33 -0
  30. package/dist/commands/utils.js +139 -0
  31. package/dist/commands/versions.js +4 -4
  32. package/dist/commands/view.d.ts +6 -0
  33. package/dist/commands/view.js +169 -8
  34. package/dist/commands/workflows.js +29 -6
  35. package/dist/index.js +4 -0
  36. package/dist/lib/acp/client.js +6 -1
  37. package/dist/lib/agents.d.ts +4 -0
  38. package/dist/lib/agents.js +41 -17
  39. package/dist/lib/auto-pull-worker.js +18 -1
  40. package/dist/lib/browser/chrome.js +4 -0
  41. package/dist/lib/browser/drivers/ssh.js +1 -1
  42. package/dist/lib/browser/profiles.d.ts +3 -3
  43. package/dist/lib/browser/profiles.js +3 -3
  44. package/dist/lib/browser/service.js +19 -0
  45. package/dist/lib/browser/types.d.ts +4 -4
  46. package/dist/lib/cli-resources.d.ts +36 -8
  47. package/dist/lib/cli-resources.js +268 -46
  48. package/dist/lib/cloud/factory.d.ts +1 -1
  49. package/dist/lib/cloud/factory.js +1 -1
  50. package/dist/lib/events.d.ts +16 -2
  51. package/dist/lib/events.js +33 -2
  52. package/dist/lib/exec.d.ts +39 -11
  53. package/dist/lib/exec.js +90 -31
  54. package/dist/lib/help.js +11 -5
  55. package/dist/lib/hooks/cache.d.ts +38 -0
  56. package/dist/lib/hooks/cache.js +242 -0
  57. package/dist/lib/hooks/profile.d.ts +33 -0
  58. package/dist/lib/hooks/profile.js +129 -0
  59. package/dist/lib/hooks.d.ts +0 -10
  60. package/dist/lib/hooks.js +68 -15
  61. package/dist/lib/import.d.ts +21 -0
  62. package/dist/lib/import.js +55 -2
  63. package/dist/lib/mcp.d.ts +15 -0
  64. package/dist/lib/mcp.js +40 -0
  65. package/dist/lib/permissions.d.ts +13 -0
  66. package/dist/lib/permissions.js +51 -1
  67. package/dist/lib/plugin-marketplace.d.ts +10 -0
  68. package/dist/lib/plugin-marketplace.js +47 -1
  69. package/dist/lib/plugins.js +15 -1
  70. package/dist/lib/profiles-presets.d.ts +26 -0
  71. package/dist/lib/profiles-presets.js +187 -8
  72. package/dist/lib/profiles.d.ts +34 -0
  73. package/dist/lib/profiles.js +112 -1
  74. package/dist/lib/pty-server.js +27 -3
  75. package/dist/lib/routines-format.d.ts +17 -5
  76. package/dist/lib/routines-format.js +37 -16
  77. package/dist/lib/routines.d.ts +1 -1
  78. package/dist/lib/routines.js +2 -2
  79. package/dist/lib/runner.js +64 -10
  80. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  81. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  82. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
  83. package/dist/lib/secrets/bundles.d.ts +18 -22
  84. package/dist/lib/secrets/bundles.js +75 -99
  85. package/dist/lib/secrets/index.d.ts +51 -27
  86. package/dist/lib/secrets/index.js +147 -156
  87. package/dist/lib/secrets/install-helper.d.ts +45 -0
  88. package/dist/lib/secrets/install-helper.js +165 -0
  89. package/dist/lib/secrets/linux.js +4 -4
  90. package/dist/lib/secrets/sync.d.ts +56 -0
  91. package/dist/lib/secrets/sync.js +180 -0
  92. package/dist/lib/session/render.js +4 -4
  93. package/dist/lib/session/types.d.ts +1 -1
  94. package/dist/lib/shims.d.ts +4 -1
  95. package/dist/lib/shims.js +5 -35
  96. package/dist/lib/state.d.ts +14 -1
  97. package/dist/lib/state.js +49 -5
  98. package/dist/lib/teams/agents.d.ts +5 -4
  99. package/dist/lib/teams/agents.js +47 -21
  100. package/dist/lib/teams/api.d.ts +2 -1
  101. package/dist/lib/teams/api.js +4 -3
  102. package/dist/lib/types.d.ts +57 -1
  103. package/dist/lib/types.js +2 -0
  104. package/dist/lib/usage.d.ts +27 -2
  105. package/dist/lib/usage.js +100 -17
  106. package/dist/lib/versions.d.ts +35 -1
  107. package/dist/lib/versions.js +288 -64
  108. package/package.json +13 -12
  109. package/scripts/install-helper.js +97 -0
  110. package/scripts/postinstall.js +16 -0
  111. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
@@ -8,9 +8,9 @@ import { AGENTS, HOOKS_CAPABLE_AGENTS, resolveAgentName, formatAgentError, agent
8
8
  import { supports } from '../lib/capabilities.js';
9
9
  import { cloneRepo } from '../lib/git.js';
10
10
  import { discoverHooksFromRepo, installHooksCentrally, listCentralHooks, listInstalledHooksWithScope, getHookInfo, parseHookManifest, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks.js';
11
- import { listInstalledVersions, getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, resolveAgentVersionTargets, } from '../lib/versions.js';
11
+ import { listInstalledVersions, getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, resolveInstalledAgentTargets, } from '../lib/versions.js';
12
12
  import { recordVersionResources } from '../lib/state.js';
13
- import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, promptRemovalTargets, } from './utils.js';
13
+ import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, promptRemovalTargets, resolveAgentTargetsAutoInstalling, } from './utils.js';
14
14
  /** Register the `agents hooks` command tree (list, add, remove, sync, prune, view). */
15
15
  export function registerHooksCommands(program) {
16
16
  const hooksCmd = program.command('hooks')
@@ -340,13 +340,17 @@ Examples:
340
340
  let versionSelections;
341
341
  const hooksCapableAgents = Array.from(HOOKS_CAPABLE_AGENTS);
342
342
  if (options.agents) {
343
- const result = resolveAgentVersionTargets(options.agents, hooksCapableAgents);
343
+ const result = await resolveAgentTargetsAutoInstalling(options.agents, hooksCapableAgents, { yes: options.yes });
344
+ if (!result) {
345
+ console.log(chalk.gray('Cancelled.'));
346
+ return;
347
+ }
344
348
  selectedAgents = result.selectedAgents;
345
349
  versionSelections = result.versionSelections;
346
350
  }
347
351
  else {
348
352
  const result = await promptAgentVersionSelection(hooksCapableAgents, {
349
- skipPrompts: options.yes || !isInteractiveTerminal(),
353
+ skipPrompts: options.yes,
350
354
  });
351
355
  selectedAgents = result.selectedAgents;
352
356
  versionSelections = result.versionSelections;
@@ -459,11 +463,24 @@ Examples:
459
463
  console.log(chalk.yellow(` Hook '${hookName}' not found in any version.`));
460
464
  continue;
461
465
  }
462
- // Filter by --agents if specified
466
+ // Filter by --agents if specified. Routes through resolveInstalledAgentTargets
467
+ // so the same selector syntax used everywhere else (agent, agent@default,
468
+ // agent@x.y.z, agent@all, literal all) works here too.
463
469
  let availableTargets = hookInfo.targets;
464
470
  if (options?.agents) {
465
- const requestedAgents = new Set(options.agents.split(','));
466
- availableTargets = availableTargets.filter((t) => requestedAgents.has(t.agent));
471
+ const requestedTargets = resolveInstalledAgentTargets(options.agents, [...HOOKS_CAPABLE_AGENTS]);
472
+ const requested = new Set();
473
+ for (const aid of requestedTargets.directAgents) {
474
+ for (const ver of listInstalledVersions(aid)) {
475
+ requested.add(`${aid}@${ver}`);
476
+ }
477
+ }
478
+ for (const [aid, versions] of requestedTargets.versionSelections) {
479
+ for (const ver of versions) {
480
+ requested.add(`${aid}@${ver}`);
481
+ }
482
+ }
483
+ availableTargets = availableTargets.filter((t) => requested.has(`${t.agent}@${t.version}`));
467
484
  }
468
485
  if (availableTargets.length === 0) {
469
486
  console.log(chalk.yellow(` Hook '${hookName}' not found in specified agents.`));
@@ -579,4 +596,66 @@ Examples:
579
596
  printWithPager(output, contentLines.length);
580
597
  }
581
598
  });
599
+ hooksCmd
600
+ .command('profile')
601
+ .description('Per-hook timing + cache stats from recent invocations')
602
+ .option('--days <n>', 'Number of days of logs to read', '7')
603
+ .option('--warn-ms <n>', 'p99 threshold above which a hook is flagged as slow', '2000')
604
+ .option('--json', 'Emit raw JSON rows instead of the table')
605
+ .addHelpText('after', `
606
+ Shows aggregated stats for every hook that emitted a hook.fire event into
607
+ ~/.agents/.cache/logs/events-YYYY-MM-DD.jsonl. Only hooks with \`cache:\` in
608
+ their manifest are instrumented today — the generated shim writes the events.
609
+
610
+ Examples:
611
+ agents hooks profile # last 7 days, table form
612
+ agents hooks profile --days 30 # roll up the full month
613
+ agents hooks profile --json | jq # pipe somewhere
614
+
615
+ A hook whose p99 exceeds --warn-ms gets flagged in the cache column. Add
616
+ 'cache: 5m' or 'cache: 5m-bg' to its hooks.yaml entry to fix it.
617
+ `)
618
+ .action(async (options) => {
619
+ const { aggregateHookProfile, loadHookFireEvents, formatMs, formatCacheColumn, DEFAULT_SLOW_HOOK_WARN_MS } = await import('../lib/hooks/profile.js');
620
+ const days = Math.max(1, parseInt(options.days, 10) || 7);
621
+ const warnMs = Math.max(0, parseInt(options.warnMs, 10) || DEFAULT_SLOW_HOOK_WARN_MS);
622
+ const rows = aggregateHookProfile(loadHookFireEvents(days));
623
+ if (options.json) {
624
+ console.log(JSON.stringify(rows, null, 2));
625
+ return;
626
+ }
627
+ if (rows.length === 0) {
628
+ console.log(chalk.gray(`No hook.fire events in the last ${days} day${days === 1 ? '' : 's'}.`));
629
+ console.log(chalk.gray('Add \'cache: 5m\' to a hook in hooks.yaml to start collecting stats.'));
630
+ return;
631
+ }
632
+ const widths = { hook: 36, n: 5, p50: 7, p99: 7, mean: 7, max: 7, cache: 30 };
633
+ const pad = (s, w) => (s.length >= w ? s.slice(0, w) : s + ' '.repeat(w - s.length));
634
+ const header = [
635
+ pad('HOOK', widths.hook),
636
+ pad('N', widths.n),
637
+ pad('P50', widths.p50),
638
+ pad('P99', widths.p99),
639
+ pad('MEAN', widths.mean),
640
+ pad('MAX', widths.max),
641
+ pad('CACHE', widths.cache),
642
+ ].join(' ');
643
+ console.log(chalk.bold(header));
644
+ console.log(chalk.gray('─'.repeat(header.length)));
645
+ for (const r of rows) {
646
+ const slow = r.p99Ms > warnMs;
647
+ const cacheCol = formatCacheColumn(r);
648
+ const warning = slow && r.cacheHitPct + r.cacheStalePct === 0 ? ' ← add cache: 5m' : '';
649
+ const line = [
650
+ pad(r.hook, widths.hook),
651
+ pad(String(r.n), widths.n),
652
+ pad(formatMs(r.p50Ms), widths.p50),
653
+ pad(formatMs(r.p99Ms), widths.p99),
654
+ pad(formatMs(r.meanMs), widths.mean),
655
+ pad(formatMs(r.maxMs), widths.max),
656
+ pad(cacheCol, widths.cache),
657
+ ].join(' ') + warning;
658
+ console.log(slow ? chalk.yellow(line) : line);
659
+ }
660
+ });
582
661
  }
@@ -23,12 +23,13 @@
23
23
  import chalk from 'chalk';
24
24
  import ora from 'ora';
25
25
  import * as fs from 'fs';
26
+ import * as os from 'os';
26
27
  import * as path from 'path';
27
28
  import { confirm } from '@inquirer/prompts';
28
29
  import { ALL_AGENT_IDS } from '../lib/agents.js';
29
30
  import { AGENTS, getCliPath, getCliVersion, agentLabel } from '../lib/agents.js';
30
31
  import { getVersionDir } from '../lib/versions.js';
31
- import { finalizeImport, importAgentBinary, importAgentConfig, isValidImportVersion, resolvePackageDirFromBinary, } from '../lib/import.js';
32
+ import { finalizeImport, importAgentBinary, importAgentConfig, importInstallScriptBinary, isValidImportVersion, resolvePackageDirFromBinary, } from '../lib/import.js';
32
33
  import { isPromptCancelled, isInteractiveTerminal } from './utils.js';
33
34
  function isValidAgentId(value) {
34
35
  return ALL_AGENT_IDS.includes(value);
@@ -41,54 +42,94 @@ async function runImport(agentArg, opts) {
41
42
  }
42
43
  const agentId = agentArg;
43
44
  const agent = AGENTS[agentId];
44
- // Reject agents that don't ship via npm before we spin up PATH lookups and
45
- // prompts. cursor/kiro/goose/roo all have npmPackage='' and use custom
46
- // install scripts the symlink-farm import doesn't apply to them.
47
- if (!agent.npmPackage) {
48
- console.error(chalk.red(`${agentLabel(agentId)} doesn't install via npm — \`agents import\` only handles npm-style packages.`));
49
- console.error(chalk.gray(`Use \`agents add ${agentId}\` to install via its native script.`));
50
- process.exit(1);
51
- }
45
+ // installScript-based agents (Grok, Antigravity, Cursor, Kiro, Goose, Roo)
46
+ // don't have an npm package; their binary lives wherever the curl/brew
47
+ // installer dropped it. We adopt by symlinking that PATH binary directly
48
+ // into the version's `node_modules/.bin/`. No package.json walk.
49
+ const isInstallScriptAgent = !agent.npmPackage;
52
50
  let globalPath = null;
51
+ let installScriptBinary = null;
53
52
  if (opts.fromPath) {
54
53
  globalPath = path.resolve(opts.fromPath);
55
54
  if (!fs.existsSync(globalPath)) {
56
55
  console.error(chalk.red(`Path does not exist: ${globalPath}`));
57
56
  process.exit(1);
58
57
  }
58
+ if (isInstallScriptAgent) {
59
+ // With --from-path on an installScript agent, the path is the binary
60
+ // itself (or a directory containing it). Accept either.
61
+ if (fs.statSync(globalPath).isDirectory()) {
62
+ const candidate = path.join(globalPath, agent.cliCommand);
63
+ if (!fs.existsSync(candidate)) {
64
+ console.error(chalk.red(`No "${agent.cliCommand}" in ${globalPath}`));
65
+ process.exit(1);
66
+ }
67
+ installScriptBinary = candidate;
68
+ }
69
+ else {
70
+ installScriptBinary = globalPath;
71
+ }
72
+ }
59
73
  }
60
74
  else {
61
75
  const binary = await getCliPath(agentId);
62
76
  if (!binary) {
63
- // Use || (not ??) so empty-string npmPackage falls back to cliCommand.
64
- // Defensive: agents with empty npmPackage are rejected above, but keep
65
- // the operator correct in case that early check is ever relaxed.
66
- const installName = agent.npmPackage || agent.cliCommand;
77
+ const installHint = isInstallScriptAgent
78
+ ? `Run \`agents add ${agentId}\` to install via the official script, or pass --from-path.`
79
+ : `Install it first (e.g. \`npm i -g ${agent.npmPackage || agent.cliCommand}\`) or pass --from-path.`;
67
80
  console.error(chalk.red(`No "${agent.cliCommand}" found on PATH.`));
68
- console.error(chalk.gray(`Install it first (e.g. \`npm i -g ${installName}\`) or pass --from-path.`));
81
+ console.error(chalk.gray(installHint));
69
82
  process.exit(1);
70
83
  }
71
- globalPath = resolvePackageDirFromBinary(binary);
72
- if (!globalPath) {
73
- console.error(chalk.red(`Could not resolve npm package for binary: ${binary}`));
74
- console.error(chalk.gray('Pass --from-path <dir> with the package directory explicitly.'));
75
- process.exit(1);
84
+ if (isInstallScriptAgent) {
85
+ installScriptBinary = binary;
86
+ }
87
+ else {
88
+ globalPath = resolvePackageDirFromBinary(binary);
89
+ if (!globalPath) {
90
+ console.error(chalk.red(`Could not resolve npm package for binary: ${binary}`));
91
+ console.error(chalk.gray('Pass --from-path <dir> with the package directory explicitly.'));
92
+ process.exit(1);
93
+ }
94
+ }
95
+ }
96
+ // For Grok, the binary on PATH is typically `~/.grok/bin/grok` (a moving
97
+ // pointer to the latest install). Prefer the exact versioned file in
98
+ // `~/.grok/downloads/` so the v<x.y.z> alias is pinned to that file and
99
+ // doesn't drift when the user upgrades externally.
100
+ if (isInstallScriptAgent && agentId === 'grok' && !opts.fromPath) {
101
+ const detected = await getCliVersion(agentId);
102
+ if (detected) {
103
+ const downloads = path.join(os.homedir(), '.grok', 'downloads');
104
+ try {
105
+ const entries = fs.readdirSync(downloads);
106
+ const exact = entries.find((e) => e.startsWith('grok-') && e.includes(detected));
107
+ if (exact) {
108
+ installScriptBinary = path.join(downloads, exact);
109
+ }
110
+ }
111
+ catch {
112
+ /* fall back to PATH binary already set above */
113
+ }
76
114
  }
77
115
  }
78
116
  let version = opts.version;
79
117
  if (!version) {
80
- try {
81
- const pkg = JSON.parse(fs.readFileSync(path.join(globalPath, 'package.json'), 'utf8'));
82
- version = typeof pkg.version === 'string' ? pkg.version : undefined;
83
- }
84
- catch {
85
- /* fall through */
118
+ if (!isInstallScriptAgent && globalPath) {
119
+ try {
120
+ const pkg = JSON.parse(fs.readFileSync(path.join(globalPath, 'package.json'), 'utf8'));
121
+ version = typeof pkg.version === 'string' ? pkg.version : undefined;
122
+ }
123
+ catch {
124
+ /* fall through */
125
+ }
86
126
  }
87
127
  // Only fall back to running the PATH binary's --version when we're
88
- // auto-detecting. With --from-path, the PATH binary may belong to a
89
- // different install entirely; reporting its version here would silently
90
- // mis-attribute the imported version.
91
- if (!version && !opts.fromPath) {
128
+ // auto-detecting. With --from-path on an npm agent, the PATH binary may
129
+ // belong to a different install entirely; reporting its version here
130
+ // would silently mis-attribute the imported version. installScript agents
131
+ // always use `<bin> --version` since they have no package.json to read.
132
+ if (!version && (isInstallScriptAgent || !opts.fromPath)) {
92
133
  const detected = await getCliVersion(agentId);
93
134
  version = detected ?? undefined;
94
135
  }
@@ -104,8 +145,9 @@ async function runImport(agentArg, opts) {
104
145
  process.exit(1);
105
146
  }
106
147
  const versionDir = getVersionDir(agentId, version);
148
+ const fromLabel = isInstallScriptAgent ? installScriptBinary : globalPath;
107
149
  console.log(chalk.bold(`\nImport ${agentLabel(agentId)} v${version}`));
108
- console.log(` from: ${chalk.gray(globalPath)}`);
150
+ console.log(` from: ${chalk.gray(fromLabel)}`);
109
151
  console.log(` into: ${chalk.gray(versionDir)}`);
110
152
  const configDirExists = fs.existsSync(agent.configDir);
111
153
  let configAlreadyManaged = false;
@@ -146,7 +188,8 @@ async function runImport(agentArg, opts) {
146
188
  const cfgSpinner = ora(`Importing config dir for ${agentLabel(agentId)} v${version}...`).start();
147
189
  const cfgResult = await importAgentConfig(agentId, version);
148
190
  if (cfgResult.success) {
149
- cfgSpinner.succeed(`Config imported (${agent.configDir} -> ${versionDir}/home/.${agentId})`);
191
+ const relConfig = path.relative(os.homedir(), agent.configDir);
192
+ cfgSpinner.succeed(`Config imported (${agent.configDir} -> ${versionDir}/home/${relConfig})`);
150
193
  }
151
194
  else if (cfgResult.skipped) {
152
195
  cfgSpinner.warn(`Config: ${cfgResult.error}`);
@@ -157,9 +200,11 @@ async function runImport(agentArg, opts) {
157
200
  }
158
201
  }
159
202
  const binSpinner = ora(`Registering ${agentLabel(agentId)} v${version} binary...`).start();
160
- const binResult = importAgentBinary({ agentId, npmPackage: agent.npmPackage, cliCommand: agent.cliCommand }, version, globalPath, versionDir);
203
+ const binResult = isInstallScriptAgent
204
+ ? importInstallScriptBinary({ agentId, npmPackage: agent.npmPackage, cliCommand: agent.cliCommand }, version, installScriptBinary, versionDir)
205
+ : importAgentBinary({ agentId, npmPackage: agent.npmPackage, cliCommand: agent.cliCommand }, version, globalPath, versionDir);
161
206
  if (binResult.success) {
162
- binSpinner.succeed(`Binary registered (${agent.cliCommand} -> ${globalPath})`);
207
+ binSpinner.succeed(`Binary registered (${agent.cliCommand} -> ${binResult.resolvedFromPath})`);
163
208
  }
164
209
  else if (binResult.skipped) {
165
210
  binSpinner.warn(`Binary: ${binResult.error}`);
@@ -198,11 +243,19 @@ Examples:
198
243
  $ agents import openclaw --version 2026.3.8 Pin a version label
199
244
  $ agents import openclaw --from-path /opt/homebrew/lib/node_modules/openclaw
200
245
 
246
+ # installScript-based agents (curl/brew installers, no npm package):
247
+ $ agents import grok Adopt ~/.grok/downloads/grok-<ver>
248
+ $ agents import antigravity Adopt ~/.local/bin/agy
249
+ $ agents import cursor Adopt ~/.local/bin/cursor-agent
250
+ $ agents import antigravity --from-path ~/.local/bin/agy
251
+
201
252
  When to use:
202
- When an agent CLI is already installed globally via npm or homebrew and you
203
- want to bring it under agents-cli management without reinstalling. Creates a
204
- symlink farm pointing at the existing install — nothing is copied or moved
205
- (except the agent's config dir, which is moved into the version's home).
253
+ When an agent CLI is already installed globally and you want to bring it
254
+ under agents-cli management without reinstalling. Creates a symlink farm
255
+ pointing at the existing install — nothing is copied or moved (except the
256
+ agent's config dir, which is moved into the version's home). Works for both
257
+ npm-style packages (claude, codex, gemini, opencode, openclaw) and
258
+ installScript-based agents (grok, antigravity, cursor, kiro, goose, roo).
206
259
  `)
207
260
  .action(runImport);
208
261
  }
@@ -3,20 +3,49 @@ import ora from 'ora';
3
3
  import { checkbox } from '@inquirer/prompts';
4
4
  import { AGENTS, MCP_CAPABLE_AGENTS, getAllCliStates, resolveAgentName, formatAgentError, registerMcpToTargets, unregisterMcpFromTargets, listInstalledMcpsWithScope, parseMcpConfig, getMcpConfigPathForHome, agentLabel, } from '../lib/agents.js';
5
5
  import { readManifest, writeManifest, createDefaultManifest } from '../lib/manifest.js';
6
- import { listMcpServerConfigs } from '../lib/mcp.js';
6
+ import { listMcpServerConfigs, discoverMcpConfigsFromRepo, installMcpConfigCentrally, } from '../lib/mcp.js';
7
+ import { cloneRepo } from '../lib/git.js';
7
8
  import { getMcpDir } from '../lib/state.js';
8
- import { getEffectiveHome, getGlobalDefault, listInstalledVersions, getVersionHomePath, resolveInstalledAgentTargets, resolveConfiguredAgentTargets, resolveVersionAlias, } from '../lib/versions.js';
9
+ import { getEffectiveHome, getGlobalDefault, listInstalledVersions, getVersionHomePath, resolveInstalledAgentTargets, resolveConfiguredAgentTargets, resolveVersionAlias, syncResourcesToVersion, } from '../lib/versions.js';
9
10
  import { getUserAgentsDir } from '../lib/state.js';
10
- import { isPromptCancelled, isInteractiveTerminal, requireInteractiveSelection, promptRemovalTargets } from './utils.js';
11
+ import { isPromptCancelled, isInteractiveTerminal, requireInteractiveSelection, promptRemovalTargets, parseCommaSeparatedList, ensureAgentVersionsInstalled, resolveAgentTargetsAutoInstalling, resolveInstalledAgentTargetsAutoInstalling, VersionNotInstalledError, } from './utils.js';
11
12
  import { showResourceList, buildTargetsSection, } from './resource-view.js';
12
- /** Parse a comma-separated --agents string into validated agent IDs and optional version targets. */
13
+ /**
14
+ * Parse a comma-separated --agents string into validated agent IDs and
15
+ * optional version targets in the manifest shape.
16
+ *
17
+ * Supports the same selector syntax as resolveAgentVersionTargets:
18
+ * - bare `agent` → manifest agents:[agent] (no version pin)
19
+ * - `agent@default` → manifest agents:[agent] (no version pin)
20
+ * - `agent@x.y.z` → manifest agentVersions[agent] = ['x.y.z']
21
+ * - `agent@all` → manifest agentVersions[agent] = every installed version
22
+ * - literal `all` → expand to all MCP-capable agents (each as `@all`)
23
+ *
24
+ * Throws VersionNotInstalledError for unknown specific versions so callers
25
+ * can prompt-and-install before retrying.
26
+ */
13
27
  function parseMcpAgentTargets(value) {
14
28
  const agents = [];
15
29
  const agentVersions = {};
16
- const targets = value
30
+ const rawTargets = value
17
31
  .split(',')
18
32
  .map((item) => item.trim())
19
33
  .filter(Boolean);
34
+ // Expand literal `all` / `all@all` into per-agent @all. Skip agents with no
35
+ // installed versions so `all` is lenient — mirrors resolveAgentVersionTargets.
36
+ const targets = [];
37
+ for (const t of rawTargets) {
38
+ if (t === 'all' || t === 'all@all') {
39
+ for (const a of MCP_CAPABLE_AGENTS) {
40
+ if (listInstalledVersions(a).length > 0) {
41
+ targets.push(`${a}@all`);
42
+ }
43
+ }
44
+ }
45
+ else {
46
+ targets.push(t);
47
+ }
48
+ }
20
49
  for (const target of targets) {
21
50
  const atIndex = target.indexOf('@');
22
51
  const agentToken = (atIndex === -1 ? target : target.slice(0, atIndex)).trim();
@@ -25,7 +54,7 @@ function parseMcpAgentTargets(value) {
25
54
  continue;
26
55
  }
27
56
  if (atIndex !== -1 && !versionToken) {
28
- throw new Error(`Missing version in --agents entry '${target}'. Use agent@x.y.z or agent@default.`);
57
+ throw new Error(`Missing version in --agents entry '${target}'. Use agent@x.y.z, agent@default, or agent@all.`);
29
58
  }
30
59
  const agentId = resolveAgentName(agentToken);
31
60
  if (!agentId || !MCP_CAPABLE_AGENTS.includes(agentId)) {
@@ -50,11 +79,23 @@ function parseMcpAgentTargets(value) {
50
79
  if (installedVersions.length === 0) {
51
80
  throw new Error(`No managed versions are installed for ${AGENTS[agentId].name}. Run: agents add ${agentId}@latest`);
52
81
  }
82
+ if (versionToken === 'all') {
83
+ const versions = agentVersions[agentId] || [];
84
+ for (const ver of installedVersions) {
85
+ if (!versions.includes(ver))
86
+ versions.push(ver);
87
+ }
88
+ agentVersions[agentId] = versions;
89
+ if (!agents.includes(agentId)) {
90
+ agents.push(agentId);
91
+ }
92
+ continue;
93
+ }
53
94
  const resolvedVersion = versionToken === 'latest'
54
95
  ? installedVersions[installedVersions.length - 1]
55
96
  : versionToken;
56
97
  if (!installedVersions.includes(resolvedVersion)) {
57
- throw new Error(`Version ${resolvedVersion} is not installed for ${AGENTS[agentId].name}. Installed versions: ${installedVersions.join(', ')}`);
98
+ throw new VersionNotInstalledError(agentId, resolvedVersion, installedVersions);
58
99
  }
59
100
  const versions = agentVersions[agentId] || [];
60
101
  if (!versions.includes(resolvedVersion)) {
@@ -140,6 +181,8 @@ When to use:
140
181
  .option('-a, --agents <list>', 'Targets: claude, codex@0.116.0', MCP_CAPABLE_AGENTS.join(','))
141
182
  .option('-s, --scope <scope>', 'user (global) or project (repo-specific)', 'user')
142
183
  .option('-t, --transport <type>', 'stdio (default) or http', 'stdio')
184
+ .option('--names <list>', 'When source is a repo: MCP server names to install (comma-separated)')
185
+ .option('-y, --yes', 'Auto-install any missing agent versions without prompting')
143
186
  .option('-H, --header <header>', 'HTTP header as name:value (repeatable)', (val, acc) => {
144
187
  acc.push(val);
145
188
  return acc;
@@ -154,8 +197,22 @@ Examples:
154
197
 
155
198
  # Add to manifest only (register later)
156
199
  agents mcp add db-server -- uvx postgres-mcp
200
+
201
+ # Install all MCP server configs from a repo's mcp/*.yaml
202
+ agents mcp add gh:user/repo --agents claude@all
203
+
204
+ # Install specific servers by name
205
+ agents mcp add gh:phnx-labs/.agents-system --names notion,figma --agents claude
157
206
  `)
158
207
  .action(async (name, commandOrUrl, options) => {
208
+ // Repo-source form: `agents mcp add gh:user/repo [--names a,b] [--agents …]`
209
+ // Mirrors `agents skills add gh:…`. Discovers <repoPath>/mcp/*.yaml,
210
+ // copies to ~/.agents/mcp/, and syncs to selected agent versions.
211
+ const isRepoSource = /^(gh:|git:|ssh:|https?:\/\/)/.test(name);
212
+ if (isRepoSource && commandOrUrl.length === 0) {
213
+ await installMcpsFromRepoSource(name, options);
214
+ return;
215
+ }
159
216
  // Registry resolution: if the user just typed `agents mcp add <name>`,
160
217
  // try looking up `<name>` in any configured MCP registry (by default the
161
218
  // official MCP Registry at registry.modelcontextprotocol.io) and derive
@@ -195,6 +252,13 @@ Examples:
195
252
  const localPath = getUserAgentsDir();
196
253
  const manifest = readManifest(localPath) || createDefaultManifest();
197
254
  manifest.mcp = manifest.mcp || {};
255
+ // Pre-flight: prompt-and-install any requested agent@version that isn't
256
+ // installed yet, before parseMcpAgentTargets validates the selector.
257
+ const okInstall = await ensureAgentVersionsInstalled(options.agents, MCP_CAPABLE_AGENTS, { yes: options.yes });
258
+ if (!okInstall) {
259
+ console.log(chalk.gray('Cancelled.'));
260
+ return;
261
+ }
198
262
  const targetConfig = parseMcpAgentTargets(options.agents);
199
263
  if (transport === 'http') {
200
264
  const url = commandOrUrl[0];
@@ -459,6 +523,7 @@ Examples:
459
523
  .command('register [name]')
460
524
  .description('Apply MCP servers from manifest to agent config files')
461
525
  .option('-a, --agents <list>', 'Override manifest targets: claude, codex@0.116.0')
526
+ .option('-y, --yes', 'Auto-install any missing agent versions without prompting')
462
527
  .addHelpText('after', `
463
528
  Examples:
464
529
  # Register all servers from manifest
@@ -495,9 +560,18 @@ Examples:
495
560
  continue;
496
561
  }
497
562
  console.log(`\n ${chalk.cyan(mcpName)}:`);
498
- const targets = options.agents
499
- ? resolveInstalledAgentTargets(options.agents, MCP_CAPABLE_AGENTS)
500
- : resolveConfiguredAgentTargets(config.agents, config.agentVersions, MCP_CAPABLE_AGENTS);
563
+ let targets;
564
+ if (options.agents) {
565
+ const resolved = await resolveInstalledAgentTargetsAutoInstalling(options.agents, MCP_CAPABLE_AGENTS, { yes: options.yes });
566
+ if (!resolved) {
567
+ console.log(chalk.gray(' Cancelled.'));
568
+ continue;
569
+ }
570
+ targets = resolved;
571
+ }
572
+ else {
573
+ targets = resolveConfiguredAgentTargets(config.agents, config.agentVersions, MCP_CAPABLE_AGENTS);
574
+ }
501
575
  const results = await registerMcpToTargets(targets, mcpName, commandOrUrl, config.scope || 'user', transport, { headers: config.headers });
502
576
  for (const result of results) {
503
577
  if (result.success) {
@@ -513,6 +587,88 @@ Examples:
513
587
  }
514
588
  });
515
589
  }
590
+ async function installMcpsFromRepoSource(source, options) {
591
+ const spinner = ora('Cloning repository...').start();
592
+ let localPath;
593
+ try {
594
+ const cloneResult = await cloneRepo(source);
595
+ localPath = cloneResult.localPath;
596
+ }
597
+ catch (err) {
598
+ spinner.fail(`Failed to clone: ${err.message}`);
599
+ process.exit(1);
600
+ }
601
+ spinner.succeed('Repository cloned');
602
+ let discovered = discoverMcpConfigsFromRepo(localPath);
603
+ if (discovered.length === 0) {
604
+ console.log(chalk.yellow('No MCP server configs found (looking for mcp/*.yaml)'));
605
+ return;
606
+ }
607
+ const requestedNames = parseCommaSeparatedList(options.names);
608
+ if (requestedNames.length > 0) {
609
+ const discoveredNames = new Set(discovered.map((s) => s.name));
610
+ const missing = requestedNames.filter((n) => !discoveredNames.has(n));
611
+ if (missing.length > 0) {
612
+ console.log(chalk.red(`\nMCP server(s) not found in source: ${missing.join(', ')}`));
613
+ console.log(chalk.gray(`Available: ${[...discoveredNames].join(', ')}`));
614
+ process.exit(1);
615
+ }
616
+ discovered = discovered.filter((s) => requestedNames.includes(s.name));
617
+ }
618
+ console.log(chalk.bold(`\nFound ${discovered.length} MCP server config(s):`));
619
+ for (const s of discovered) {
620
+ const summary = s.config.transport === 'stdio'
621
+ ? `${s.config.command}${s.config.args?.length ? ' ' + s.config.args.join(' ') : ''}`
622
+ : s.config.url ?? '';
623
+ console.log(` ${chalk.cyan(s.name)}: ${chalk.gray(summary)}`);
624
+ }
625
+ const installSpinner = ora('Installing MCP configs to ~/.agents/mcp/...').start();
626
+ let installed = 0;
627
+ for (const s of discovered) {
628
+ const result = installMcpConfigCentrally(s.path);
629
+ if (result.success) {
630
+ installed++;
631
+ }
632
+ else {
633
+ installSpinner.stop();
634
+ console.log(chalk.red(` Failed to install ${s.name}: ${result.error}`));
635
+ installSpinner.start();
636
+ }
637
+ }
638
+ installSpinner.succeed(`Installed ${installed} MCP config(s) to ~/.agents/mcp/`);
639
+ // Agent/version selection — same default as the non-repo form: every
640
+ // MCP-capable agent. Routes through resolveAgentTargetsAutoInstalling so
641
+ // a typo'd `claude@2.1.999` prompts to install (and --yes auto-installs).
642
+ const agentsValue = options.agents ?? MCP_CAPABLE_AGENTS.join(',');
643
+ let targets;
644
+ try {
645
+ const resolved = await resolveAgentTargetsAutoInstalling(agentsValue, MCP_CAPABLE_AGENTS, { yes: options.yes });
646
+ if (!resolved) {
647
+ console.log(chalk.gray('\nCancelled.'));
648
+ return;
649
+ }
650
+ targets = resolved;
651
+ }
652
+ catch (err) {
653
+ console.log(chalk.red(err.message));
654
+ process.exit(1);
655
+ }
656
+ if (targets.versionSelections.size === 0) {
657
+ console.log(chalk.gray('\nStored centrally; no agent versions selected for sync.'));
658
+ return;
659
+ }
660
+ const syncSpinner = ora('Syncing to agent versions...').start();
661
+ const mcpNames = discovered.map((s) => s.name);
662
+ let synced = 0;
663
+ for (const [agentId, versions] of targets.versionSelections) {
664
+ for (const version of versions) {
665
+ const result = syncResourcesToVersion(agentId, version, { mcp: mcpNames });
666
+ if (result.mcp.length > 0)
667
+ synced++;
668
+ }
669
+ }
670
+ syncSpinner.succeed(`Synced MCP configs to ${synced} agent version(s).`);
671
+ }
516
672
  /** Enumerate (agent, version) pairs that support MCP and have a version home. */
517
673
  function iterMcpCapableVersions(filter) {
518
674
  const out = [];