@phnx-labs/agents-cli 1.19.2 → 1.20.3

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 (156) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/README.md +72 -12
  3. package/dist/browser.js +0 -0
  4. package/dist/commands/browser.js +88 -16
  5. package/dist/commands/cli.d.ts +14 -0
  6. package/dist/commands/cli.js +244 -0
  7. package/dist/commands/cloud.js +1 -1
  8. package/dist/commands/commands.js +27 -10
  9. package/dist/commands/computer.js +18 -1
  10. package/dist/commands/doctor.d.ts +1 -1
  11. package/dist/commands/doctor.js +2 -2
  12. package/dist/commands/exec.js +38 -18
  13. package/dist/commands/factory.d.ts +3 -14
  14. package/dist/commands/factory.js +3 -3
  15. package/dist/commands/feedback.d.ts +7 -0
  16. package/dist/commands/feedback.js +89 -0
  17. package/dist/commands/helper.d.ts +12 -0
  18. package/dist/commands/helper.js +87 -0
  19. package/dist/commands/hooks.js +89 -10
  20. package/dist/commands/mcp.js +166 -10
  21. package/dist/commands/packages.js +196 -27
  22. package/dist/commands/permissions.js +21 -6
  23. package/dist/commands/plugins.js +11 -4
  24. package/dist/commands/profiles.d.ts +8 -0
  25. package/dist/commands/profiles.js +118 -5
  26. package/dist/commands/prune.js +39 -160
  27. package/dist/commands/pull.js +58 -5
  28. package/dist/commands/routines.js +107 -14
  29. package/dist/commands/rules.js +8 -4
  30. package/dist/commands/secrets-migrate.d.ts +24 -0
  31. package/dist/commands/secrets-migrate.js +198 -0
  32. package/dist/commands/secrets-sync.d.ts +11 -0
  33. package/dist/commands/secrets-sync.js +155 -0
  34. package/dist/commands/secrets.js +79 -46
  35. package/dist/commands/sessions.d.ts +28 -0
  36. package/dist/commands/sessions.js +98 -33
  37. package/dist/commands/setup.d.ts +1 -0
  38. package/dist/commands/setup.js +37 -28
  39. package/dist/commands/skills.js +25 -8
  40. package/dist/commands/subagents.js +69 -49
  41. package/dist/commands/teams.js +61 -10
  42. package/dist/commands/utils.d.ts +33 -0
  43. package/dist/commands/utils.js +139 -0
  44. package/dist/commands/versions.d.ts +4 -3
  45. package/dist/commands/versions.js +134 -130
  46. package/dist/commands/view.d.ts +6 -0
  47. package/dist/commands/view.js +175 -19
  48. package/dist/commands/workflows.js +29 -6
  49. package/dist/computer.js +0 -0
  50. package/dist/index.js +38 -6
  51. package/dist/lib/acp/client.js +6 -1
  52. package/dist/lib/acp/harnesses.js +8 -0
  53. package/dist/lib/agents.d.ts +4 -0
  54. package/dist/lib/agents.js +125 -34
  55. package/dist/lib/auto-pull-worker.js +18 -1
  56. package/dist/lib/browser/cdp.d.ts +8 -1
  57. package/dist/lib/browser/cdp.js +40 -3
  58. package/dist/lib/browser/chrome.d.ts +13 -0
  59. package/dist/lib/browser/chrome.js +46 -3
  60. package/dist/lib/browser/domain-skills.d.ts +51 -0
  61. package/dist/lib/browser/domain-skills.js +157 -0
  62. package/dist/lib/browser/drivers/local.js +45 -4
  63. package/dist/lib/browser/drivers/ssh.js +2 -2
  64. package/dist/lib/browser/ipc.d.ts +8 -1
  65. package/dist/lib/browser/ipc.js +37 -28
  66. package/dist/lib/browser/profiles.d.ts +16 -3
  67. package/dist/lib/browser/profiles.js +44 -4
  68. package/dist/lib/browser/service.d.ts +3 -0
  69. package/dist/lib/browser/service.js +40 -5
  70. package/dist/lib/browser/types.d.ts +11 -4
  71. package/dist/lib/cli-resources.d.ts +137 -0
  72. package/dist/lib/cli-resources.js +477 -0
  73. package/dist/lib/cloud/factory.d.ts +1 -1
  74. package/dist/lib/cloud/factory.js +1 -1
  75. package/dist/lib/cloud/rush.js +5 -5
  76. package/dist/lib/command-skills.js +0 -2
  77. package/dist/lib/computer-rpc.d.ts +3 -0
  78. package/dist/lib/computer-rpc.js +53 -0
  79. package/dist/lib/daemon.js +20 -0
  80. package/dist/lib/events.d.ts +16 -2
  81. package/dist/lib/events.js +33 -2
  82. package/dist/lib/exec.d.ts +42 -13
  83. package/dist/lib/exec.js +127 -33
  84. package/dist/lib/help.js +11 -5
  85. package/dist/lib/hooks/cache.d.ts +38 -0
  86. package/dist/lib/hooks/cache.js +242 -0
  87. package/dist/lib/hooks/profile.d.ts +33 -0
  88. package/dist/lib/hooks/profile.js +129 -0
  89. package/dist/lib/hooks.d.ts +0 -10
  90. package/dist/lib/hooks.js +246 -11
  91. package/dist/lib/mcp.d.ts +15 -0
  92. package/dist/lib/mcp.js +46 -0
  93. package/dist/lib/migrate.js +1 -1
  94. package/dist/lib/overdue.d.ts +26 -0
  95. package/dist/lib/overdue.js +101 -0
  96. package/dist/lib/permissions.d.ts +13 -0
  97. package/dist/lib/permissions.js +55 -1
  98. package/dist/lib/plugin-marketplace.js +1 -1
  99. package/dist/lib/plugins.js +15 -1
  100. package/dist/lib/profiles-presets.d.ts +26 -0
  101. package/dist/lib/profiles-presets.js +216 -0
  102. package/dist/lib/profiles.d.ts +34 -0
  103. package/dist/lib/profiles.js +112 -1
  104. package/dist/lib/resources/mcp.js +37 -0
  105. package/dist/lib/resources.d.ts +1 -1
  106. package/dist/lib/rotate.js +10 -4
  107. package/dist/lib/routines-format.d.ts +47 -0
  108. package/dist/lib/routines-format.js +194 -0
  109. package/dist/lib/routines.d.ts +8 -2
  110. package/dist/lib/routines.js +34 -14
  111. package/dist/lib/runner.js +83 -15
  112. package/dist/lib/scheduler.js +8 -1
  113. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  114. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  115. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
  116. package/dist/lib/secrets/bundles.d.ts +34 -17
  117. package/dist/lib/secrets/bundles.js +210 -36
  118. package/dist/lib/secrets/index.d.ts +49 -30
  119. package/dist/lib/secrets/index.js +126 -115
  120. package/dist/lib/secrets/install-helper.d.ts +45 -0
  121. package/dist/lib/secrets/install-helper.js +165 -0
  122. package/dist/lib/secrets/linux.js +4 -4
  123. package/dist/lib/secrets/sync.d.ts +56 -0
  124. package/dist/lib/secrets/sync.js +180 -0
  125. package/dist/lib/session/active.d.ts +8 -0
  126. package/dist/lib/session/active.js +3 -2
  127. package/dist/lib/session/db.d.ts +0 -4
  128. package/dist/lib/session/db.js +0 -26
  129. package/dist/lib/session/parse.d.ts +1 -0
  130. package/dist/lib/session/parse.js +44 -0
  131. package/dist/lib/session/render.js +4 -4
  132. package/dist/lib/session/types.d.ts +2 -2
  133. package/dist/lib/session/types.js +1 -1
  134. package/dist/lib/shims.d.ts +5 -2
  135. package/dist/lib/shims.js +70 -38
  136. package/dist/lib/state.d.ts +14 -2
  137. package/dist/lib/state.js +51 -20
  138. package/dist/lib/teams/agents.d.ts +5 -4
  139. package/dist/lib/teams/agents.js +48 -22
  140. package/dist/lib/teams/api.d.ts +2 -1
  141. package/dist/lib/teams/api.js +4 -3
  142. package/dist/lib/teams/parsers.d.ts +1 -1
  143. package/dist/lib/teams/parsers.js +153 -3
  144. package/dist/lib/teams/summarizer.js +18 -2
  145. package/dist/lib/teams/worktree.js +14 -3
  146. package/dist/lib/types.d.ts +63 -4
  147. package/dist/lib/types.js +8 -3
  148. package/dist/lib/usage.d.ts +27 -2
  149. package/dist/lib/usage.js +100 -17
  150. package/dist/lib/versions.d.ts +45 -3
  151. package/dist/lib/versions.js +455 -60
  152. package/package.json +15 -14
  153. package/scripts/install-helper.js +97 -0
  154. package/scripts/postinstall.js +16 -0
  155. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  156. package/npm-shrinkwrap.json +0 -3162
@@ -0,0 +1,89 @@
1
+ /**
2
+ * `agents feedback` — frictionless, in-CLI feedback. Opens a Discussion
3
+ * pre-filled with version + OS + agent inventory; falls back to printing the
4
+ * URL when no browser is available.
5
+ */
6
+ import { spawnSync } from 'node:child_process';
7
+ import { arch, platform, release } from 'node:os';
8
+ import { createRequire } from 'node:module';
9
+ import chalk from 'chalk';
10
+ const REPO = 'phnx-labs/agents-cli';
11
+ const DISCUSSION_BASE = `https://github.com/${REPO}/discussions/new`;
12
+ const ISSUE_BASE = `https://github.com/${REPO}/issues/new`;
13
+ function readCliVersion() {
14
+ try {
15
+ const require = createRequire(import.meta.url);
16
+ const pkg = require('../../package.json');
17
+ return pkg.version ?? 'unknown';
18
+ }
19
+ catch {
20
+ return 'unknown';
21
+ }
22
+ }
23
+ function openInBrowser(url) {
24
+ const openers = process.platform === 'darwin'
25
+ ? [['open', [url]]]
26
+ : process.platform === 'win32'
27
+ ? [['cmd', ['/c', 'start', '""', url]]]
28
+ : [
29
+ ['xdg-open', [url]],
30
+ ['gnome-open', [url]]
31
+ ];
32
+ for (const [cmd, args] of openers) {
33
+ const r = spawnSync(cmd, args, { stdio: 'ignore' });
34
+ if (r.status === 0)
35
+ return true;
36
+ }
37
+ return false;
38
+ }
39
+ function buildPrefill(kind, summary) {
40
+ const version = readCliVersion();
41
+ const os = `${platform()} ${release()} (${arch()})`;
42
+ const node = process.version;
43
+ const body = [
44
+ summary.trim() ? `${summary.trim()}\n` : '<!-- describe what you ran into / what you want -->\n',
45
+ '---',
46
+ '',
47
+ '**Environment**',
48
+ '',
49
+ `- agents-cli: \`${version}\``,
50
+ `- OS: \`${os}\``,
51
+ `- Node: \`${node}\``,
52
+ '',
53
+ '<!-- For bugs: include the exact command you ran and the full output. -->',
54
+ '<!-- For ideas: include a concrete use case. -->'
55
+ ].join('\n');
56
+ if (kind === 'bug') {
57
+ const url = `${ISSUE_BASE}?template=bug_report.yml&title=${encodeURIComponent(summary || 'Bug: ')}`;
58
+ return { url, body };
59
+ }
60
+ const category = kind === 'idea' ? 'ideas' : 'q-a';
61
+ const url = `${DISCUSSION_BASE}?category=${category}&title=${encodeURIComponent(summary || '')}&body=${encodeURIComponent(body)}`;
62
+ return { url, body };
63
+ }
64
+ export function registerFeedbackCommand(program) {
65
+ program
66
+ .command('feedback [summary...]')
67
+ .description('Open a pre-filled feedback Discussion or bug report')
68
+ .option('-b, --bug', 'File as a bug report (opens issue tracker)')
69
+ .option('-i, --idea', 'File as a feature idea (Discussions → Ideas)')
70
+ .option('-q, --question', 'Ask a question (Discussions → Q&A)')
71
+ .option('--print', 'Print the URL instead of opening it')
72
+ .action((summary, opts) => {
73
+ const kind = opts.bug ? 'bug' : opts.idea ? 'idea' : 'question';
74
+ const summaryText = (summary ?? []).join(' ').trim();
75
+ const { url } = buildPrefill(kind, summaryText);
76
+ if (opts.print) {
77
+ console.log(url);
78
+ return;
79
+ }
80
+ const opened = openInBrowser(url);
81
+ if (opened) {
82
+ console.log(chalk.dim(`Opened ${kind} form in your browser:\n ${url}`));
83
+ }
84
+ else {
85
+ console.log(chalk.yellow('Could not auto-open a browser. Paste this URL:'));
86
+ console.log(` ${url}`);
87
+ }
88
+ });
89
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `agents helper` -- install, inspect, and reinstall the signed macOS
3
+ * Keychain helper at the stable user path.
4
+ *
5
+ * The signed `Agents CLI.app` ships inside the npm package, but its keychain
6
+ * ACLs need a stable signature-pinned location to survive `npm i -g` and
7
+ * version bumps. This command copies it to
8
+ * `~/Library/Application Support/agents-cli/` once and lets users force a
9
+ * reinstall when the trusted-app ACL needs to be re-established.
10
+ */
11
+ import type { Command } from 'commander';
12
+ export declare function registerHelperCommand(program: Command): void;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * `agents helper` -- install, inspect, and reinstall the signed macOS
3
+ * Keychain helper at the stable user path.
4
+ *
5
+ * The signed `Agents CLI.app` ships inside the npm package, but its keychain
6
+ * ACLs need a stable signature-pinned location to survive `npm i -g` and
7
+ * version bumps. This command copies it to
8
+ * `~/Library/Application Support/agents-cli/` once and lets users force a
9
+ * reinstall when the trusted-app ACL needs to be re-established.
10
+ */
11
+ import chalk from 'chalk';
12
+ import { ensureKeychainHelperInstalled, getKeychainHelperPath, getKeychainHelperStatus, } from '../lib/secrets/install-helper.js';
13
+ function requireDarwin() {
14
+ if (process.platform !== 'darwin') {
15
+ console.error(chalk.red('agents helper: macOS only.'));
16
+ process.exit(1);
17
+ }
18
+ }
19
+ export function registerHelperCommand(program) {
20
+ const cmd = program
21
+ .command('helper')
22
+ .description('Manage the signed macOS Keychain helper (.app) install');
23
+ cmd
24
+ .command('install')
25
+ .description('Copy the bundled .app to ~/Library/Application Support/agents-cli/')
26
+ .action(() => {
27
+ requireDarwin();
28
+ try {
29
+ ensureKeychainHelperInstalled({ forceReinstall: true });
30
+ }
31
+ catch (err) {
32
+ console.error(chalk.red(err.message || String(err)));
33
+ process.exit(1);
34
+ }
35
+ const s = getKeychainHelperStatus();
36
+ console.log(chalk.green('Installed:'), s.destination);
37
+ console.log(chalk.dim('codesign:'), s.codesignOk ? chalk.green('ok') : chalk.red(s.codesignOutput));
38
+ console.log(chalk.dim('spctl: '), s.spctlOk ? chalk.green('ok') : chalk.yellow(s.spctlOutput));
39
+ });
40
+ cmd
41
+ .command('update')
42
+ .description('Reinstall the .app, overwriting any existing copy (alias of install)')
43
+ .action(() => {
44
+ requireDarwin();
45
+ try {
46
+ ensureKeychainHelperInstalled({ forceReinstall: true });
47
+ }
48
+ catch (err) {
49
+ console.error(chalk.red(err.message || String(err)));
50
+ process.exit(1);
51
+ }
52
+ const s = getKeychainHelperStatus();
53
+ console.log(chalk.green('Updated: '), s.destination);
54
+ console.log(chalk.dim('codesign:'), s.codesignOk ? chalk.green('ok') : chalk.red(s.codesignOutput));
55
+ console.log(chalk.dim('spctl: '), s.spctlOk ? chalk.green('ok') : chalk.yellow(s.spctlOutput));
56
+ });
57
+ cmd
58
+ .command('status')
59
+ .description('Show source, destination, codesign and notarization status')
60
+ .action(() => {
61
+ requireDarwin();
62
+ const s = getKeychainHelperStatus();
63
+ console.log(chalk.bold('Source: '), s.source ?? chalk.red('(not found)'));
64
+ console.log(chalk.bold('Destination:'), s.destination);
65
+ console.log(chalk.bold('Installed: '), s.installed ? chalk.green('yes') : chalk.yellow('no'));
66
+ if (s.installed) {
67
+ console.log(chalk.bold('codesign: '), s.codesignOk ? chalk.green('ok') : chalk.red(s.codesignOutput));
68
+ console.log(chalk.bold('spctl: '), s.spctlOk ? chalk.green('ok') : chalk.yellow(s.spctlOutput));
69
+ }
70
+ else {
71
+ console.log(chalk.dim('Run `agents helper install` to copy the bundled .app to the destination.'));
72
+ }
73
+ });
74
+ cmd
75
+ .command('where')
76
+ .description('Print the absolute path to the installed helper executable')
77
+ .action(() => {
78
+ requireDarwin();
79
+ try {
80
+ console.log(getKeychainHelperPath());
81
+ }
82
+ catch (err) {
83
+ console.error(chalk.red(err.message || String(err)));
84
+ process.exit(1);
85
+ }
86
+ });
87
+ }
@@ -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.`));
@@ -508,17 +525,17 @@ Examples:
508
525
  .action(() => {
509
526
  console.error(chalk.red('"agents hooks sync" is gone.'));
510
527
  console.error(chalk.gray('Sync runs automatically when you launch the agent.'));
511
- console.error(chalk.gray('To remove orphans, use: agents prune hooks'));
528
+ console.error(chalk.gray('To remove orphans, use: agents prune cleanup hooks'));
512
529
  process.exit(1);
513
530
  });
514
- // `hooks prune` moved to the top-level `agents prune` command.
531
+ // `hooks prune` moved to the top-level `agents prune cleanup` command.
515
532
  hooksCmd
516
533
  .command('prune', { hidden: true })
517
534
  .allowUnknownOption()
518
535
  .allowExcessArguments()
519
536
  .action(() => {
520
537
  console.error(chalk.red('"agents hooks prune" moved.'));
521
- console.error(chalk.gray('Use: agents prune hooks (or `agents prune` for everything)'));
538
+ console.error(chalk.gray('Use: agents prune cleanup hooks (or `agents prune cleanup` for everything)'));
522
539
  process.exit(1);
523
540
  });
524
541
  hooksCmd
@@ -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
  }
@@ -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 = [];