@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.
- package/CHANGELOG.md +30 -0
- package/README.md +3 -0
- package/dist/commands/computer-actions.d.ts +3 -0
- package/dist/commands/computer-actions.js +16 -0
- package/dist/commands/doctor.js +51 -7
- package/dist/commands/exec.js +25 -4
- package/dist/commands/import.js +17 -6
- package/dist/commands/inspect.d.ts +28 -1
- package/dist/commands/inspect.js +330 -47
- package/dist/commands/mcp.js +3 -3
- package/dist/commands/plugins.d.ts +2 -0
- package/dist/commands/plugins.js +69 -26
- package/dist/commands/prune.js +8 -5
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +1 -0
- package/dist/commands/trash.d.ts +11 -0
- package/dist/commands/trash.js +57 -41
- package/dist/commands/versions.js +68 -20
- package/dist/commands/view.d.ts +1 -0
- package/dist/commands/view.js +56 -12
- package/dist/commands/wallet.d.ts +14 -0
- package/dist/commands/wallet.js +199 -0
- package/dist/index.js +4 -1
- package/dist/lib/agents.js +70 -22
- package/dist/lib/browser/ipc.d.ts +7 -0
- package/dist/lib/browser/ipc.js +43 -27
- package/dist/lib/capabilities.js +7 -1
- package/dist/lib/command-skills.d.ts +1 -0
- package/dist/lib/command-skills.js +23 -7
- package/dist/lib/exec.d.ts +32 -1
- package/dist/lib/exec.js +79 -7
- package/dist/lib/hooks.d.ts +21 -1
- package/dist/lib/hooks.js +69 -7
- package/dist/lib/mcp.js +33 -0
- package/dist/lib/models.js +5 -0
- package/dist/lib/picker.d.ts +2 -0
- package/dist/lib/picker.js +96 -6
- package/dist/lib/platform/index.d.ts +1 -0
- package/dist/lib/platform/index.js +1 -0
- package/dist/lib/platform/winpath.d.ts +35 -0
- package/dist/lib/platform/winpath.js +86 -0
- package/dist/lib/plugins.d.ts +24 -0
- package/dist/lib/plugins.js +37 -2
- package/dist/lib/project-launch.js +110 -5
- package/dist/lib/registry.js +15 -2
- package/dist/lib/rotate.d.ts +7 -0
- package/dist/lib/rotate.js +17 -7
- package/dist/lib/runner.js +14 -0
- package/dist/lib/sandbox.js +5 -2
- package/dist/lib/settings-manifest.d.ts +39 -0
- package/dist/lib/settings-manifest.js +163 -0
- package/dist/lib/shims.d.ts +1 -1
- package/dist/lib/shims.js +16 -31
- package/dist/lib/staleness/detectors/subagents.js +16 -0
- package/dist/lib/staleness/writers/subagents.js +11 -3
- package/dist/lib/subagents.d.ts +9 -0
- package/dist/lib/subagents.js +33 -0
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +6 -0
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/versions.d.ts +15 -3
- package/dist/lib/versions.js +88 -19
- package/dist/lib/wallet/index.d.ts +78 -0
- package/dist/lib/wallet/index.js +253 -0
- package/package.json +3 -3
- 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
|
|
|
31
31
|
<a href="https://x.ai" title="Grok Build (xAI)"><strong>Grok</strong></a>
|
|
32
|
+
|
|
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)`);
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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 {
|
|
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
|
|
60
|
-
if (
|
|
61
|
-
ensure(agent, version).hooks =
|
|
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({
|
|
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);
|
package/dist/commands/exec.js
CHANGED
|
@@ -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', '
|
|
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
|
|
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.'));
|
package/dist/commands/import.js
CHANGED
|
@@ -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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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
|
-
/**
|
|
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 {};
|