@phnx-labs/agents-cli 1.20.12 → 1.20.14

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 (67) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +3 -0
  3. package/dist/commands/computer-actions.d.ts +3 -0
  4. package/dist/commands/computer-actions.js +16 -0
  5. package/dist/commands/doctor.js +51 -7
  6. package/dist/commands/exec.js +25 -4
  7. package/dist/commands/import.js +17 -6
  8. package/dist/commands/inspect.d.ts +28 -1
  9. package/dist/commands/inspect.js +330 -47
  10. package/dist/commands/mcp.js +3 -3
  11. package/dist/commands/plugins.d.ts +2 -0
  12. package/dist/commands/plugins.js +69 -26
  13. package/dist/commands/prune.js +8 -5
  14. package/dist/commands/sync.js +1 -1
  15. package/dist/commands/teams.js +1 -0
  16. package/dist/commands/trash.d.ts +11 -0
  17. package/dist/commands/trash.js +57 -41
  18. package/dist/commands/versions.js +68 -20
  19. package/dist/commands/view.d.ts +1 -0
  20. package/dist/commands/view.js +56 -12
  21. package/dist/commands/wallet.d.ts +14 -0
  22. package/dist/commands/wallet.js +199 -0
  23. package/dist/index.js +4 -1
  24. package/dist/lib/agents.js +70 -22
  25. package/dist/lib/browser/ipc.d.ts +7 -0
  26. package/dist/lib/browser/ipc.js +43 -27
  27. package/dist/lib/capabilities.js +7 -1
  28. package/dist/lib/command-skills.d.ts +1 -0
  29. package/dist/lib/command-skills.js +23 -7
  30. package/dist/lib/exec.d.ts +32 -1
  31. package/dist/lib/exec.js +79 -7
  32. package/dist/lib/hooks.d.ts +21 -1
  33. package/dist/lib/hooks.js +69 -7
  34. package/dist/lib/mcp.js +33 -0
  35. package/dist/lib/models.js +5 -0
  36. package/dist/lib/picker.d.ts +2 -0
  37. package/dist/lib/picker.js +96 -6
  38. package/dist/lib/platform/index.d.ts +1 -0
  39. package/dist/lib/platform/index.js +1 -0
  40. package/dist/lib/platform/winpath.d.ts +35 -0
  41. package/dist/lib/platform/winpath.js +86 -0
  42. package/dist/lib/plugins.d.ts +24 -0
  43. package/dist/lib/plugins.js +37 -2
  44. package/dist/lib/project-launch.js +110 -5
  45. package/dist/lib/registry.js +15 -2
  46. package/dist/lib/rotate.d.ts +7 -0
  47. package/dist/lib/rotate.js +17 -7
  48. package/dist/lib/runner.js +14 -0
  49. package/dist/lib/sandbox.js +5 -2
  50. package/dist/lib/settings-manifest.d.ts +39 -0
  51. package/dist/lib/settings-manifest.js +163 -0
  52. package/dist/lib/shims.d.ts +1 -1
  53. package/dist/lib/shims.js +16 -31
  54. package/dist/lib/staleness/detectors/subagents.js +16 -0
  55. package/dist/lib/staleness/writers/subagents.js +11 -3
  56. package/dist/lib/subagents.d.ts +9 -0
  57. package/dist/lib/subagents.js +33 -0
  58. package/dist/lib/teams/agents.js +1 -1
  59. package/dist/lib/teams/parsers.d.ts +1 -1
  60. package/dist/lib/teams/parsers.js +6 -0
  61. package/dist/lib/types.d.ts +1 -1
  62. package/dist/lib/versions.d.ts +15 -3
  63. package/dist/lib/versions.js +88 -19
  64. package/dist/lib/wallet/index.d.ts +78 -0
  65. package/dist/lib/wallet/index.js +253 -0
  66. package/package.json +3 -3
  67. package/scripts/postinstall.js +35 -7
@@ -23,7 +23,10 @@ import { readMeta, getUserAgentsDir, getSystemAgentsDir, getProjectAgentsDir, ge
23
23
  import { getVersionHomePath } from '../lib/versions.js';
24
24
  import { getShimsDir, getVersionedAliasPath } from '../lib/shims.js';
25
25
  import { getAgentResources, listResources, } from '../lib/resources.js';
26
- import { discoverPlugins, discoverPluginsInDir } from '../lib/plugins.js';
26
+ import { listHookEntriesFromDir } from '../lib/hooks.js';
27
+ import { listMcpServerConfigs, discoverMcpConfigsFromRepo } from '../lib/mcp.js';
28
+ import { discoverPlugins, discoverPluginsInDir, pluginResourceGroups } from '../lib/plugins.js';
29
+ import { PLUGIN_GROUP_COLORS } from './plugins.js';
27
30
  import { countSessionsInScope } from '../lib/session/discover.js';
28
31
  import { damerauLevenshtein } from '../lib/fuzzy.js';
29
32
  /** Resource kinds the inspect command can drill into. */
@@ -37,6 +40,29 @@ const DRILLABLE_KINDS = [
37
40
  'workflows',
38
41
  'subagents',
39
42
  ];
43
+ /**
44
+ * Summary-view partition. SIMPLE kinds render as a one-line count + name preview;
45
+ * RICH kinds (hooks/plugins/mcp) get their own expanded section showing each
46
+ * item's key detail (events/predicates, bundle contents, transport/url). Together
47
+ * they cover every DrillableKind.
48
+ */
49
+ const SIMPLE_KINDS = ['commands', 'skills', 'rules', 'subagents', 'workflows'];
50
+ const RICH_KINDS = ['hooks', 'plugins', 'mcp'];
51
+ /**
52
+ * Singular aliases for the plural drill-down flags. `--plugin code` reads as
53
+ * "show the one plugin named code" — a required-value flag that always lands in
54
+ * detail mode, the natural counterpart to `--plugins` (list). `mcp` has no
55
+ * distinct singular, so it is intentionally absent.
56
+ */
57
+ const SINGULAR_DRILL_ALIASES = {
58
+ command: 'commands',
59
+ skill: 'skills',
60
+ hook: 'hooks',
61
+ rule: 'rules',
62
+ plugin: 'plugins',
63
+ workflow: 'workflows',
64
+ subagent: 'subagents',
65
+ };
40
66
  const CAPABILITY_NAMES = [
41
67
  'hooks', 'mcp', 'skills', 'commands', 'subagents', 'plugins', 'workflows', 'rules', 'allowlist',
42
68
  ];
@@ -50,6 +76,9 @@ export function registerInspectCommand(program) {
50
76
  for (const kind of DRILLABLE_KINDS) {
51
77
  cmd.option(`--${kind} [query]`, `list ${kind}; pass a name (fuzzy) to show detail`);
52
78
  }
79
+ for (const singular of Object.keys(SINGULAR_DRILL_ALIASES)) {
80
+ cmd.option(`--${singular} <query>`, `show detail for one ${singular} by name (fuzzy)`);
81
+ }
53
82
  cmd.action(async (target, options) => {
54
83
  await inspectAction(target, options);
55
84
  });
@@ -121,15 +150,21 @@ function pickDrillKind(options) {
121
150
  for (const kind of DRILLABLE_KINDS) {
122
151
  const value = options[kind];
123
152
  if (value !== undefined)
124
- active.push({ kind, query: value });
153
+ active.push({ flag: `--${kind}`, kind, query: value });
154
+ }
155
+ // Singular aliases (`--plugin code`) always carry a name → detail mode.
156
+ for (const [singular, plural] of Object.entries(SINGULAR_DRILL_ALIASES)) {
157
+ const value = options[singular];
158
+ if (typeof value === 'string')
159
+ active.push({ flag: `--${singular}`, kind: plural, query: value });
125
160
  }
126
161
  if (active.length === 0)
127
162
  return null;
128
163
  if (active.length > 1) {
129
- console.error(chalk.red(`Pick at most one drill-down flag. Got: ${active.map(a => '--' + a.kind).join(', ')}`));
164
+ console.error(chalk.red(`Pick at most one drill-down flag. Got: ${active.map(a => a.flag).join(', ')}`));
130
165
  process.exit(1);
131
166
  }
132
- return active[0];
167
+ return { kind: active[0].kind, query: active[0].query };
133
168
  }
134
169
  /** Files at a DotAgents root that mark it as one, beyond the per-kind dirs. */
135
170
  const REPO_MARKER_FILES = ['agents.yaml', 'hooks.yaml'];
@@ -330,6 +365,9 @@ function renderRepoSummary(repo, options) {
330
365
  const manifest = repoManifestSummary(repo.root);
331
366
  const kindData = {};
332
367
  let totalBytes = 0, totalFiles = 0;
368
+ let repoHookByScript = new Map();
369
+ let repoHookItemList = [];
370
+ let repoMcpConfigs = new Map();
333
371
  if (!options.brief) {
334
372
  for (const kind of DRILLABLE_KINDS) {
335
373
  const items = collectRepoKind(repo, kind);
@@ -338,6 +376,9 @@ function renderRepoSummary(repo, options) {
338
376
  totalBytes += size.bytes;
339
377
  totalFiles += size.files;
340
378
  }
379
+ repoHookByScript = hookManifestByScript(hookManifestFromFile(path.join(repo.root, 'agents.yaml')));
380
+ repoHookItemList = repoHookItems(repo);
381
+ repoMcpConfigs = new Map(discoverMcpConfigsFromRepo(repo.root).map(s => [s.name, s.config]));
341
382
  }
342
383
  if (options.json) {
343
384
  console.log(JSON.stringify({
@@ -347,12 +388,34 @@ function renderRepoSummary(repo, options) {
347
388
  manifests,
348
389
  manifest,
349
390
  size: options.brief ? null : { bytes: totalBytes, files: totalFiles },
350
- resources: options.brief ? null : Object.fromEntries(DRILLABLE_KINDS.map(kind => [kind, {
351
- count: kindData[kind].items.length,
352
- bytes: kindData[kind].size.bytes,
353
- files: kindData[kind].size.files,
354
- names: kindData[kind].items.map(i => i.name),
355
- }])),
391
+ resources: options.brief ? null : Object.fromEntries(DRILLABLE_KINDS.map(kind => {
392
+ const size = kindData[kind].size;
393
+ // Hooks use the grouped reader (clean names) instead of the raw readdir.
394
+ const items = kind === 'hooks' ? repoHookItemList : kindData[kind].items;
395
+ const base = {
396
+ count: items.length,
397
+ bytes: size.bytes,
398
+ files: size.files,
399
+ names: items.map(i => i.name),
400
+ };
401
+ if (kind === 'hooks')
402
+ return [kind, { ...base, items: items.map(i => {
403
+ const h = repoHookByScript.get(i.name);
404
+ return { name: i.name, events: h?.events ?? [], matcher: h?.matcher, matches: h?.matches, cache: h?.cache };
405
+ }) }];
406
+ if (kind === 'mcp')
407
+ return [kind, { ...base, items: items.map(i => {
408
+ const c = repoMcpConfigs.get(i.name);
409
+ return { name: i.name, transport: c?.transport, url: c?.url, command: c?.command, args: c?.args };
410
+ }) }];
411
+ if (kind === 'plugins')
412
+ return [kind, { ...base, items: items.map(i => ({
413
+ name: i.name,
414
+ version: i.extra?.find(([k]) => k === 'version')?.[1],
415
+ groups: Object.fromEntries((i.groups ?? []).map(g => [g.label, g.items.length])),
416
+ })) }];
417
+ return [kind, base];
418
+ })),
356
419
  }, null, 2));
357
420
  return;
358
421
  }
@@ -391,13 +454,16 @@ function renderRepoSummary(repo, options) {
391
454
  if (!options.brief) {
392
455
  console.log(` ${'size'.padEnd(10)} ${formatBytes(totalBytes)} ${chalk.gray('·')} ${totalFiles} files`);
393
456
  console.log('\n' + chalk.bold('Resources'));
394
- for (const kind of DRILLABLE_KINDS) {
457
+ for (const kind of SIMPLE_KINDS) {
395
458
  const { items, size } = kindData[kind];
396
459
  const count = String(items.length).padStart(4);
397
460
  const sz = items.length > 0 ? formatBytes(size.bytes).padStart(8) : ''.padEnd(8);
398
461
  const preview = items.length > 0 ? chalk.gray(truncate(previewNames(items, 4), 60)) : '';
399
462
  console.log(` ${kind.padEnd(10)} ${count} ${sz} ${preview}`.trimEnd());
400
463
  }
464
+ printExpandedSection('Hooks', hookRows(repoHookItemList, repoHookByScript));
465
+ printExpandedSection('Plugins', pluginRows(kindData.plugins.items));
466
+ printExpandedSection('MCP', mcpRows(kindData.mcp.items, repoMcpConfigs));
401
467
  }
402
468
  console.log('');
403
469
  console.log(chalk.gray(`Drill in: agents inspect ${repo.label} --skills <query>`));
@@ -455,7 +521,9 @@ async function renderSummary(agent, version, versionHome, options) {
455
521
  const shimPath = path.join(getShimsDir(), AGENTS[agent].cliCommand);
456
522
  const aliasPath = getVersionedAliasPath(agent, version);
457
523
  const capabilities = collectCapabilities(agent, version);
458
- const counts = options.brief ? null : collectCounts(agent, versionHome);
524
+ const itemsByKind = options.brief ? null : collectItemsByKind(agent, versionHome);
525
+ const hookByScript = options.brief ? null : hookManifestByScript(loadCentralHookManifest());
526
+ const mcpConfigs = options.brief ? null : new Map(listMcpServerConfigs().map(s => [s.name, s.config]));
459
527
  const sessions = options.brief ? null : {
460
528
  total: safeCountSessions(agent),
461
529
  };
@@ -471,7 +539,7 @@ async function renderSummary(agent, version, versionHome, options) {
471
539
  strategy,
472
540
  installedShim: cliState?.installed === true ? cliState.path : null,
473
541
  capabilities,
474
- resources: counts,
542
+ resources: itemsByKind ? summaryResourcesJson(itemsByKind, hookByScript, mcpConfigs) : null,
475
543
  sessions,
476
544
  };
477
545
  console.log(JSON.stringify(json, null, 2));
@@ -496,15 +564,14 @@ async function renderSummary(agent, version, versionHome, options) {
496
564
  const reason = res.ok ? '' : chalk.gray(`(${res.reason}${res.need ? ' ' + res.need : ''})`);
497
565
  console.log(` ${cap.padEnd(10)} ${mark} ${reason}`);
498
566
  }
499
- if (counts) {
567
+ if (itemsByKind) {
500
568
  console.log('\n' + chalk.bold('Resources'));
501
- for (const kind of DRILLABLE_KINDS) {
502
- const c = counts[kind];
503
- if (!c)
504
- continue;
505
- const breakdown = formatScopeBreakdown(c.bySource);
506
- console.log(` ${kind.padEnd(10)} ${String(c.total).padStart(4)} ${chalk.gray(breakdown)}`);
569
+ for (const kind of SIMPLE_KINDS) {
570
+ printSimpleResourceRow(kind, itemsByKind[kind]);
507
571
  }
572
+ printExpandedSection('Hooks', hookRows(itemsByKind.hooks, hookByScript));
573
+ printExpandedSection('Plugins', pluginRows(itemsByKind.plugins));
574
+ printExpandedSection('MCP', mcpRows(itemsByKind.mcp, mcpConfigs));
508
575
  }
509
576
  if (sessions) {
510
577
  console.log('\n' + chalk.bold('Sessions'));
@@ -526,7 +593,7 @@ function renderItemList(header, jsonHead, kind, items, options) {
526
593
  ...jsonHead,
527
594
  kind,
528
595
  count: items.length,
529
- items: items.map(i => ({ name: i.name, source: i.source, path: i.path, description: i.description })),
596
+ items: items.map(i => ({ name: i.name, source: i.source, path: i.path, description: i.description, ...(i.groups ? { groups: i.groups } : {}) })),
530
597
  }, null, 2));
531
598
  return;
532
599
  }
@@ -542,9 +609,23 @@ function renderItemList(header, jsonHead, kind, items, options) {
542
609
  if (item.description) {
543
610
  console.log(` ${chalk.gray(truncate(item.description, 90))}`);
544
611
  }
612
+ if (item.groups)
613
+ printGroupRows(item.groups);
545
614
  }
546
615
  console.log('');
547
616
  }
617
+ /** Print a plugin's resource breakdown as aligned `label items` rows under a list entry. */
618
+ function printGroupRows(groups) {
619
+ if (groups.length === 0)
620
+ return;
621
+ const width = Math.max(...groups.map(g => g.label.length));
622
+ for (const g of groups) {
623
+ const colorFn = PLUGIN_GROUP_COLORS[g.label] ?? chalk.white;
624
+ const label = chalk.gray(g.label.padEnd(width));
625
+ const value = g.items.map((s) => colorFn(s)).join(chalk.gray(', '));
626
+ console.log(` ${label} ${value}`);
627
+ }
628
+ }
548
629
  // ─── Detail mode (fuzzy) ─────────────────────────────────────────────────────
549
630
  async function renderDetail(agent, version, versionHome, kind, query, options) {
550
631
  const items = collectKind(agent, versionHome, kind);
@@ -610,14 +691,52 @@ function collectCapabilities(agent, version) {
610
691
  }
611
692
  return out;
612
693
  }
613
- function collectCounts(agent, versionHome) {
694
+ function collectItemsByKind(agent, versionHome) {
695
+ const out = {};
696
+ for (const kind of DRILLABLE_KINDS)
697
+ out[kind] = collectKind(agent, versionHome, kind);
698
+ return out;
699
+ }
700
+ /** A simple-kind count row: `kind N user:30 system:12 name, name …(+K)`. */
701
+ function printSimpleResourceRow(kind, items) {
702
+ const count = String(items.length).padStart(4);
703
+ const breakdown = chalk.gray(scopeBreakdownPlain(countBySource(items.map(i => i.source))).padEnd(18));
704
+ const preview = items.length > 0 ? chalk.gray(truncate(previewNames(items, 3), 48)) : '';
705
+ console.log(` ${kind.padEnd(10)} ${count} ${breakdown} ${preview}`.trimEnd());
706
+ }
707
+ /**
708
+ * Build the `resources` JSON: every kind keeps `total` + `bySource` (back-compat),
709
+ * simple kinds add `names`, and the rich kinds add structured `items` (hook
710
+ * events/predicates, mcp transport/url/command, plugin version + group counts).
711
+ */
712
+ function summaryResourcesJson(itemsByKind, hookByScript, mcpConfigs) {
614
713
  const out = {};
615
714
  for (const kind of DRILLABLE_KINDS) {
616
- const items = collectKind(agent, versionHome, kind);
617
- const bySource = {};
618
- for (const item of items)
619
- bySource[item.source] = (bySource[item.source] || 0) + 1;
620
- out[kind] = { total: items.length, bySource };
715
+ const items = itemsByKind[kind];
716
+ const base = { total: items.length, bySource: countBySource(items.map(i => i.source)) };
717
+ if (kind === 'hooks') {
718
+ out[kind] = { ...base, items: items.map(i => {
719
+ const h = hookByScript.get(i.name);
720
+ return { name: i.name, source: i.source, events: h?.events ?? [], matcher: h?.matcher, matches: h?.matches, cache: h?.cache };
721
+ }) };
722
+ }
723
+ else if (kind === 'mcp') {
724
+ out[kind] = { ...base, items: items.map(i => {
725
+ const c = mcpConfigs.get(i.name);
726
+ return { name: i.name, source: i.source, transport: c?.transport, url: c?.url, command: c?.command, args: c?.args };
727
+ }) };
728
+ }
729
+ else if (kind === 'plugins') {
730
+ out[kind] = { ...base, items: items.map(i => ({
731
+ name: i.name,
732
+ source: i.source,
733
+ version: i.extra?.find(([k]) => k === 'version')?.[1],
734
+ groups: Object.fromEntries((i.groups ?? []).map(g => [g.label, g.items.length])),
735
+ })) };
736
+ }
737
+ else {
738
+ out[kind] = { ...base, names: items.map(i => i.name) };
739
+ }
621
740
  }
622
741
  return out;
623
742
  }
@@ -653,17 +772,6 @@ function pluginItems() {
653
772
  */
654
773
  function pluginToItem(plugin, source) {
655
774
  const extra = [];
656
- const list = (names) => names.length <= 8 ? names.join(', ') : `${names.slice(0, 8).join(', ')}, +${names.length - 8} more`;
657
- if (plugin.skills.length)
658
- extra.push(['skills', `${plugin.skills.length} (${list(plugin.skills)})`]);
659
- if (plugin.commands.length)
660
- extra.push(['commands', `${plugin.commands.length} (${list(plugin.commands)})`]);
661
- if (plugin.agentDefs.length)
662
- extra.push(['subagents', `${plugin.agentDefs.length} (${list(plugin.agentDefs)})`]);
663
- if (plugin.hooks.length)
664
- extra.push(['hooks', String(plugin.hooks.length)]);
665
- if (plugin.mcpServers.length)
666
- extra.push(['mcp', list(plugin.mcpServers)]);
667
775
  if (plugin.manifest.version)
668
776
  extra.push(['version', plugin.manifest.version]);
669
777
  return {
@@ -673,6 +781,7 @@ function pluginToItem(plugin, source) {
673
781
  linkTarget: linkTarget(plugin.root),
674
782
  description: plugin.manifest.description ?? '',
675
783
  extra,
784
+ groups: pluginResourceGroups(plugin),
676
785
  };
677
786
  }
678
787
  function entriesFromAgentResources(agent, versionHome, kind) {
@@ -742,12 +851,192 @@ function buildDetailRows(item, kind) {
742
851
  rows.push(['tools', fm.tools.join(', ')]);
743
852
  }
744
853
  }
745
- // Plugin bundles carry their nested resources as pre-built rows.
746
- if (kind === 'plugins' && item.extra) {
747
- rows.push(...item.extra);
854
+ // Plugin bundles surface their nested resources (skills, commands, …) plus
855
+ // scalar rows (version).
856
+ if (kind === 'plugins') {
857
+ if (item.groups)
858
+ for (const g of item.groups)
859
+ rows.push([g.label, g.items.join(', ')]);
860
+ if (item.extra)
861
+ rows.push(...item.extra);
748
862
  }
749
863
  return rows;
750
864
  }
865
+ /** `system` → `sys`; everything else unchanged. Keeps the tag column narrow. */
866
+ function abbrevSource(s) {
867
+ return s === 'system' ? 'sys' : s;
868
+ }
869
+ /**
870
+ * Compact one-liner for a hook from its manifest entry: the firing events (with
871
+ * the matcher/tool-name in parens), then a `·`-separated predicate summary, then
872
+ * an optional cache tail. Plain text — the caller applies color.
873
+ */
874
+ export function summarizeHook(hook) {
875
+ const events = (hook.events ?? []).join('/') || '(no event)';
876
+ let matcher = hook.matcher;
877
+ if (!matcher && hook.matches?.tool_name) {
878
+ const tn = hook.matches.tool_name;
879
+ matcher = Array.isArray(tn) ? tn.join('|') : tn;
880
+ }
881
+ const head = matcher ? `${events}(${matcher})` : events;
882
+ const parts = [head];
883
+ const preds = summarizeMatches(hook.matches);
884
+ if (preds)
885
+ parts.push(preds);
886
+ let line = parts.join(' · ');
887
+ const ttl = hookCacheTtl(hook.cache);
888
+ if (ttl)
889
+ line += ` (${ttl} cache)`;
890
+ return line;
891
+ }
892
+ /** `·`-separated predicate summary from a hook's `matches:` block (tool_name omitted — shown in the matcher parens). */
893
+ function summarizeMatches(m) {
894
+ if (!m)
895
+ return '';
896
+ const bits = [];
897
+ if (m.git_dirty)
898
+ bits.push('git_dirty');
899
+ if (m.prompt_contains)
900
+ bits.push(`prompt~"${truncate(m.prompt_contains, 24)}"`);
901
+ if (m.prompt_matches)
902
+ bits.push(`prompt=/${truncate(m.prompt_matches, 24)}/`);
903
+ if (m.tool_args_match)
904
+ bits.push(`args=/${truncate(m.tool_args_match, 20)}/`);
905
+ if (m.cwd_includes) {
906
+ const c = Array.isArray(m.cwd_includes) ? m.cwd_includes.join('|') : m.cwd_includes;
907
+ bits.push(`cwd~${truncate(c, 24)}`);
908
+ }
909
+ if (m.project_has)
910
+ bits.push(`has ${m.project_has}`);
911
+ return bits.join(' · ');
912
+ }
913
+ /** Normalize a hook cache shorthand/object to a display ttl ("5m", "1h"); null when uncached. */
914
+ function hookCacheTtl(cache) {
915
+ if (cache === undefined || cache === null)
916
+ return null;
917
+ if (typeof cache === 'string')
918
+ return cache.replace(/-bg$/, '');
919
+ return String(cache.ttl);
920
+ }
921
+ /** Compact one-liner for an MCP server: padded transport + the url (http) or command line (stdio). */
922
+ export function summarizeMcp(cfg) {
923
+ const target = cfg.transport === 'http'
924
+ ? (cfg.url ?? '')
925
+ : [cfg.command, ...(cfg.args ?? [])].filter(Boolean).join(' ');
926
+ return `${cfg.transport.padEnd(5)} ${truncate(target, 60)}`.trimEnd();
927
+ }
928
+ /** Print `Title (N)` then up to `max` aligned `[source] name detail` rows with a `…(+K)` tail. */
929
+ function printExpandedSection(title, rows, max = 6) {
930
+ console.log('\n' + chalk.bold(title) + chalk.gray(` (${rows.length})`));
931
+ if (rows.length === 0) {
932
+ console.log(chalk.gray(' (none)'));
933
+ return;
934
+ }
935
+ const shown = rows.slice(0, max);
936
+ const nameW = Math.max(...shown.map(r => r.name.length));
937
+ for (const r of shown) {
938
+ const tag = chalk.gray(`[${abbrevSource(r.source)}]`.padEnd(8));
939
+ const padded = r.name.padEnd(nameW);
940
+ const name = r.linkTarget ? termLink(chalk.cyan(padded), r.linkTarget) : chalk.cyan(padded);
941
+ const detail = r.detail ? ' ' + chalk.gray(r.detail) : '';
942
+ console.log(` ${tag} ${name}${detail}`);
943
+ }
944
+ if (rows.length > max)
945
+ console.log(chalk.gray(` …(+${rows.length - max})`));
946
+ }
947
+ /** Tally a source list into `{user: n, system: m}`. */
948
+ function countBySource(sources) {
949
+ const out = {};
950
+ for (const s of sources)
951
+ out[s] = (out[s] || 0) + 1;
952
+ return out;
953
+ }
954
+ /** Unbracketed scope breakdown for the simple count rows: `user:30 system:12`. */
955
+ function scopeBreakdownPlain(bySource) {
956
+ return Object.entries(bySource).map(([k, v]) => `${k}:${v}`).join(' ');
957
+ }
958
+ /**
959
+ * Index a hook manifest by script basename (no extension). Installed hooks are
960
+ * named after their script file (`04-capture-…`), while the manifest is keyed by
961
+ * logical name (`capture-…`) with the filename in `script:` — so we join on the
962
+ * script basename, not the manifest key.
963
+ */
964
+ export function hookManifestByScript(manifest) {
965
+ const out = new Map();
966
+ for (const hook of Object.values(manifest)) {
967
+ if (hook && typeof hook.script === 'string') {
968
+ out.set(path.basename(hook.script).replace(/\.[^.]+$/, ''), hook);
969
+ }
970
+ }
971
+ return out;
972
+ }
973
+ /** Build hook rows by enriching the installed hook items with manifest events/predicates. */
974
+ function hookRows(items, byScript) {
975
+ return items.map(item => {
976
+ const hook = byScript.get(item.name);
977
+ return {
978
+ source: item.source,
979
+ name: item.name,
980
+ linkTarget: item.linkTarget,
981
+ // Hooks are shell scripts with no human description — show events/predicates
982
+ // from the manifest, or nothing rather than a meaningless shebang line.
983
+ detail: hook ? summarizeHook(hook) : '',
984
+ };
985
+ });
986
+ }
987
+ /** Build plugin rows: `vVERSION skills:6 commands:5 …` from the bundle's groups. */
988
+ function pluginRows(items) {
989
+ return items.map(item => {
990
+ const version = item.extra?.find(([k]) => k === 'version')?.[1];
991
+ const counts = (item.groups ?? []).map(g => `${g.label}:${g.items.length}`).join(' ');
992
+ const detail = [version ? `v${version}` : '', counts].filter(Boolean).join(' ');
993
+ return { source: item.source, name: item.name, detail, linkTarget: item.linkTarget };
994
+ });
995
+ }
996
+ /** Build MCP rows by joining the installed mcp items with their full configs (transport/url/command). */
997
+ function mcpRows(items, configs) {
998
+ return items.map(item => {
999
+ const cfg = configs.get(item.name);
1000
+ return { source: item.source, name: item.name, detail: cfg ? summarizeMcp(cfg) : item.description };
1001
+ });
1002
+ }
1003
+ /** Read a repo/agents.yaml `hooks:` section into a name→ManifestHook map (best-effort). */
1004
+ function hookManifestFromFile(agentsYamlPath) {
1005
+ try {
1006
+ const meta = yaml.parse(fs.readFileSync(agentsYamlPath, 'utf-8'));
1007
+ return meta?.hooks ?? {};
1008
+ }
1009
+ catch {
1010
+ return {};
1011
+ }
1012
+ }
1013
+ /**
1014
+ * Merge the system + user `agents.yaml` hook manifests (user wins on key
1015
+ * collision). Built directly from the two layer files rather than via
1016
+ * `parseHookManifest()` so inspecting never emits the shadow/override warnings
1017
+ * that the registrar path prints.
1018
+ */
1019
+ function loadCentralHookManifest() {
1020
+ return {
1021
+ ...hookManifestFromFile(path.join(getSystemAgentsDir(), 'agents.yaml')),
1022
+ ...hookManifestFromFile(path.join(getUserAgentsDir(), 'agents.yaml')),
1023
+ };
1024
+ }
1025
+ /**
1026
+ * Hook items for a repo's Hooks section. Uses the grouped hook reader (script +
1027
+ * data file collapsed into one entry, non-hook files like promptcuts.yaml or
1028
+ * README.md filtered out) rather than a naive readdir, so names are clean and
1029
+ * join cleanly against the manifest by script basename.
1030
+ */
1031
+ function repoHookItems(repo) {
1032
+ return listHookEntriesFromDir(path.join(repo.root, 'hooks')).map(h => ({
1033
+ name: h.name,
1034
+ source: repo.label,
1035
+ path: h.scriptPath,
1036
+ linkTarget: h.scriptPath,
1037
+ description: '',
1038
+ }));
1039
+ }
751
1040
  function findMatches(items, query) {
752
1041
  const q = query.toLowerCase();
753
1042
  const out = [];
@@ -945,9 +1234,3 @@ function truncate(s, n) {
945
1234
  return s;
946
1235
  return s.slice(0, n - 1) + '…';
947
1236
  }
948
- function formatScopeBreakdown(bySource) {
949
- const entries = Object.entries(bySource);
950
- if (entries.length === 0)
951
- return '';
952
- return '[' + entries.map(([k, v]) => `${k}:${v}`).join(' ') + ']';
953
- }
@@ -92,9 +92,9 @@ function parseMcpAgentTargets(value) {
92
92
  }
93
93
  continue;
94
94
  }
95
- const resolvedVersion = versionToken === 'latest'
96
- ? installedVersions[installedVersions.length - 1]
97
- : versionToken;
95
+ const resolvedVersion = versionToken === 'latest' ? installedVersions[installedVersions.length - 1]
96
+ : versionToken === 'oldest' ? installedVersions[0]
97
+ : versionToken;
98
98
  if (!installedVersions.includes(resolvedVersion)) {
99
99
  throw new VersionNotInstalledError(agentId, resolvedVersion, installedVersions);
100
100
  }
@@ -22,4 +22,6 @@ interface MarketplaceRow {
22
22
  * default Claude version's settings.json#enabledPlugins keyed on @<marketplace>.
23
23
  */
24
24
  export declare function collectMarketplaceRows(): MarketplaceRow[];
25
+ /** Per-category color for a plugin resource breakdown (shared with `agents inspect`). */
26
+ export declare const PLUGIN_GROUP_COLORS: Record<string, (s: string) => string>;
25
27
  export {};