@phnx-labs/agents-cli 1.20.12 → 1.20.13
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 +13 -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/exec.js +25 -4
- package/dist/commands/import.js +17 -6
- package/dist/commands/inspect.d.ts +11 -1
- package/dist/commands/inspect.js +53 -19
- 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/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.js +1 -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.js +37 -5
- 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 +14 -0
- package/dist/lib/plugins.js +23 -0
- package/dist/lib/project-launch.js +110 -5
- package/dist/lib/registry.js +15 -2
- 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,19 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
**Windows: `agents` is discoverable right after `npm i -g`**
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
- 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.
|
|
9
|
+
- 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.
|
|
10
|
+
|
|
11
|
+
**Factory AI Droid (first-class support)**
|
|
12
|
+
|
|
13
|
+
- 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).
|
|
14
|
+
- 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.
|
|
15
|
+
- `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.
|
|
16
|
+
- 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.
|
|
17
|
+
|
|
5
18
|
**`agents upgrade` now refreshes the macOS Keychain helper**
|
|
6
19
|
|
|
7
20
|
- 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/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,7 @@
|
|
|
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 PluginResourceGroup } from '../lib/plugins.js';
|
|
15
16
|
/** Resource kinds the inspect command can drill into. */
|
|
16
17
|
declare const DRILLABLE_KINDS: readonly ["commands", "skills", "hooks", "mcp", "rules", "plugins", "workflows", "subagents"];
|
|
17
18
|
type DrillableKind = typeof DRILLABLE_KINDS[number];
|
|
@@ -24,8 +25,10 @@ interface ResourceItem {
|
|
|
24
25
|
linkTarget: string;
|
|
25
26
|
/** One-line description (frontmatter `description:` or first non-frontmatter line). */
|
|
26
27
|
description: string;
|
|
27
|
-
/**
|
|
28
|
+
/** Scalar detail rows surfaced in detail mode (e.g. a plugin's version). */
|
|
28
29
|
extra?: Array<[string, string]>;
|
|
30
|
+
/** For plugins: the resource categories (skills, commands, …) the bundle packages. */
|
|
31
|
+
groups?: PluginResourceGroup[];
|
|
29
32
|
}
|
|
30
33
|
interface InspectOptions {
|
|
31
34
|
brief?: boolean;
|
|
@@ -38,6 +41,13 @@ interface InspectOptions {
|
|
|
38
41
|
plugins?: boolean | string;
|
|
39
42
|
workflows?: boolean | string;
|
|
40
43
|
subagents?: boolean | string;
|
|
44
|
+
command?: string;
|
|
45
|
+
skill?: string;
|
|
46
|
+
hook?: string;
|
|
47
|
+
rule?: string;
|
|
48
|
+
plugin?: string;
|
|
49
|
+
workflow?: string;
|
|
50
|
+
subagent?: string;
|
|
41
51
|
}
|
|
42
52
|
export declare function registerInspectCommand(program: Command): void;
|
|
43
53
|
export declare function inspectAction(target: string, options: InspectOptions): Promise<void>;
|
package/dist/commands/inspect.js
CHANGED
|
@@ -23,7 +23,8 @@ 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 { discoverPlugins, discoverPluginsInDir, pluginResourceGroups } from '../lib/plugins.js';
|
|
27
|
+
import { PLUGIN_GROUP_COLORS } from './plugins.js';
|
|
27
28
|
import { countSessionsInScope } from '../lib/session/discover.js';
|
|
28
29
|
import { damerauLevenshtein } from '../lib/fuzzy.js';
|
|
29
30
|
/** Resource kinds the inspect command can drill into. */
|
|
@@ -37,6 +38,21 @@ const DRILLABLE_KINDS = [
|
|
|
37
38
|
'workflows',
|
|
38
39
|
'subagents',
|
|
39
40
|
];
|
|
41
|
+
/**
|
|
42
|
+
* Singular aliases for the plural drill-down flags. `--plugin code` reads as
|
|
43
|
+
* "show the one plugin named code" — a required-value flag that always lands in
|
|
44
|
+
* detail mode, the natural counterpart to `--plugins` (list). `mcp` has no
|
|
45
|
+
* distinct singular, so it is intentionally absent.
|
|
46
|
+
*/
|
|
47
|
+
const SINGULAR_DRILL_ALIASES = {
|
|
48
|
+
command: 'commands',
|
|
49
|
+
skill: 'skills',
|
|
50
|
+
hook: 'hooks',
|
|
51
|
+
rule: 'rules',
|
|
52
|
+
plugin: 'plugins',
|
|
53
|
+
workflow: 'workflows',
|
|
54
|
+
subagent: 'subagents',
|
|
55
|
+
};
|
|
40
56
|
const CAPABILITY_NAMES = [
|
|
41
57
|
'hooks', 'mcp', 'skills', 'commands', 'subagents', 'plugins', 'workflows', 'rules', 'allowlist',
|
|
42
58
|
];
|
|
@@ -50,6 +66,9 @@ export function registerInspectCommand(program) {
|
|
|
50
66
|
for (const kind of DRILLABLE_KINDS) {
|
|
51
67
|
cmd.option(`--${kind} [query]`, `list ${kind}; pass a name (fuzzy) to show detail`);
|
|
52
68
|
}
|
|
69
|
+
for (const singular of Object.keys(SINGULAR_DRILL_ALIASES)) {
|
|
70
|
+
cmd.option(`--${singular} <query>`, `show detail for one ${singular} by name (fuzzy)`);
|
|
71
|
+
}
|
|
53
72
|
cmd.action(async (target, options) => {
|
|
54
73
|
await inspectAction(target, options);
|
|
55
74
|
});
|
|
@@ -121,15 +140,21 @@ function pickDrillKind(options) {
|
|
|
121
140
|
for (const kind of DRILLABLE_KINDS) {
|
|
122
141
|
const value = options[kind];
|
|
123
142
|
if (value !== undefined)
|
|
124
|
-
active.push({ kind, query: value });
|
|
143
|
+
active.push({ flag: `--${kind}`, kind, query: value });
|
|
144
|
+
}
|
|
145
|
+
// Singular aliases (`--plugin code`) always carry a name → detail mode.
|
|
146
|
+
for (const [singular, plural] of Object.entries(SINGULAR_DRILL_ALIASES)) {
|
|
147
|
+
const value = options[singular];
|
|
148
|
+
if (typeof value === 'string')
|
|
149
|
+
active.push({ flag: `--${singular}`, kind: plural, query: value });
|
|
125
150
|
}
|
|
126
151
|
if (active.length === 0)
|
|
127
152
|
return null;
|
|
128
153
|
if (active.length > 1) {
|
|
129
|
-
console.error(chalk.red(`Pick at most one drill-down flag. Got: ${active.map(a =>
|
|
154
|
+
console.error(chalk.red(`Pick at most one drill-down flag. Got: ${active.map(a => a.flag).join(', ')}`));
|
|
130
155
|
process.exit(1);
|
|
131
156
|
}
|
|
132
|
-
return active[0];
|
|
157
|
+
return { kind: active[0].kind, query: active[0].query };
|
|
133
158
|
}
|
|
134
159
|
/** Files at a DotAgents root that mark it as one, beyond the per-kind dirs. */
|
|
135
160
|
const REPO_MARKER_FILES = ['agents.yaml', 'hooks.yaml'];
|
|
@@ -526,7 +551,7 @@ function renderItemList(header, jsonHead, kind, items, options) {
|
|
|
526
551
|
...jsonHead,
|
|
527
552
|
kind,
|
|
528
553
|
count: items.length,
|
|
529
|
-
items: items.map(i => ({ name: i.name, source: i.source, path: i.path, description: i.description })),
|
|
554
|
+
items: items.map(i => ({ name: i.name, source: i.source, path: i.path, description: i.description, ...(i.groups ? { groups: i.groups } : {}) })),
|
|
530
555
|
}, null, 2));
|
|
531
556
|
return;
|
|
532
557
|
}
|
|
@@ -542,9 +567,23 @@ function renderItemList(header, jsonHead, kind, items, options) {
|
|
|
542
567
|
if (item.description) {
|
|
543
568
|
console.log(` ${chalk.gray(truncate(item.description, 90))}`);
|
|
544
569
|
}
|
|
570
|
+
if (item.groups)
|
|
571
|
+
printGroupRows(item.groups);
|
|
545
572
|
}
|
|
546
573
|
console.log('');
|
|
547
574
|
}
|
|
575
|
+
/** Print a plugin's resource breakdown as aligned `label items` rows under a list entry. */
|
|
576
|
+
function printGroupRows(groups) {
|
|
577
|
+
if (groups.length === 0)
|
|
578
|
+
return;
|
|
579
|
+
const width = Math.max(...groups.map(g => g.label.length));
|
|
580
|
+
for (const g of groups) {
|
|
581
|
+
const colorFn = PLUGIN_GROUP_COLORS[g.label] ?? chalk.white;
|
|
582
|
+
const label = chalk.gray(g.label.padEnd(width));
|
|
583
|
+
const value = g.items.map((s) => colorFn(s)).join(chalk.gray(', '));
|
|
584
|
+
console.log(` ${label} ${value}`);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
548
587
|
// ─── Detail mode (fuzzy) ─────────────────────────────────────────────────────
|
|
549
588
|
async function renderDetail(agent, version, versionHome, kind, query, options) {
|
|
550
589
|
const items = collectKind(agent, versionHome, kind);
|
|
@@ -653,17 +692,6 @@ function pluginItems() {
|
|
|
653
692
|
*/
|
|
654
693
|
function pluginToItem(plugin, source) {
|
|
655
694
|
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
695
|
if (plugin.manifest.version)
|
|
668
696
|
extra.push(['version', plugin.manifest.version]);
|
|
669
697
|
return {
|
|
@@ -673,6 +701,7 @@ function pluginToItem(plugin, source) {
|
|
|
673
701
|
linkTarget: linkTarget(plugin.root),
|
|
674
702
|
description: plugin.manifest.description ?? '',
|
|
675
703
|
extra,
|
|
704
|
+
groups: pluginResourceGroups(plugin),
|
|
676
705
|
};
|
|
677
706
|
}
|
|
678
707
|
function entriesFromAgentResources(agent, versionHome, kind) {
|
|
@@ -742,9 +771,14 @@ function buildDetailRows(item, kind) {
|
|
|
742
771
|
rows.push(['tools', fm.tools.join(', ')]);
|
|
743
772
|
}
|
|
744
773
|
}
|
|
745
|
-
// Plugin bundles
|
|
746
|
-
|
|
747
|
-
|
|
774
|
+
// Plugin bundles surface their nested resources (skills, commands, …) plus
|
|
775
|
+
// scalar rows (version).
|
|
776
|
+
if (kind === 'plugins') {
|
|
777
|
+
if (item.groups)
|
|
778
|
+
for (const g of item.groups)
|
|
779
|
+
rows.push([g.label, g.items.join(', ')]);
|
|
780
|
+
if (item.extra)
|
|
781
|
+
rows.push(...item.extra);
|
|
748
782
|
}
|
|
749
783
|
return rows;
|
|
750
784
|
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -92,9 +92,9 @@ function parseMcpAgentTargets(value) {
|
|
|
92
92
|
}
|
|
93
93
|
continue;
|
|
94
94
|
}
|
|
95
|
-
const resolvedVersion = versionToken === 'latest'
|
|
96
|
-
? installedVersions[
|
|
97
|
-
|
|
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 {};
|
package/dist/commands/plugins.js
CHANGED
|
@@ -12,7 +12,7 @@ import { homeDir } from '../lib/platform/index.js';
|
|
|
12
12
|
import { input } from '@inquirer/prompts';
|
|
13
13
|
import { agentLabel } from '../lib/agents.js';
|
|
14
14
|
import { capableAgents, isCapable } from '../lib/capabilities.js';
|
|
15
|
-
import { discoverPlugins, getPlugin, pluginSupportsAgent, removePluginFromVersion, isPluginSynced, installPlugin, updatePlugin, loadUserConfig, saveUserConfig, checkPluginDependencies, hasPluginExecSurfaces, inspectPluginCapabilities, pluginCapabilityLabels, parseInstallSpec, syncPluginToVersion, } from '../lib/plugins.js';
|
|
15
|
+
import { discoverPlugins, getPlugin, pluginSupportsAgent, removePluginFromVersion, isPluginSynced, installPlugin, updatePlugin, loadUserConfig, saveUserConfig, checkPluginDependencies, hasPluginExecSurfaces, inspectPluginCapabilities, pluginCapabilityLabels, parseInstallSpec, syncPluginToVersion, pluginResourceGroups, } from '../lib/plugins.js';
|
|
16
16
|
import { listInstalledVersions, syncResourcesToVersion, getGlobalDefault, getVersionHomePath, } from '../lib/versions.js';
|
|
17
17
|
import { isPromptCancelled, isInteractiveTerminal, requireDestructiveArg, requireInteractiveSelection, promptRemovalTargets, } from './utils.js';
|
|
18
18
|
import { itemPicker } from '../lib/picker.js';
|
|
@@ -276,14 +276,17 @@ Examples:
|
|
|
276
276
|
// agents plugins sync <name> [agent]
|
|
277
277
|
pluginsCmd
|
|
278
278
|
.command('sync <name> [agent]')
|
|
279
|
-
.description('Apply a plugin to
|
|
279
|
+
.description('Apply a plugin to an agent. Syncs every installed version (pass agent@version to target one).')
|
|
280
280
|
.option('--allow-exec-surfaces', 'Enable the plugin even when it ships hooks/, .mcp.json, bin/, scripts/, settings.json, or permissions/')
|
|
281
281
|
.addHelpText('after', `
|
|
282
282
|
Examples:
|
|
283
|
-
# Sync a plugin to
|
|
283
|
+
# Sync a plugin to every installed version of an agent
|
|
284
284
|
agents plugins sync rush-toolkit claude
|
|
285
285
|
|
|
286
|
-
# Sync to
|
|
286
|
+
# Sync to one specific version (parity with 'agents sync')
|
|
287
|
+
agents plugins sync rush-toolkit claude@2.1.142
|
|
288
|
+
|
|
289
|
+
# Sync to all supported agents (every installed version of each)
|
|
287
290
|
agents plugins sync rush-toolkit
|
|
288
291
|
|
|
289
292
|
# Re-affirm consent for a hooks-bearing plugin
|
|
@@ -295,12 +298,22 @@ Examples:
|
|
|
295
298
|
console.log(chalk.red(`Plugin '${name}' not found`));
|
|
296
299
|
process.exit(1);
|
|
297
300
|
}
|
|
301
|
+
// Accept the same "agent@version" form as `agents sync`. Splitting here
|
|
302
|
+
// also means an unknown spec is reported cleanly rather than crashing
|
|
303
|
+
// isCapable() with a bare "claude@2.1.168".
|
|
304
|
+
let versionArg;
|
|
305
|
+
let agentName = agentArg;
|
|
306
|
+
if (agentArg && agentArg.includes('@')) {
|
|
307
|
+
const at = agentArg.lastIndexOf('@');
|
|
308
|
+
agentName = agentArg.slice(0, at);
|
|
309
|
+
versionArg = agentArg.slice(at + 1);
|
|
310
|
+
}
|
|
298
311
|
// Determine target agents
|
|
299
312
|
let targetAgents;
|
|
300
|
-
if (
|
|
301
|
-
const agentId =
|
|
313
|
+
if (agentName) {
|
|
314
|
+
const agentId = agentName;
|
|
302
315
|
if (!isCapable(agentId, 'plugins')) {
|
|
303
|
-
console.log(chalk.red(`Agent '${
|
|
316
|
+
console.log(chalk.red(`Agent '${agentName}' does not support plugins`));
|
|
304
317
|
process.exit(1);
|
|
305
318
|
}
|
|
306
319
|
if (!pluginSupportsAgent(plugin, agentId)) {
|
|
@@ -310,6 +323,10 @@ Examples:
|
|
|
310
323
|
targetAgents = [agentId];
|
|
311
324
|
}
|
|
312
325
|
else {
|
|
326
|
+
if (versionArg) {
|
|
327
|
+
console.log(chalk.red(`A version (@${versionArg}) requires naming the agent, e.g. claude@${versionArg}`));
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
313
330
|
targetAgents = capableAgents('plugins').filter(a => pluginSupportsAgent(plugin, a));
|
|
314
331
|
}
|
|
315
332
|
const allowExec = options.allowExecSurfaces === true;
|
|
@@ -317,8 +334,21 @@ Examples:
|
|
|
317
334
|
const versions = listInstalledVersions(agentId);
|
|
318
335
|
if (versions.length === 0)
|
|
319
336
|
continue;
|
|
320
|
-
|
|
321
|
-
|
|
337
|
+
// Default to EVERY installed version. The previous behaviour synced only
|
|
338
|
+
// the global default, which silently skipped non-default versions used
|
|
339
|
+
// by balanced rotation -- so a rotated version would lack the plugin's
|
|
340
|
+
// slash commands. An explicit agent@version narrows back to one.
|
|
341
|
+
let targetVersions;
|
|
342
|
+
if (versionArg) {
|
|
343
|
+
if (!versions.includes(versionArg)) {
|
|
344
|
+
console.log(chalk.red(`${agentLabel(agentId)} has no installed version ${versionArg} (installed: ${versions.join(', ')})`));
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
targetVersions = [versionArg];
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
targetVersions = versions;
|
|
351
|
+
}
|
|
322
352
|
for (const version of targetVersions) {
|
|
323
353
|
const didSync = allowExec
|
|
324
354
|
? syncPluginToVersion(plugin, agentId, getVersionHomePath(agentId, version), { allowExecSurfaces: true, version }).success
|
|
@@ -711,6 +741,32 @@ function buildPluginRows(plugins) {
|
|
|
711
741
|
});
|
|
712
742
|
return rows;
|
|
713
743
|
}
|
|
744
|
+
/** Per-category color for a plugin resource breakdown (shared with `agents inspect`). */
|
|
745
|
+
export const PLUGIN_GROUP_COLORS = {
|
|
746
|
+
skills: chalk.cyan,
|
|
747
|
+
commands: chalk.cyan,
|
|
748
|
+
subagents: chalk.magenta,
|
|
749
|
+
hooks: chalk.yellow,
|
|
750
|
+
mcp: chalk.green,
|
|
751
|
+
lsp: chalk.green,
|
|
752
|
+
monitors: chalk.blue,
|
|
753
|
+
bin: chalk.white,
|
|
754
|
+
scripts: chalk.white,
|
|
755
|
+
settings: chalk.gray,
|
|
756
|
+
};
|
|
757
|
+
/** Human-readable section header per category, used by the picker detail pane. */
|
|
758
|
+
const PLUGIN_GROUP_TITLES = {
|
|
759
|
+
skills: 'Skills',
|
|
760
|
+
commands: 'Commands',
|
|
761
|
+
subagents: 'Subagents',
|
|
762
|
+
hooks: 'Hooks',
|
|
763
|
+
mcp: 'MCP Servers',
|
|
764
|
+
lsp: 'LSP Servers',
|
|
765
|
+
monitors: 'Monitors',
|
|
766
|
+
bin: 'Bin',
|
|
767
|
+
scripts: 'Scripts',
|
|
768
|
+
settings: 'Settings',
|
|
769
|
+
};
|
|
714
770
|
/** Build the multi-line detail pane shown when a plugin is selected in the picker. */
|
|
715
771
|
function formatPluginDetail(plugin, targets) {
|
|
716
772
|
const lines = [];
|
|
@@ -728,24 +784,11 @@ function formatPluginDetail(plugin, targets) {
|
|
|
728
784
|
lines.push(' ' + chalk.gray('Supports: ') + supported.join(chalk.gray(' · ')));
|
|
729
785
|
}
|
|
730
786
|
lines.push(' ' + chalk.gray(formatPath(plugin.root)));
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
return;
|
|
787
|
+
for (const group of pluginResourceGroups(plugin)) {
|
|
788
|
+
const colorFn = PLUGIN_GROUP_COLORS[group.label] ?? chalk.white;
|
|
734
789
|
lines.push('');
|
|
735
|
-
lines.push(chalk.bold(` ${label}`));
|
|
736
|
-
lines.push(' ' + items.map(colorFn).join(chalk.gray(', ')));
|
|
737
|
-
};
|
|
738
|
-
section('Skills', plugin.skills.map((s) => `/${plugin.name}:${s}`), chalk.cyan);
|
|
739
|
-
section('Commands', plugin.commands.map((c) => `/${plugin.name}:${c}`), chalk.cyan);
|
|
740
|
-
section('Subagents', plugin.agentDefs, chalk.magenta);
|
|
741
|
-
section('Hooks', plugin.hooks, chalk.yellow);
|
|
742
|
-
section('MCP Servers', plugin.mcpServers, chalk.green);
|
|
743
|
-
section('LSP Servers', plugin.lspServers, chalk.green);
|
|
744
|
-
section('Monitors', plugin.monitors, chalk.blue);
|
|
745
|
-
section('Bin', plugin.bin, chalk.white);
|
|
746
|
-
section('Scripts', plugin.scripts, chalk.white);
|
|
747
|
-
if (plugin.hasSettings) {
|
|
748
|
-
section('Settings', ['settings.json'], chalk.gray);
|
|
790
|
+
lines.push(chalk.bold(` ${PLUGIN_GROUP_TITLES[group.label] ?? group.label}`));
|
|
791
|
+
lines.push(' ' + group.items.map((s) => colorFn(s)).join(chalk.gray(', ')));
|
|
749
792
|
}
|
|
750
793
|
if (targets.length > 0) {
|
|
751
794
|
lines.push('');
|
package/dist/commands/sync.js
CHANGED
package/dist/commands/teams.js
CHANGED
package/dist/commands/trash.d.ts
CHANGED
|
@@ -7,4 +7,15 @@
|
|
|
7
7
|
* `rm -rf ~/.agents/.history/trash/` removes bytes from disk.
|
|
8
8
|
*/
|
|
9
9
|
import type { Command } from 'commander';
|
|
10
|
+
/**
|
|
11
|
+
* Restore a soft-deleted version back into ~/.agents/.history/versions/.
|
|
12
|
+
* Shared by `agents trash restore` and the top-level `agents restore` alias.
|
|
13
|
+
* Exits the process with a non-zero code on any failure.
|
|
14
|
+
*/
|
|
15
|
+
export declare function restoreVersion(target: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Register the top-level `agents restore` command — a shorthand for
|
|
18
|
+
* `agents trash restore` so users can undo a `remove`/`prune` directly.
|
|
19
|
+
*/
|
|
20
|
+
export declare function registerRestoreCommand(program: Command): void;
|
|
10
21
|
export declare function registerTrashCommands(program: Command): void;
|