@phnx-labs/agents-cli 1.18.6 → 1.19.1
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 -2
- package/README.md +22 -20
- package/dist/commands/browser.js +25 -2
- package/dist/commands/cloud.js +3 -3
- package/dist/commands/computer.d.ts +6 -0
- package/dist/commands/computer.js +477 -0
- package/dist/commands/doctor.js +19 -17
- package/dist/commands/exec.js +37 -59
- package/dist/commands/factory.js +12 -5
- package/dist/commands/import.js +6 -1
- package/dist/commands/mcp.js +9 -4
- package/dist/commands/packages.d.ts +3 -0
- package/dist/commands/packages.js +20 -12
- package/dist/commands/permissions.d.ts +2 -0
- package/dist/commands/permissions.js +20 -1
- package/dist/commands/plugins.d.ts +2 -0
- package/dist/commands/plugins.js +23 -4
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/pty.js +126 -112
- package/dist/commands/pull.js +29 -25
- package/dist/commands/repo.js +24 -26
- package/dist/commands/routines.js +29 -26
- package/dist/commands/secrets.js +66 -73
- package/dist/commands/sessions-tail.js +21 -22
- package/dist/commands/sessions.js +36 -68
- package/dist/commands/setup.js +20 -24
- package/dist/commands/teams.js +30 -39
- package/dist/commands/versions.js +60 -68
- package/dist/commands/worktree.d.ts +20 -0
- package/dist/commands/worktree.js +242 -0
- package/dist/computer.d.ts +2 -0
- package/dist/computer.js +7 -0
- package/dist/index.js +70 -26
- package/dist/lib/agents.d.ts +4 -1
- package/dist/lib/agents.js +23 -5
- package/dist/lib/browser/cdp.d.ts +15 -1
- package/dist/lib/browser/cdp.js +77 -8
- package/dist/lib/browser/chrome.js +17 -24
- package/dist/lib/browser/drivers/ssh.d.ts +1 -0
- package/dist/lib/browser/drivers/ssh.js +20 -8
- package/dist/lib/browser/ipc.js +38 -5
- package/dist/lib/browser/profiles.js +34 -2
- package/dist/lib/browser/runtime-state.d.ts +1 -2
- package/dist/lib/browser/runtime-state.js +11 -3
- package/dist/lib/browser/service.d.ts +5 -0
- package/dist/lib/browser/service.js +32 -4
- package/dist/lib/browser/types.d.ts +1 -1
- package/dist/lib/browser/upload.d.ts +2 -0
- package/dist/lib/browser/upload.js +34 -0
- package/dist/lib/cloud/rush.d.ts +2 -1
- package/dist/lib/cloud/rush.js +28 -9
- package/dist/lib/computer-rpc.d.ts +24 -0
- package/dist/lib/computer-rpc.js +263 -0
- package/dist/lib/daemon.js +7 -7
- package/dist/lib/exec.d.ts +2 -1
- package/dist/lib/exec.js +3 -2
- package/dist/lib/fs-atomic.d.ts +18 -0
- package/dist/lib/fs-atomic.js +76 -0
- package/dist/lib/git.js +2 -4
- package/dist/lib/help.d.ts +15 -0
- package/dist/lib/help.js +41 -0
- package/dist/lib/hooks/match.d.ts +1 -0
- package/dist/lib/hooks/match.js +57 -12
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +27 -10
- package/dist/lib/import.d.ts +1 -0
- package/dist/lib/import.js +7 -0
- package/dist/lib/manifest.js +27 -1
- package/dist/lib/mcp.d.ts +14 -0
- package/dist/lib/mcp.js +79 -14
- package/dist/lib/migrate.js +3 -3
- package/dist/lib/models.js +3 -1
- package/dist/lib/permissions.d.ts +5 -0
- package/dist/lib/permissions.js +35 -0
- package/dist/lib/plugin-marketplace.d.ts +3 -1
- package/dist/lib/plugin-marketplace.js +36 -1
- package/dist/lib/plugins.d.ts +19 -1
- package/dist/lib/plugins.js +99 -8
- package/dist/lib/redact.d.ts +4 -0
- package/dist/lib/redact.js +18 -0
- package/dist/lib/registry.d.ts +2 -0
- package/dist/lib/registry.js +15 -0
- package/dist/lib/sandbox.js +15 -5
- package/dist/lib/secrets/bundles.d.ts +7 -12
- package/dist/lib/secrets/bundles.js +45 -29
- package/dist/lib/secrets/index.js +4 -4
- package/dist/lib/session/cloud.d.ts +2 -0
- package/dist/lib/session/cloud.js +34 -6
- package/dist/lib/session/parse.js +7 -2
- package/dist/lib/session/render.d.ts +4 -1
- package/dist/lib/session/render.js +81 -35
- package/dist/lib/shims.d.ts +5 -2
- package/dist/lib/shims.js +29 -7
- package/dist/lib/state.d.ts +5 -5
- package/dist/lib/state.js +43 -13
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/types.d.ts +4 -3
- package/dist/lib/types.js +0 -2
- package/dist/lib/versions.js +65 -40
- package/dist/lib/workflows.d.ts +7 -0
- package/dist/lib/workflows.js +42 -1
- package/npm-shrinkwrap.json +3162 -0
- package/package.json +32 -26
- package/scripts/postinstall.js +8 -2
package/dist/commands/exec.js
CHANGED
|
@@ -8,21 +8,12 @@
|
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import { buildExecCommand, parseExecEnv, execAgent, runWithFallback, AGENT_COMMANDS, } from '../lib/exec.js';
|
|
10
10
|
import { profileExists, resolveProfileForRun } from '../lib/profiles.js';
|
|
11
|
-
import {
|
|
12
|
-
/** Resolve a workflow by name. User repo wins over system repo. Returns the workflow dir or null. */
|
|
13
|
-
function resolveWorkflow(name) {
|
|
14
|
-
for (const base of [getUserAgentsDir(), getSystemAgentsDir()]) {
|
|
15
|
-
const dir = path.join(base, 'workflows', name);
|
|
16
|
-
if (fs.existsSync(path.join(dir, 'WORKFLOW.md')))
|
|
17
|
-
return dir;
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
11
|
+
import { setHelpSections } from '../lib/help.js';
|
|
21
12
|
import { readBundle, resolveBundleEnv, describeBundle } from '../lib/secrets/bundles.js';
|
|
22
13
|
import { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, RUN_STRATEGIES, } from '../lib/rotate.js';
|
|
23
14
|
import { getGlobalDefault, getVersionHomePath, resolveVersionAlias } from '../lib/versions.js';
|
|
24
15
|
import { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion } from '../lib/plugins.js';
|
|
25
|
-
import { parseWorkflowFrontmatter } from '../lib/workflows.js';
|
|
16
|
+
import { parseWorkflowFrontmatter, resolveWorkflowRef } from '../lib/workflows.js';
|
|
26
17
|
import * as fs from 'fs';
|
|
27
18
|
import * as path from 'path';
|
|
28
19
|
const VALID_AGENTS = Object.keys(AGENT_COMMANDS);
|
|
@@ -39,7 +30,7 @@ function formatRotationBanner(result, verb = 'balanced') {
|
|
|
39
30
|
}
|
|
40
31
|
/** Register the `agents run <agent> [prompt]` command. */
|
|
41
32
|
export function registerRunCommand(program) {
|
|
42
|
-
program
|
|
33
|
+
const runCmd = program
|
|
43
34
|
.command('run <agent> [prompt]')
|
|
44
35
|
.description('Execute an agent. Pass a prompt for headless runs; omit it to launch the agent interactively.')
|
|
45
36
|
.option('-m, --mode <mode>', 'How much the agent can do: plan (read-only), edit (can write files), full (writes + all permissions)', 'plan')
|
|
@@ -60,62 +51,50 @@ export function registerRunCommand(program) {
|
|
|
60
51
|
.option('--fallback <agents>', 'Comma-separated agents to try on rate-limit failure. Each entry accepts an optional @version pin (e.g., codex@0.116.0,gemini). The primary runs first; if it exits with a rate-limit error, the next agent picks up via /continue handoff.')
|
|
61
52
|
.option('-b, --balanced', 'Shortcut for --strategy balanced. Ignored when @version is pinned.')
|
|
62
53
|
.option('--strategy <strategy>', 'Version/account selection strategy: pinned | available | balanced. Defaults to run.<agent>.strategy, then pinned. (Legacy `rotate` accepted as alias for `balanced`.)')
|
|
63
|
-
.option('--acp', 'Route through the Agent Client Protocol instead of direct exec. Supported for gemini, claude (via @zed-industries/claude-code-acp adapter). Unified event stream; emits ndjson when --json.')
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Run strategy:
|
|
70
|
-
pinned Use the workspace/global pinned version from agents.yaml.
|
|
71
|
-
available Use the pinned version if it has usage available; otherwise switch
|
|
72
|
-
to another signed-in version with usage available.
|
|
73
|
-
balanced Distribute traffic across healthy accounts using weighted random
|
|
74
|
-
by remaining capacity — fresher accounts get more, near-exhausted
|
|
75
|
-
ones get less. Avoids bursting any single account.
|
|
76
|
-
Configure with run.<agent>.strategy in agents.yaml, or override with
|
|
77
|
-
--strategy. --balanced is kept as a shortcut for --strategy balanced.
|
|
78
|
-
Legacy "rotate" is accepted as an alias for "balanced".
|
|
79
|
-
Ignored when @version is pinned, when a profile is used, or with --fallback.
|
|
80
|
-
|
|
81
|
-
Examples:
|
|
82
|
-
# Interactive with the pinned default version
|
|
83
|
-
agents run claude
|
|
84
|
-
|
|
85
|
-
# Interactive, distribute load across healthy accounts
|
|
86
|
-
agents run claude --strategy balanced
|
|
54
|
+
.option('--acp', 'Route through the Agent Client Protocol instead of direct exec. Supported for gemini, claude (via @zed-industries/claude-code-acp adapter). Unified event stream; emits ndjson when --json.');
|
|
55
|
+
setHelpSections(runCmd, {
|
|
56
|
+
examples: `
|
|
57
|
+
# Headless, read-only: investigate or summarize without writing files
|
|
58
|
+
agents run claude "summarize recent git commits" --mode plan
|
|
87
59
|
|
|
88
|
-
|
|
89
|
-
|
|
60
|
+
# Headless, can edit: have the agent make changes
|
|
61
|
+
agents run claude "fix lint errors in src/" --mode edit
|
|
90
62
|
|
|
91
|
-
|
|
92
|
-
|
|
63
|
+
# Interactive (TUI) with the pinned default version
|
|
64
|
+
agents run claude
|
|
93
65
|
|
|
94
|
-
|
|
95
|
-
|
|
66
|
+
# Pipe JSON events to a parser (--quiet drops the preamble)
|
|
67
|
+
agents run claude "..." --json --quiet | jq
|
|
96
68
|
|
|
97
|
-
|
|
98
|
-
|
|
69
|
+
# Bounded run — kill the agent after 30 minutes
|
|
70
|
+
agents run claude "generate sales report for yesterday" --mode plan --timeout 30m
|
|
99
71
|
|
|
100
|
-
|
|
101
|
-
|
|
72
|
+
# Inject a keychain-backed secrets bundle
|
|
73
|
+
agents run claude "deploy the worker" --secrets prod --mode edit
|
|
74
|
+
`,
|
|
75
|
+
notes: `
|
|
76
|
+
Modes:
|
|
77
|
+
plan read-only edit can write files full writes + all permissions
|
|
102
78
|
|
|
103
|
-
|
|
104
|
-
|
|
79
|
+
Run strategy (set via --strategy or run.<agent>.strategy in agents.yaml):
|
|
80
|
+
pinned use the workspace/global pinned version (default)
|
|
81
|
+
available use pinned if usage available; otherwise switch to another signed-in version
|
|
82
|
+
balanced distribute load across healthy accounts by remaining capacity
|
|
83
|
+
--balanced is shorthand for --strategy balanced. Ignored when @version is pinned, when a profile is used, or with --fallback.
|
|
105
84
|
|
|
106
|
-
|
|
107
|
-
agents run claude "charge a test card" --secrets prod-stripe
|
|
85
|
+
Fallback: --fallback codex,gemini retries on rate-limit failure via /continue handoff. Each entry accepts @version.
|
|
108
86
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
87
|
+
Resume: --session-id <id> continues a prior Claude conversation.
|
|
88
|
+
`,
|
|
89
|
+
});
|
|
90
|
+
runCmd.action(async (agentSpec, prompt, options) => {
|
|
113
91
|
// Parse agent@version
|
|
114
92
|
const [rawAgent, rawVersion] = agentSpec.split('@');
|
|
115
93
|
let agent;
|
|
116
94
|
let version = rawVersion || undefined;
|
|
117
95
|
let profileEnv;
|
|
118
96
|
let fromProfile = false;
|
|
97
|
+
const cwd = options.cwd ?? process.cwd();
|
|
119
98
|
if (isValidAgent(rawAgent)) {
|
|
120
99
|
agent = rawAgent;
|
|
121
100
|
}
|
|
@@ -138,13 +117,13 @@ Examples:
|
|
|
138
117
|
process.exit(1);
|
|
139
118
|
}
|
|
140
119
|
}
|
|
141
|
-
else if (
|
|
142
|
-
// Workflow:
|
|
143
|
-
// Resolution:
|
|
120
|
+
else if (resolveWorkflowRef(rawAgent, cwd)) {
|
|
121
|
+
// Workflow: explicit directory, project .agents/workflows/<name>, user, system, or extra repo.
|
|
122
|
+
// Resolution follows resource precedence: direct path, then project > user > system > extras.
|
|
144
123
|
// Structure:
|
|
145
124
|
// WORKFLOW.md ← orchestrator instructions fed to claude as system prompt
|
|
146
125
|
// subagents/*.md ← flat .md files copied to ~/.claude/agents/ for Agent tool discovery
|
|
147
|
-
const workflowDir =
|
|
126
|
+
const workflowDir = resolveWorkflowRef(rawAgent, cwd);
|
|
148
127
|
agent = 'claude';
|
|
149
128
|
const resolvedVersion = resolveVersionAlias('claude', version);
|
|
150
129
|
const versionHome = getVersionHomePath('claude', resolvedVersion ?? getGlobalDefault('claude') ?? '');
|
|
@@ -221,7 +200,6 @@ Examples:
|
|
|
221
200
|
process.exit(1);
|
|
222
201
|
}
|
|
223
202
|
version = resolveVersionAlias(agent, version);
|
|
224
|
-
const cwd = options.cwd ?? process.cwd();
|
|
225
203
|
const configuredStrategy = getConfiguredRunStrategy(agent, cwd);
|
|
226
204
|
const explicitStrategy = options.strategy ? normalizeRunStrategy(options.strategy) : null;
|
|
227
205
|
if (options.strategy && !explicitStrategy) {
|
package/dist/commands/factory.js
CHANGED
|
@@ -4,11 +4,17 @@ import * as path from 'path';
|
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import { betaEnableHint, isBetaEnabled } from '../lib/beta.js';
|
|
6
6
|
import { insertTask } from '../lib/cloud/store.js';
|
|
7
|
-
const FACTORY_URL = process.env.FACTORY_FLOOR_URL ?? 'https://agents.427yosemite.com';
|
|
8
7
|
function die(msg, code = 1) {
|
|
9
8
|
console.error(chalk.red(msg));
|
|
10
9
|
process.exit(code);
|
|
11
10
|
}
|
|
11
|
+
function requireFactoryUrl() {
|
|
12
|
+
const url = process.env.FACTORY_FLOOR_URL;
|
|
13
|
+
if (!url) {
|
|
14
|
+
die('FACTORY_FLOOR_URL is not set. Point it at your Software Factory endpoint.');
|
|
15
|
+
}
|
|
16
|
+
return url;
|
|
17
|
+
}
|
|
12
18
|
function readRushToken() {
|
|
13
19
|
const userYaml = path.join(homedir(), '.rush', 'user.yaml');
|
|
14
20
|
if (!fs.existsSync(userYaml)) {
|
|
@@ -22,8 +28,9 @@ function readRushToken() {
|
|
|
22
28
|
return match[1].replace(/^['"]|['"]$/g, '');
|
|
23
29
|
}
|
|
24
30
|
async function postFactorySubmit(ref) {
|
|
31
|
+
const factoryUrl = requireFactoryUrl();
|
|
25
32
|
const token = readRushToken();
|
|
26
|
-
const res = await fetch(`${
|
|
33
|
+
const res = await fetch(`${factoryUrl}/factory/submit`, {
|
|
27
34
|
method: 'POST',
|
|
28
35
|
headers: {
|
|
29
36
|
'Authorization': `Bearer ${token}`,
|
|
@@ -44,8 +51,8 @@ export function registerFactoryCommands(program) {
|
|
|
44
51
|
.description('Software Factory -- submit Linear tickets to the cloud orchestrator.')
|
|
45
52
|
.addHelpText('after', `
|
|
46
53
|
Examples:
|
|
47
|
-
agents factory submit
|
|
48
|
-
agents factory submit https://linear.app/
|
|
54
|
+
agents factory submit EXAMPLE-2451
|
|
55
|
+
agents factory submit https://linear.app/example/issue/EXAMPLE-2451
|
|
49
56
|
`);
|
|
50
57
|
factory.hook('preAction', () => {
|
|
51
58
|
if (enabled)
|
|
@@ -56,7 +63,7 @@ Examples:
|
|
|
56
63
|
});
|
|
57
64
|
factory
|
|
58
65
|
.command('submit <linear-ref>')
|
|
59
|
-
.description('Submit a Linear issue (
|
|
66
|
+
.description('Submit a Linear issue (EXAMPLE-123 or URL) to the Software Factory.')
|
|
60
67
|
.option('--json', 'Output machine-readable JSON')
|
|
61
68
|
.action(async (ref, opts) => {
|
|
62
69
|
const result = await postFactorySubmit(ref);
|
package/dist/commands/import.js
CHANGED
|
@@ -28,7 +28,7 @@ import { confirm } from '@inquirer/prompts';
|
|
|
28
28
|
import { ALL_AGENT_IDS } from '../lib/agents.js';
|
|
29
29
|
import { AGENTS, getCliPath, getCliVersion, agentLabel } from '../lib/agents.js';
|
|
30
30
|
import { getVersionDir } from '../lib/versions.js';
|
|
31
|
-
import { finalizeImport, importAgentBinary, importAgentConfig, resolvePackageDirFromBinary, } from '../lib/import.js';
|
|
31
|
+
import { finalizeImport, importAgentBinary, importAgentConfig, isValidImportVersion, resolvePackageDirFromBinary, } from '../lib/import.js';
|
|
32
32
|
import { isPromptCancelled, isInteractiveTerminal } from './utils.js';
|
|
33
33
|
function isValidAgentId(value) {
|
|
34
34
|
return ALL_AGENT_IDS.includes(value);
|
|
@@ -98,6 +98,11 @@ async function runImport(agentArg, opts) {
|
|
|
98
98
|
console.error(chalk.gray('Pass --version <version> explicitly.'));
|
|
99
99
|
process.exit(1);
|
|
100
100
|
}
|
|
101
|
+
if (!isValidImportVersion(version)) {
|
|
102
|
+
console.error(chalk.red(`Invalid version: ${version}`));
|
|
103
|
+
console.error(chalk.gray('Version must be "latest" or 1-64 letters, numbers, dots, underscores, plus signs, or hyphens.'));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
101
106
|
const versionDir = getVersionDir(agentId, version);
|
|
102
107
|
console.log(chalk.bold(`\nImport ${agentLabel(agentId)} v${version}`));
|
|
103
108
|
console.log(` from: ${chalk.gray(globalPath)}`);
|
package/dist/commands/mcp.js
CHANGED
|
@@ -428,7 +428,7 @@ Examples:
|
|
|
428
428
|
});
|
|
429
429
|
mcpCmd
|
|
430
430
|
.command('register [name]')
|
|
431
|
-
.description('Apply MCP servers from manifest to agent config files
|
|
431
|
+
.description('Apply MCP servers from manifest to agent config files')
|
|
432
432
|
.option('-a, --agents <list>', 'Override manifest targets: claude, codex@0.116.0')
|
|
433
433
|
.addHelpText('after', `
|
|
434
434
|
Examples:
|
|
@@ -459,19 +459,24 @@ Examples:
|
|
|
459
459
|
return;
|
|
460
460
|
}
|
|
461
461
|
for (const [mcpName, config] of entries) {
|
|
462
|
-
|
|
463
|
-
|
|
462
|
+
const transport = config.transport || 'stdio';
|
|
463
|
+
const commandOrUrl = transport === 'http' ? config.url : config.command;
|
|
464
|
+
if (!commandOrUrl) {
|
|
465
|
+
console.log(`\n ${chalk.cyan(mcpName)}: ${chalk.yellow(`missing ${transport === 'http' ? 'url' : 'command'}`)}`);
|
|
464
466
|
continue;
|
|
465
467
|
}
|
|
466
468
|
console.log(`\n ${chalk.cyan(mcpName)}:`);
|
|
467
469
|
const targets = options.agents
|
|
468
470
|
? resolveInstalledAgentTargets(options.agents, MCP_CAPABLE_AGENTS)
|
|
469
471
|
: resolveConfiguredAgentTargets(config.agents, config.agentVersions, MCP_CAPABLE_AGENTS);
|
|
470
|
-
const results = await registerMcpToTargets(targets, mcpName,
|
|
472
|
+
const results = await registerMcpToTargets(targets, mcpName, commandOrUrl, config.scope || 'user', transport, { headers: config.headers });
|
|
471
473
|
for (const result of results) {
|
|
472
474
|
if (result.success) {
|
|
473
475
|
console.log(` ${chalk.green('+')} ${formatTargetLabel(result.agentId, result.version)}`);
|
|
474
476
|
}
|
|
477
|
+
else if (result.error?.startsWith('skipped:')) {
|
|
478
|
+
console.log(` ${chalk.yellow('-')} ${formatTargetLabel(result.agentId, result.version)}: ${result.error}`);
|
|
479
|
+
}
|
|
475
480
|
else {
|
|
476
481
|
console.log(` ${chalk.red('x')} ${formatTargetLabel(result.agentId, result.version)}: ${result.error}`);
|
|
477
482
|
}
|
|
@@ -6,5 +6,8 @@
|
|
|
6
6
|
* hooks from configured registries or GitHub sources.
|
|
7
7
|
*/
|
|
8
8
|
import type { Command } from 'commander';
|
|
9
|
+
import type { McpPackage } from '../lib/types.js';
|
|
10
|
+
import { type McpCommandSpec } from '../lib/mcp.js';
|
|
11
|
+
export declare function buildMcpPackageCommand(pkg: McpPackage): McpCommandSpec;
|
|
9
12
|
/** Register the `agents registry`, `agents search`, and `agents install` commands. */
|
|
10
13
|
export declare function registerPackagesCommands(program: Command): void;
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import ora from 'ora';
|
|
10
|
-
import { AGENTS, ALL_AGENT_IDS, MCP_CAPABLE_AGENTS, getAllCliStates,
|
|
10
|
+
import { AGENTS, ALL_AGENT_IDS, MCP_CAPABLE_AGENTS, getAllCliStates, agentLabel, } from '../lib/agents.js';
|
|
11
11
|
import { DEFAULT_REGISTRIES } from '../lib/types.js';
|
|
12
|
-
import { getRegistries, setRegistry, removeRegistry, search as searchRegistries, resolvePackage, } from '../lib/registry.js';
|
|
12
|
+
import { getRegistries, setRegistry, removeRegistry, search as searchRegistries, resolvePackage, validatedNpmSpec, validatedPyPISpec, } from '../lib/registry.js';
|
|
13
13
|
import { cloneRepo } from '../lib/git.js';
|
|
14
14
|
import { discoverCommands, resolveCommandSource, installCommand, installCommandCentrally, } from '../lib/commands.js';
|
|
15
15
|
import { discoverSkillsFromRepo, installSkill, installSkillCentrally, } from '../lib/skills.js';
|
|
@@ -17,6 +17,17 @@ import { discoverHooksFromRepo, installHooks, installHooksCentrally, } from '../
|
|
|
17
17
|
import { listInstalledVersions, resolveInstalledAgentTargets, resolveConfiguredAgentTargets, syncResourcesToVersion, } from '../lib/versions.js';
|
|
18
18
|
import { isInteractiveTerminal, isPromptCancelled, requireDestructiveArg, requireInteractiveSelection, } from './utils.js';
|
|
19
19
|
import { itemPicker } from '../lib/picker.js';
|
|
20
|
+
import { registerMcpCommandToTargets } from '../lib/mcp.js';
|
|
21
|
+
export function buildMcpPackageCommand(pkg) {
|
|
22
|
+
const packageName = pkg.name || pkg.registry_name;
|
|
23
|
+
if (pkg.runtime === 'node') {
|
|
24
|
+
return { command: 'npx', args: ['-y', validatedNpmSpec(packageName)] };
|
|
25
|
+
}
|
|
26
|
+
if (pkg.runtime === 'python') {
|
|
27
|
+
return { command: 'uvx', args: [validatedPyPISpec(packageName)] };
|
|
28
|
+
}
|
|
29
|
+
throw new Error(`Unsupported MCP runtime: ${pkg.runtime}. Supported: node, python.`);
|
|
30
|
+
}
|
|
20
31
|
/**
|
|
21
32
|
* Picker fallback for `registry enable/config [name]`.
|
|
22
33
|
* Returns the picked name, or null if the user cancels. In non-TTY shells,
|
|
@@ -378,16 +389,13 @@ When to use:
|
|
|
378
389
|
console.log(` ${arg.name}${req}: ${arg.description || ''}`);
|
|
379
390
|
}
|
|
380
391
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
command = `npx -y ${pkg.name || pkg.registry_name}`;
|
|
392
|
+
let commandSpec;
|
|
393
|
+
try {
|
|
394
|
+
commandSpec = buildMcpPackageCommand(pkg);
|
|
385
395
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
else {
|
|
390
|
-
command = pkg.name || pkg.registry_name;
|
|
396
|
+
catch (err) {
|
|
397
|
+
console.log(chalk.red(err.message));
|
|
398
|
+
process.exit(1);
|
|
391
399
|
}
|
|
392
400
|
const cliStates = await getAllCliStates();
|
|
393
401
|
const installedAgents = MCP_CAPABLE_AGENTS.filter((id) => cliStates[id]?.installed || listInstalledVersions(id).length > 0);
|
|
@@ -399,7 +407,7 @@ When to use:
|
|
|
399
407
|
process.exit(1);
|
|
400
408
|
}
|
|
401
409
|
console.log(chalk.bold('\nInstalling to agents...'));
|
|
402
|
-
const results = await
|
|
410
|
+
const results = await registerMcpCommandToTargets(targets, entry.name, commandSpec, 'user');
|
|
403
411
|
for (const result of results) {
|
|
404
412
|
const label = result.version ? `${agentLabel(result.agentId)}@${result.version}` : agentLabel(result.agentId);
|
|
405
413
|
if (result.success) {
|
|
@@ -7,5 +7,7 @@
|
|
|
7
7
|
* semantics and multi-version targeting.
|
|
8
8
|
*/
|
|
9
9
|
import type { Command } from 'commander';
|
|
10
|
+
import { discoverPermissionsFromRepo } from '../lib/permissions.js';
|
|
11
|
+
export declare function shouldRefuseBroadPermissions(permissions: ReturnType<typeof discoverPermissionsFromRepo>, allowBroadPermissions: boolean): boolean;
|
|
10
12
|
/** Register the `agents permissions` command tree (list, add, remove, view). */
|
|
11
13
|
export declare function registerPermissionsCommands(program: Command): void;
|
|
@@ -6,10 +6,13 @@ import * as path from 'path';
|
|
|
6
6
|
import { confirm, checkbox } from '@inquirer/prompts';
|
|
7
7
|
import { AGENTS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
|
|
8
8
|
import { cloneRepo } from '../lib/git.js';
|
|
9
|
-
import { PERMISSIONS_CAPABLE_AGENTS, listInstalledPermissions, discoverPermissionsFromRepo, installPermissionSet, removePermissionSet, applyPermissionsToVersion, readAgentPermissions, exportPermissionsFromPath, getDefaultPermissionSet, computePermissionsDiff, mergePermissionSets, saveDefaultPermissionSet, } from '../lib/permissions.js';
|
|
9
|
+
import { PERMISSIONS_CAPABLE_AGENTS, listInstalledPermissions, discoverPermissionsFromRepo, installPermissionSet, removePermissionSet, applyPermissionsToVersion, readAgentPermissions, exportPermissionsFromPath, getDefaultPermissionSet, computePermissionsDiff, mergePermissionSets, saveDefaultPermissionSet, containsBroadGrants, } from '../lib/permissions.js';
|
|
10
10
|
import { listInstalledVersions, getGlobalDefault, getVersionHomePath, promptAgentVersionSelection, resolveAgentVersionTargets, resolveVersionAlias, } from '../lib/versions.js';
|
|
11
11
|
import { recordVersionResources } from '../lib/state.js';
|
|
12
12
|
import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, } from './utils.js';
|
|
13
|
+
export function shouldRefuseBroadPermissions(permissions, allowBroadPermissions) {
|
|
14
|
+
return !allowBroadPermissions && permissions.some((perm) => containsBroadGrants(perm.set) !== null);
|
|
15
|
+
}
|
|
13
16
|
/** Register the `agents permissions` command tree (list, add, remove, view). */
|
|
14
17
|
export function registerPermissionsCommands(program) {
|
|
15
18
|
const permissionsCmd = program
|
|
@@ -201,6 +204,7 @@ When to use:
|
|
|
201
204
|
.option('--names <list>', 'Permission set names from ~/.agents/permissions/ (comma-separated)')
|
|
202
205
|
.option('--all', 'Apply to all installed versions instead of just defaults')
|
|
203
206
|
.option('--replace', 'Replace managed permission block instead of merging (drops rules no longer in the set)')
|
|
207
|
+
.option('--allow-broad-permissions', 'Allow permission packs that significantly weaken sandboxing')
|
|
204
208
|
.option('-y, --yes', 'Skip all prompts and confirmations')
|
|
205
209
|
.addHelpText('after', `
|
|
206
210
|
Examples:
|
|
@@ -489,6 +493,21 @@ Examples:
|
|
|
489
493
|
console.log(` ${chalk.cyan(perm.name)}${desc}`);
|
|
490
494
|
console.log(` ${chalk.gray(`${perm.set.allow.length} allow, ${perm.set.deny?.length || 0} deny rules`)}`);
|
|
491
495
|
}
|
|
496
|
+
const broadGrants = permissions
|
|
497
|
+
.map((perm) => ({ name: perm.name, grants: containsBroadGrants(perm.set) }))
|
|
498
|
+
.filter((entry) => entry.grants !== null);
|
|
499
|
+
if (broadGrants.length > 0) {
|
|
500
|
+
console.error(chalk.yellow('\nWARNING: this permission pack contains broad grants that significantly weaken agent sandboxing:'));
|
|
501
|
+
for (const entry of broadGrants) {
|
|
502
|
+
for (const rule of entry.grants.broad) {
|
|
503
|
+
console.error(` ${entry.name}: ${rule}: ${entry.grants.reason}`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (shouldRefuseBroadPermissions(permissions, options.allowBroadPermissions === true)) {
|
|
507
|
+
console.error(chalk.red('Refusing to install. Re-run with --allow-broad-permissions if you trust this permission pack.'));
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
492
511
|
// Confirm installation
|
|
493
512
|
if (!skipPrompts) {
|
|
494
513
|
const proceed = await confirm({
|
|
@@ -6,5 +6,7 @@
|
|
|
6
6
|
* stored in ~/.agents/plugins/.
|
|
7
7
|
*/
|
|
8
8
|
import type { Command } from 'commander';
|
|
9
|
+
import { type PluginCapabilities } from '../lib/plugins.js';
|
|
10
|
+
export declare function shouldRefusePluginInstall(capabilities: PluginCapabilities, allowExecSurfaces: boolean): boolean;
|
|
9
11
|
/** Register the `agents plugins` command tree. */
|
|
10
12
|
export declare function registerPluginsCommands(program: Command): void;
|
package/dist/commands/plugins.js
CHANGED
|
@@ -10,7 +10,7 @@ import * as path from 'path';
|
|
|
10
10
|
import chalk from 'chalk';
|
|
11
11
|
import { input } from '@inquirer/prompts';
|
|
12
12
|
import { PLUGINS_CAPABLE_AGENTS, agentLabel } from '../lib/agents.js';
|
|
13
|
-
import { discoverPlugins, getPlugin, pluginSupportsAgent, removePluginFromVersion, isPluginSynced, installPlugin, updatePlugin, loadUserConfig, saveUserConfig, checkPluginDependencies, } from '../lib/plugins.js';
|
|
13
|
+
import { discoverPlugins, getPlugin, pluginSupportsAgent, removePluginFromVersion, isPluginSynced, installPlugin, updatePlugin, loadUserConfig, saveUserConfig, checkPluginDependencies, hasPluginExecSurfaces, inspectPluginCapabilities, pluginCapabilityLabels, parseInstallSpec, syncPluginToVersion, } from '../lib/plugins.js';
|
|
14
14
|
import { listInstalledVersions, syncResourcesToVersion, getGlobalDefault, getVersionHomePath, } from '../lib/versions.js';
|
|
15
15
|
import { isPromptCancelled, isInteractiveTerminal, requireDestructiveArg, requireInteractiveSelection, promptRemovalTargets, } from './utils.js';
|
|
16
16
|
import { itemPicker } from '../lib/picker.js';
|
|
@@ -25,6 +25,9 @@ function formatPath(p) {
|
|
|
25
25
|
}
|
|
26
26
|
return p;
|
|
27
27
|
}
|
|
28
|
+
export function shouldRefusePluginInstall(capabilities, allowExecSurfaces) {
|
|
29
|
+
return hasPluginExecSurfaces(capabilities) && !allowExecSurfaces;
|
|
30
|
+
}
|
|
28
31
|
/** Register the `agents plugins` command tree. */
|
|
29
32
|
export function registerPluginsCommands(program) {
|
|
30
33
|
const pluginsCmd = program
|
|
@@ -405,6 +408,7 @@ Examples:
|
|
|
405
408
|
pluginsCmd
|
|
406
409
|
.command('install <spec>')
|
|
407
410
|
.description('Install a plugin from a git URL or local path (format: name@source or source)')
|
|
411
|
+
.option('--allow-exec-surfaces', 'Allow installing plugins that ship executable surfaces')
|
|
408
412
|
.addHelpText('after', `
|
|
409
413
|
Examples:
|
|
410
414
|
# Install from a git URL
|
|
@@ -416,14 +420,16 @@ Examples:
|
|
|
416
420
|
# Named install from a local path
|
|
417
421
|
agents plugins install rush-toolkit@~/Projects/rush-toolkit
|
|
418
422
|
`)
|
|
419
|
-
.action(async (spec) => {
|
|
423
|
+
.action(async (spec, options) => {
|
|
420
424
|
console.log(chalk.gray(`Installing plugin from: ${spec}`));
|
|
421
425
|
let name;
|
|
422
426
|
let root;
|
|
427
|
+
let capabilities;
|
|
423
428
|
try {
|
|
424
429
|
const result = await installPlugin(spec);
|
|
425
430
|
name = result.name;
|
|
426
431
|
root = result.root;
|
|
432
|
+
capabilities = result.capabilities;
|
|
427
433
|
}
|
|
428
434
|
catch (err) {
|
|
429
435
|
console.log(chalk.red(`Install failed: ${err.message}`));
|
|
@@ -434,6 +440,17 @@ Examples:
|
|
|
434
440
|
console.log(chalk.red(`Installed but could not load plugin '${name}'`));
|
|
435
441
|
process.exit(1);
|
|
436
442
|
}
|
|
443
|
+
capabilities = inspectPluginCapabilities(root);
|
|
444
|
+
if (shouldRefusePluginInstall(capabilities, options.allowExecSurfaces === true)) {
|
|
445
|
+
const source = parseInstallSpec(spec).source;
|
|
446
|
+
console.error(chalk.red('Install refused: plugin ships executable surfaces:'));
|
|
447
|
+
for (const label of pluginCapabilityLabels(capabilities)) {
|
|
448
|
+
console.error(` ${label}`);
|
|
449
|
+
}
|
|
450
|
+
console.error(`This plugin ships executable surfaces. Re-run with --allow-exec-surfaces if you trust the source: ${source}@HEAD`);
|
|
451
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
437
454
|
// Check dependencies
|
|
438
455
|
const missingDeps = checkPluginDependencies(plugin.manifest);
|
|
439
456
|
if (missingDeps.length > 0) {
|
|
@@ -461,8 +478,10 @@ Examples:
|
|
|
461
478
|
const defaultVer = getGlobalDefault(agentId);
|
|
462
479
|
const targetVersions = defaultVer ? [defaultVer] : [versions[versions.length - 1]];
|
|
463
480
|
for (const version of targetVersions) {
|
|
464
|
-
const
|
|
465
|
-
|
|
481
|
+
const didSync = options.allowExecSurfaces === true
|
|
482
|
+
? syncPluginToVersion(plugin, agentId, getVersionHomePath(agentId, version), { allowExecSurfaces: true }).success
|
|
483
|
+
: syncResourcesToVersion(agentId, version, { plugins: [name] }).plugins.length > 0;
|
|
484
|
+
if (didSync) {
|
|
466
485
|
console.log(chalk.green(` Synced to ${agentLabel(agentId)}@${version}`));
|
|
467
486
|
synced++;
|
|
468
487
|
}
|
|
@@ -116,7 +116,7 @@ Examples:
|
|
|
116
116
|
|
|
117
117
|
# Add MiniMax for SWE-bench style fixes; reuses the same OpenRouter key
|
|
118
118
|
agents profiles add minimax
|
|
119
|
-
agents run minimax "investigate
|
|
119
|
+
agents run minimax "investigate EXAMPLE-2317 and patch the off-by-one in pagination"
|
|
120
120
|
|
|
121
121
|
# Add DeepSeek for cheap, fast non-reasoning work
|
|
122
122
|
agents profiles add deepseek
|