@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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,36 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ **`agents inspect` summary: expanded detail for hooks, plugins, and MCP**
6
+
7
+ - The bare `agents inspect <agent>` / `agents inspect <repo>` summary no longer collapses everything to a count table. Simple kinds (commands, skills, rules, subagents, workflows) keep a count line but now preview a few names; the rich kinds get their own expanded sections: **hooks** show their events + `matches:` predicates + cache (`PreToolUse(Bash) · git_dirty · prompt~"deploy" (5m cache)`), **plugins** show version + bundle contents (`v2.1.0 skills:6 commands:5 hooks:2 mcp:1`), and **MCP** show transport + url/command. Drill-down flags (`--hooks`, `--plugins`, `--mcp`) and `--brief` are unchanged; `--json` gains the structured detail additively (existing keys retained).
8
+ - Hook detail joins installed hooks to the manifest by **script basename** (installed hooks are named after their script file while the manifest keys on the logical name), and the repo Hooks section uses the grouped hook reader so a script + its data file collapse to one clean entry.
9
+
10
+ **Plugin hooks were misreported — fixed**
11
+
12
+ - `discoverPluginHooks` read the **top-level** keys of a plugin's `hooks/hooks.json`, so the official `{ description, hooks: { SessionStart: [...] } }` format surfaced as `description, hooks` instead of the real events. It now reads the `hooks` wrapper when present (falling back to top-level keys for the flat format), so `agents inspect --plugin <name>` and the plugin row show the actual lifecycle events (e.g. `SessionStart, PreToolUse, …`).
13
+
14
+ **`agents doctor` / `agents prune`: precise orphan-hooks detection**
15
+
16
+ - Orphan-hook detection now flags hook scripts present in a version home that **no `agents.yaml`/`hooks.yaml` entry registers** — i.e. scripts that sync to disk but are never wired to a lifecycle event, so they never fire. This replaces the source-diff heuristic, which compared only against the user hooks dir and so **false-flagged valid system-sourced, registered hooks** (e.g. `03-linear-inject`, `04-capture`) as orphans — meaning `agents prune cleanup` could have deleted live hooks. Doctor's Orphans section and `prune cleanup hooks` now share this single manifest-based definition. `parseHookManifest` gained a silent (`{ warn: false }`) option so the diagnostic doesn't emit shadow/override warnings.
17
+
18
+ **Regression coverage: resource sync from extras repos**
19
+
20
+ - Added end-to-end regression tests (`src/lib/__tests__/extras-sync.test.ts`) locking in two behaviors for repos registered via `agents repo add` (`~/.agents-<alias>/`): a top-level `commands/<name>.md` is written into the agent's version home on `agents sync`, and plugins under `plugins/<name>/` are synthesized into a registered `agents-<alias>` marketplace on launch. Both already work in `main`; the tests exercise the real sync path (no mocking, isolated `$HOME`) so the extras-repo behavior can't silently regress (#313, #314).
21
+
22
+ **Windows: `agents` is discoverable right after `npm i -g`**
23
+
24
+ - On a global Windows install, postinstall now prepends npm's global-bin dir (where `agents.cmd`/`agents.ps1` live) to the **User PATH** via the .NET environment API. Node's installer normally adds it, but winget / portable / nvm-windows setups often don't — and then `npm i -g @phnx-labs/agents-cli` succeeds yet `agents` is "not recognized". The shims dir (claude/codex/…) is still left to `agents setup`, which the user can now run because `agents` resolves.
25
+ - Postinstall also detects a `Restricted`/`AllSigned` PowerShell execution policy (which blocks the generated `.ps1` launchers, so even an on-PATH `agents` fails in PowerShell) and prints the one-line fix (`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`). The policy is a security setting, so it is never changed silently — only surfaced.
26
+ - Refactor: the Windows User-PATH prepend logic moved from `shims.ts` into a new `src/lib/platform/winpath.ts` leaf module (`prependToWindowsUserPath`, `getEffectiveExecutionPolicy`, `blocksLocalScripts`, `npmGlobalBinFromEntry`); `addShimsToWindowsUserPath` now delegates to it. Pure helpers are unit-tested.
27
+
28
+ **Factory AI Droid (first-class support)**
29
+
30
+ - Add `droid` as a first-class supported agent (AgentId + full registry entry for Factory AI's `droid` CLI, config in `~/.factory/`). Installs via the official script (`curl -fsSL https://app.factory.ai/cli | sh`); the binary is resolved through the standard install-script path and isolated per version via the `~/.factory` config symlink (Droid has no `*_HOME` override).
31
+ - Resource sync wired for the four resource types Droid supports natively: **MCP** (`~/.factory/mcp.json`), **rules** (native `AGENTS.md`), **subagents** (custom droids flattened to `~/.factory/droids/*.md`, with the unsupported `color` frontmatter key stripped), and **commands** (`~/.factory/commands/`). Skills/plugins/workflows have no Droid equivalent and are disabled; hooks/permissions are deferred.
32
+ - `agents run droid` and `agents teams add … droid` work end-to-end: headless `droid exec` with mode mapping (plan → read-only, edit → `--auto low`, auto → `--auto high`, skip → `--skip-permissions-unsafe`), `-o stream-json` output, `-m` model selection, and `-r` reasoning effort. Routine/daemon jobs (`buildJobCommand`) support Droid too.
33
+ - Known limitation: `agents teams` renders Droid events through the generic normalizer pending a verified `droid exec -o stream-json` event schema; structured tool/file categorization will follow. Session reading and Factory cloud dispatch remain follow-ups.
34
+
5
35
  **`agents upgrade` now refreshes the macOS Keychain helper**
6
36
 
7
37
  - Upgrading runs `npm install -g … --ignore-scripts`, so the postinstall that installs the signed Keychain helper never fired — a user upgrading away from a broken build (e.g. the entitlement-less 1.20.4 helper that failed `SecItemAdd` with `errSecMissingEntitlement -34018`) kept the broken helper until the lazy staleness check in `getKeychainHelperPath()` happened to repair it on their next secret operation. `installResolvedPackage` now force-refreshes the helper (`ensureKeychainHelperInstalled({ forceReinstall: true })`) on darwin after the install, so both the explicit `agents upgrade` and the auto-update prompt land the fixed helper immediately. Best-effort and non-fatal: an upgrade never fails because the helper could not be reinstalled, and `agents helper install --force` remains the manual path.
package/README.md CHANGED
@@ -29,6 +29,8 @@
29
29
  <a href="https://github.com/NousResearch/hermes-agent" title="Hermes Agent"><img src="assets/harnesses/hermes.png" height="32" alt="Hermes Agent" /></a>
30
30
  &nbsp;&nbsp;&nbsp;&nbsp;
31
31
  <a href="https://x.ai" title="Grok Build (xAI)"><strong>Grok</strong></a>
32
+ &nbsp;&nbsp;&nbsp;&nbsp;
33
+ <a href="https://factory.ai" title="Factory AI Droid"><strong>Droid</strong></a>
32
34
  </p>
33
35
 
34
36
  https://agents-cli.sh/demo.mp4
@@ -86,6 +88,7 @@ Think `requirements.txt` for CLI coding agents, on steroids. A shim reads `agent
86
88
  ```bash
87
89
  agents add claude@2.0.65 # Install a specific version
88
90
  agents add codex@latest # Install latest
91
+ agents add codex@oldest # Install the oldest published version
89
92
  agents view # See everything installed
90
93
  ```
91
94
 
@@ -35,6 +35,9 @@ export declare function buildRaiseParams(opts: {
35
35
  windowId?: number;
36
36
  title?: string;
37
37
  }): Record<string, unknown>;
38
+ export declare const CHAR_DELAY_MIN_MS = 1;
39
+ export declare const CHAR_DELAY_MAX_MS = 250;
40
+ export declare function clampCharDelay(ms: number | undefined): number | undefined;
38
41
  export declare function buildWaitParams(opts: {
39
42
  duration?: number;
40
43
  id?: string;
@@ -70,6 +70,18 @@ export function buildRaiseParams(opts) {
70
70
  params.title = opts.title;
71
71
  return params;
72
72
  }
73
+ // Inter-character typing delay for type-text. Default 4ms matches the daemon's
74
+ // historical fixed rate; lossy keyboard relays (Parallels/VM guests) drop chars
75
+ // at that rate, so callers can raise it. Clamp to [1, 250]ms CLI-side (the
76
+ // daemon clamps too — defense in depth). Returns undefined when unset so the
77
+ // daemon applies its own default. Pure + tested.
78
+ export const CHAR_DELAY_MIN_MS = 1;
79
+ export const CHAR_DELAY_MAX_MS = 250;
80
+ export function clampCharDelay(ms) {
81
+ if (ms === undefined || !Number.isFinite(ms))
82
+ return undefined;
83
+ return Math.min(CHAR_DELAY_MAX_MS, Math.max(CHAR_DELAY_MIN_MS, Math.trunc(ms)));
84
+ }
73
85
  // Build the wait RPC params. Pure + tested. Three modes, mirroring the
74
86
  // daemon's Wait.run: --duration (unconditional sleep), --id + --until
75
87
  // (cached-element poll), or --role/--label/--identifier (live locator poll).
@@ -276,6 +288,7 @@ export function registerActionCommands(program) {
276
288
  .option('--commit', 'Press Return after typing')
277
289
  .option('--raise', 'Bring the target app to the front first')
278
290
  .option('--require-frontmost', 'Fail (not warn) if the target is not the frontmost app')
291
+ .option('--char-delay <ms>', 'Inter-character delay in ms (default 4; raise for lossy keyboard relays like VM guests, e.g. 25). Clamped to [1, 250].', (v) => parseInt(v, 10))
279
292
  .option('--json', 'Emit JSON')).action(async (opts) => {
280
293
  await withClient(async (client) => {
281
294
  const pid = await resolveTargetPid(client, opts);
@@ -285,6 +298,9 @@ export function registerActionCommands(program) {
285
298
  params.commit = true;
286
299
  if (opts.requireFrontmost)
287
300
  params.require_frontmost = true;
301
+ const charDelay = clampCharDelay(opts.charDelay);
302
+ if (charDelay !== undefined)
303
+ params.char_delay_ms = charDelay;
288
304
  const res = unwrap(await client.call('type_text', params));
289
305
  warnIfNotFrontmost(res);
290
306
  emit(res, Boolean(opts.json), () => `typed ${res.chars ?? opts.text.length} char(s)`);
@@ -5,9 +5,10 @@ import { getGlobalDefault, getVersionHomePath, isVersionInstalled, listInstalled
5
5
  import { loadManifest, isStale } from '../lib/staleness/index.js';
6
6
  import { diffVersionCommands, iterCommandsCapableVersions } from '../lib/commands.js';
7
7
  import { diffVersionSkills, iterSkillsCapableVersions } from '../lib/skills.js';
8
- import { diffVersionHooks, iterHooksCapableVersions } from '../lib/hooks.js';
8
+ import { iterHooksCapableVersions, listUnmanagedHooksInVersionHome } from '../lib/hooks.js';
9
9
  import { diffVersionResources, DOCTOR_ALL_KINDS, } from '../lib/doctor-diff.js';
10
10
  import { unifiedDiff, colorizeUnifiedDiff } from '../lib/diff-text.js';
11
+ import { listCliStatus } from '../lib/cli-resources.js';
11
12
  import { setHelpSections } from '../lib/help.js';
12
13
  import * as fs from 'fs';
13
14
  const AGENT_NAMES = Object.fromEntries(ALL_AGENT_IDS.map((id) => [id, AGENTS[id].name]));
@@ -53,16 +54,20 @@ function countOrphans() {
53
54
  if (diff.orphans.length > 0)
54
55
  ensure(agent, version).skills = diff.orphans.length;
55
56
  }
57
+ // Orphan hooks are scripts in the version home that no agents.yaml/hooks.yaml
58
+ // entry registers — so the registrar never wires them to an event and they
59
+ // never fire. (Distinct from the source-diff `diffVersionHooks().orphans`,
60
+ // which false-flags valid system-sourced registered hooks.)
56
61
  for (const { agent, version } of iterHooksCapableVersions()) {
57
62
  if (version !== getGlobalDefault(agent))
58
63
  continue;
59
- const diff = diffVersionHooks(agent, version);
60
- if (diff.orphans.length > 0)
61
- ensure(agent, version).hooks = diff.orphans.length;
64
+ const dead = listUnmanagedHooksInVersionHome(agent, version);
65
+ if (dead.length > 0)
66
+ ensure(agent, version).hooks = dead.length;
62
67
  }
63
68
  return Array.from(byKey.values()).filter((r) => r.commands + r.skills + r.hooks > 0);
64
69
  }
65
- function renderOverviewText(clis, syncRows, orphanRows) {
70
+ function renderOverviewText(clis, syncRows, orphanRows, hostClis) {
66
71
  console.log(chalk.bold('Agent CLIs'));
67
72
  if (Object.keys(clis).length === 0) {
68
73
  console.log(chalk.gray(' (no agents reported)'));
@@ -116,6 +121,31 @@ function renderOverviewText(clis, syncRows, orphanRows) {
116
121
  }
117
122
  console.log(chalk.gray(' Run `agents prune cleanup` to remove.'));
118
123
  }
124
+ console.log();
125
+ // Host CLIs are host-global (declared in any DotAgents repo's cli/, installed
126
+ // to PATH — not synced into version homes), so they live in the overview, not
127
+ // the per-version resource diff. Source tag shows which repo layer declared
128
+ // each, including user-level and extra repos.
129
+ console.log(chalk.bold('Host CLIs'));
130
+ if (hostClis.statuses.length === 0) {
131
+ console.log(chalk.gray(' (none declared — add one with `agents cli add <name>`)'));
132
+ }
133
+ else {
134
+ const nameWidth = Math.max(...hostClis.statuses.map((s) => s.manifest.name.length));
135
+ for (const { manifest, installed } of hostClis.statuses) {
136
+ const label = manifest.name.padEnd(nameWidth);
137
+ const src = chalk.gray(`[${manifest.source}]`);
138
+ if (installed) {
139
+ console.log(` ${chalk.green('ready')} ${label} ${src} ${chalk.gray(manifest.description || '')}`);
140
+ }
141
+ else {
142
+ console.log(` ${chalk.red('miss ')} ${label} ${src} ${chalk.gray(`not installed — run \`agents cli install ${manifest.name}\``)}`);
143
+ }
144
+ }
145
+ }
146
+ for (const err of hostClis.errors) {
147
+ console.log(` ${chalk.red('err ')} ${chalk.gray(err.file)}: ${chalk.gray(err.reason)}`);
148
+ }
119
149
  }
120
150
  function parseTargetArg(arg) {
121
151
  const at = arg.indexOf('@');
@@ -346,11 +376,25 @@ export function registerDoctorCommand(program) {
346
376
  const clis = checkAllClis();
347
377
  const syncRows = checkSyncStatus(cwd);
348
378
  const orphanRows = countOrphans();
379
+ const hostClis = listCliStatus(cwd);
349
380
  if (opts.json) {
350
- console.log(JSON.stringify({ clis, sync: syncRows, orphans: orphanRows }, null, 2));
381
+ console.log(JSON.stringify({
382
+ clis,
383
+ sync: syncRows,
384
+ orphans: orphanRows,
385
+ hostClis: {
386
+ statuses: hostClis.statuses.map((s) => ({
387
+ name: s.manifest.name,
388
+ source: s.manifest.source,
389
+ description: s.manifest.description ?? null,
390
+ installed: s.installed,
391
+ })),
392
+ errors: hostClis.errors,
393
+ },
394
+ }, null, 2));
351
395
  return;
352
396
  }
353
- renderOverviewText(clis, syncRows, orphanRows);
397
+ renderOverviewText(clis, syncRows, orphanRows, hostClis);
354
398
  return;
355
399
  }
356
400
  const parsed = parseTargetArg(target);
@@ -36,8 +36,8 @@ export function registerRunCommand(program) {
36
36
  .option('--add-dir <dir>', 'Grant access to an additional directory outside the project (Claude only, repeatable)', (val, prev) => [...prev, val], [])
37
37
  .option('--json', 'Stream events as JSON lines (for parsing by other tools)')
38
38
  .option('--quiet', 'Suppress preamble (rotation banner, "Running:" line). Useful when piping JSON events to a parser.', false)
39
- .option('--headless', 'Non-interactive mode (auto-enabled when prompt provided)', false)
40
- .option('-i, --interactive', 'Force interactive mode even when a prompt is provided')
39
+ .option('--headless', 'Force headless mode. Auto-enabled when a prompt is provided; pass explicitly to stay headless with no prompt (reads the prompt from stdin).', false)
40
+ .option('-i, --interactive', 'Force interactive mode even when a prompt is provided. Mutually exclusive with --headless.')
41
41
  .option('--session-id <id>', 'Resume a previous conversation (Claude only)')
42
42
  .option('--verbose', 'Show detailed execution logs')
43
43
  .option('--timeout <duration>', 'Kill the agent after this duration (e.g., 30m, 1h, 2h30m)')
@@ -85,7 +85,7 @@ export function registerRunCommand(program) {
85
85
  `,
86
86
  });
87
87
  runCmd.action(async (agentSpec, prompt, options) => {
88
- const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, defaultModeFor }, { ALL_AGENT_IDS }, { profileExists, resolveProfileForRun }, { readAndResolveBundleEnv, describeBundle }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, RUN_STRATEGIES }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef }, { resolveRunDefaults },] = await Promise.all([
88
+ const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, defaultModeFor, headlessPlanStallCommand }, { ALL_AGENT_IDS }, { profileExists, resolveProfileForRun }, { readAndResolveBundleEnv, describeBundle }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, RUN_STRATEGIES }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef }, { resolveRunDefaults },] = await Promise.all([
89
89
  import('../lib/exec.js'),
90
90
  import('../lib/agents.js'),
91
91
  import('../lib/profiles.js'),
@@ -306,6 +306,23 @@ export function registerRunCommand(program) {
306
306
  process.exit(1);
307
307
  }
308
308
  }
309
+ // Fail fast on the headless-plan stall footgun: a slash command run
310
+ // headless under the implicit default 'plan' mode hangs forever at
311
+ // ExitPlanMode (no TTY to approve the plan). Tell the user how to fix it
312
+ // instead of leaving them staring at a frozen process. Explicit
313
+ // `--mode plan` is respected for genuine read-only command runs.
314
+ const stallCmd = headlessPlanStallCommand({
315
+ prompt,
316
+ interactive: options.interactive,
317
+ mode,
318
+ modeIsDefault,
319
+ });
320
+ if (stallCmd) {
321
+ console.error(chalk.red(`Refusing to run ${stallCmd} headless in read-only 'plan' mode — it would hang at ExitPlanMode (no TTY to approve the plan).`));
322
+ console.error(chalk.yellow(`Re-run with an explicit mode: --mode auto (recommended — auto-approves safe ops, blocks risky ones), --mode edit, or --mode full.`));
323
+ console.error(chalk.gray(`Pass --mode plan explicitly if you really want a read-only run.`));
324
+ process.exit(1);
325
+ }
309
326
  const effort = options.effort;
310
327
  if (!['low', 'medium', 'high', 'xhigh', 'max', 'auto'].includes(effort)) {
311
328
  console.error(chalk.red(`Invalid effort: ${effort}. Use 'low', 'medium', 'high', 'xhigh', 'max', or 'auto'`));
@@ -363,12 +380,16 @@ export function registerRunCommand(program) {
363
380
  model,
364
381
  addDirs: options.addDir,
365
382
  json: options.json,
366
- headless: options.headless ?? true,
383
+ headless: options.headless,
367
384
  sessionId: options.sessionId,
368
385
  verbose: options.verbose,
369
386
  timeout: options.timeout,
370
387
  env,
371
388
  };
389
+ if (options.interactive && options.headless) {
390
+ console.error(chalk.red('--interactive and --headless are mutually exclusive. Pass one, or neither (mode is inferred from prompt presence).'));
391
+ process.exit(1);
392
+ }
372
393
  if (options.interactive) {
373
394
  if (options.fallback) {
374
395
  console.error(chalk.red('--interactive is not compatible with --fallback. Fallback only works for headless prompt runs.'));
@@ -44,6 +44,11 @@ async function runImport(agentArg, opts) {
44
44
  // installer dropped it. We adopt by symlinking that PATH binary directly
45
45
  // into the version's `node_modules/.bin/`. No package.json walk.
46
46
  const isInstallScriptAgent = !agent.npmPackage;
47
+ // Whether to adopt the binary by a direct symlink (installScript style) vs.
48
+ // the npm package.json walk. Starts equal to isInstallScriptAgent, but an
49
+ // npm-capable agent that turns out to be installed as a standalone binary
50
+ // (see the resolvePackageDirFromBinary fallback below) flips this to true.
51
+ let useDirectBinaryImport = isInstallScriptAgent;
47
52
  let globalPath = null;
48
53
  let installScriptBinary = null;
49
54
  if (opts.fromPath) {
@@ -84,9 +89,15 @@ async function runImport(agentArg, opts) {
84
89
  else {
85
90
  globalPath = resolvePackageDirFromBinary(binary);
86
91
  if (!globalPath) {
87
- console.error(chalk.red(`Could not resolve npm package for binary: ${binary}`));
88
- console.error(chalk.gray('Pass --from-path <dir> with the package directory explicitly.'));
89
- process.exit(1);
92
+ // npmPackage is declared, but the binary on PATH doesn't live inside an
93
+ // npm package layout — e.g. Kimi installed via its curl install.sh,
94
+ // which drops a standalone bundled binary at ~/.kimi-code/bin/kimi
95
+ // rather than into node_modules/<pkg>/. The binary is valid and
96
+ // self-contained, so adopt it the same way as an installScript agent:
97
+ // a direct symlink. Key the decision on the on-disk layout, not on
98
+ // whether an npmPackage label happens to exist.
99
+ installScriptBinary = binary;
100
+ useDirectBinaryImport = true;
90
101
  }
91
102
  }
92
103
  }
@@ -112,7 +123,7 @@ async function runImport(agentArg, opts) {
112
123
  }
113
124
  let version = opts.version;
114
125
  if (!version) {
115
- if (!isInstallScriptAgent && globalPath) {
126
+ if (!useDirectBinaryImport && globalPath) {
116
127
  try {
117
128
  const pkg = JSON.parse(fs.readFileSync(path.join(globalPath, 'package.json'), 'utf8'));
118
129
  version = typeof pkg.version === 'string' ? pkg.version : undefined;
@@ -142,7 +153,7 @@ async function runImport(agentArg, opts) {
142
153
  process.exit(1);
143
154
  }
144
155
  const versionDir = getVersionDir(agentId, version);
145
- const fromLabel = isInstallScriptAgent ? installScriptBinary : globalPath;
156
+ const fromLabel = useDirectBinaryImport ? installScriptBinary : globalPath;
146
157
  console.log(chalk.bold(`\nImport ${agentLabel(agentId)} v${version}`));
147
158
  console.log(` from: ${chalk.gray(fromLabel)}`);
148
159
  console.log(` into: ${chalk.gray(versionDir)}`);
@@ -197,7 +208,7 @@ async function runImport(agentArg, opts) {
197
208
  }
198
209
  }
199
210
  const binSpinner = ora(`Registering ${agentLabel(agentId)} v${version} binary...`).start();
200
- const binResult = isInstallScriptAgent
211
+ const binResult = useDirectBinaryImport
201
212
  ? importInstallScriptBinary({ agentId, npmPackage: agent.npmPackage, cliCommand: agent.cliCommand }, version, installScriptBinary, versionDir)
202
213
  : importAgentBinary({ agentId, npmPackage: agent.npmPackage, cliCommand: agent.cliCommand }, version, globalPath, versionDir);
203
214
  if (binResult.success) {
@@ -12,6 +12,9 @@
12
12
  * AGENT.md / the file itself) so users can click straight to the source.
13
13
  */
14
14
  import { Command } from 'commander';
15
+ import type { ManifestHook } from '../lib/types.js';
16
+ import { type McpYamlConfig } from '../lib/mcp.js';
17
+ import { type PluginResourceGroup } from '../lib/plugins.js';
15
18
  /** Resource kinds the inspect command can drill into. */
16
19
  declare const DRILLABLE_KINDS: readonly ["commands", "skills", "hooks", "mcp", "rules", "plugins", "workflows", "subagents"];
17
20
  type DrillableKind = typeof DRILLABLE_KINDS[number];
@@ -24,8 +27,10 @@ interface ResourceItem {
24
27
  linkTarget: string;
25
28
  /** One-line description (frontmatter `description:` or first non-frontmatter line). */
26
29
  description: string;
27
- /** Extra detail rows surfaced in detail mode (e.g. a plugin's bundled skills/commands). */
30
+ /** Scalar detail rows surfaced in detail mode (e.g. a plugin's version). */
28
31
  extra?: Array<[string, string]>;
32
+ /** For plugins: the resource categories (skills, commands, …) the bundle packages. */
33
+ groups?: PluginResourceGroup[];
29
34
  }
30
35
  interface InspectOptions {
31
36
  brief?: boolean;
@@ -38,6 +43,13 @@ interface InspectOptions {
38
43
  plugins?: boolean | string;
39
44
  workflows?: boolean | string;
40
45
  subagents?: boolean | string;
46
+ command?: string;
47
+ skill?: string;
48
+ hook?: string;
49
+ rule?: string;
50
+ plugin?: string;
51
+ workflow?: string;
52
+ subagent?: string;
41
53
  }
42
54
  export declare function registerInspectCommand(program: Command): void;
43
55
  export declare function inspectAction(target: string, options: InspectOptions): Promise<void>;
@@ -91,4 +103,19 @@ export interface RepoGitInfo {
91
103
  behind: number | null;
92
104
  }
93
105
  export declare function repoGitInfo(root: string): RepoGitInfo | null;
106
+ /**
107
+ * Compact one-liner for a hook from its manifest entry: the firing events (with
108
+ * the matcher/tool-name in parens), then a `·`-separated predicate summary, then
109
+ * an optional cache tail. Plain text — the caller applies color.
110
+ */
111
+ export declare function summarizeHook(hook: ManifestHook): string;
112
+ /** Compact one-liner for an MCP server: padded transport + the url (http) or command line (stdio). */
113
+ export declare function summarizeMcp(cfg: McpYamlConfig): string;
114
+ /**
115
+ * Index a hook manifest by script basename (no extension). Installed hooks are
116
+ * named after their script file (`04-capture-…`), while the manifest is keyed by
117
+ * logical name (`capture-…`) with the filename in `script:` — so we join on the
118
+ * script basename, not the manifest key.
119
+ */
120
+ export declare function hookManifestByScript(manifest: Record<string, ManifestHook>): Map<string, ManifestHook>;
94
121
  export {};