@phnx-labs/agents-cli 1.15.0 → 1.17.0
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 +143 -39
- package/README.md +6 -6
- package/dist/commands/alias.js +2 -2
- package/dist/commands/browser-picker.d.ts +21 -0
- package/dist/commands/browser-picker.js +114 -0
- package/dist/commands/browser.js +793 -83
- package/dist/commands/cloud.js +8 -0
- package/dist/commands/commands.js +72 -22
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/exec.js +70 -1
- package/dist/commands/hooks.js +71 -26
- package/dist/commands/mcp.js +81 -39
- package/dist/commands/plugins.js +224 -17
- package/dist/commands/prune.js +29 -1
- package/dist/commands/pull.js +3 -3
- package/dist/commands/repo.js +1 -1
- package/dist/commands/routines.js +2 -2
- package/dist/commands/secrets.js +154 -20
- package/dist/commands/sessions.js +62 -19
- package/dist/commands/{init.d.ts → setup.d.ts} +7 -6
- package/dist/commands/{init.js → setup.js} +22 -21
- package/dist/commands/skills.js +60 -19
- package/dist/commands/subagents.js +41 -13
- package/dist/commands/utils.d.ts +16 -0
- package/dist/commands/utils.js +32 -0
- package/dist/commands/view.js +78 -20
- package/dist/commands/workflows.d.ts +10 -0
- package/dist/commands/workflows.js +457 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +48 -36
- package/dist/lib/agents.js +2 -2
- package/dist/lib/auto-pull-worker.js +2 -3
- package/dist/lib/auto-pull.js +2 -2
- package/dist/lib/browser/cdp.d.ts +7 -1
- package/dist/lib/browser/cdp.js +32 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +41 -3
- package/dist/lib/browser/devices.d.ts +4 -0
- package/dist/lib/browser/devices.js +27 -0
- package/dist/lib/browser/drivers/local.js +22 -6
- package/dist/lib/browser/drivers/ssh.js +9 -2
- package/dist/lib/browser/input.d.ts +1 -0
- package/dist/lib/browser/input.js +3 -0
- package/dist/lib/browser/ipc.js +158 -23
- package/dist/lib/browser/profiles.d.ts +10 -2
- package/dist/lib/browser/profiles.js +122 -37
- package/dist/lib/browser/service.d.ts +91 -13
- package/dist/lib/browser/service.js +767 -132
- package/dist/lib/browser/types.d.ts +91 -3
- package/dist/lib/browser/types.js +16 -0
- package/dist/lib/cloud/rush.d.ts +28 -1
- package/dist/lib/cloud/rush.js +69 -14
- package/dist/lib/cloud/store.js +2 -2
- package/dist/lib/commands.d.ts +1 -15
- package/dist/lib/commands.js +11 -7
- package/dist/lib/daemon.js +2 -3
- package/dist/lib/doctor-diff.js +4 -4
- package/dist/lib/events.js +2 -2
- package/dist/lib/hooks.d.ts +11 -7
- package/dist/lib/hooks.js +138 -49
- package/dist/lib/migrate.d.ts +1 -1
- package/dist/lib/migrate.js +1237 -22
- package/dist/lib/models.js +2 -2
- package/dist/lib/permissions.d.ts +8 -66
- package/dist/lib/permissions.js +18 -18
- package/dist/lib/plugins.d.ts +94 -24
- package/dist/lib/plugins.js +702 -123
- package/dist/lib/pty-server.js +9 -10
- package/dist/lib/resource-patterns.d.ts +41 -0
- package/dist/lib/resource-patterns.js +82 -0
- package/dist/lib/resources/hooks.d.ts +5 -1
- package/dist/lib/resources/hooks.js +21 -4
- package/dist/lib/resources/index.d.ts +17 -0
- package/dist/lib/resources/index.js +7 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources/workflows.d.ts +24 -0
- package/dist/lib/resources/workflows.js +110 -0
- package/dist/lib/resources.d.ts +6 -1
- package/dist/lib/resources.js +12 -2
- package/dist/lib/rotate.js +3 -4
- package/dist/lib/session/active.d.ts +3 -0
- package/dist/lib/session/active.js +92 -6
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +18 -0
- package/dist/lib/session/db.js +109 -5
- package/dist/lib/session/discover.d.ts +6 -0
- package/dist/lib/session/discover.js +55 -29
- package/dist/lib/session/team-filter.js +2 -2
- package/dist/lib/shims.d.ts +4 -52
- package/dist/lib/shims.js +23 -15
- package/dist/lib/skills.js +6 -2
- package/dist/lib/sqlite.js +10 -4
- package/dist/lib/state.d.ts +101 -16
- package/dist/lib/state.js +179 -31
- package/dist/lib/subagents.d.ts +28 -0
- package/dist/lib/subagents.js +98 -1
- package/dist/lib/sync-manifest.d.ts +1 -1
- package/dist/lib/sync-manifest.js +3 -3
- package/dist/lib/teams/persistence.js +15 -5
- package/dist/lib/teams/registry.js +2 -2
- package/dist/lib/types.d.ts +75 -17
- package/dist/lib/types.js +3 -3
- package/dist/lib/usage.js +2 -2
- package/dist/lib/versions.d.ts +3 -0
- package/dist/lib/versions.js +158 -47
- package/dist/lib/workflows.d.ts +79 -0
- package/dist/lib/workflows.js +233 -0
- package/package.json +1 -5
- package/scripts/postinstall.js +60 -59
- package/dist/commands/fork.d.ts +0 -10
- package/dist/commands/fork.js +0 -146
package/dist/commands/cloud.js
CHANGED
|
@@ -106,6 +106,9 @@ Examples:
|
|
|
106
106
|
.option('--env <id>', 'Codex Cloud environment ID')
|
|
107
107
|
.option('--computer <name>', 'Factory/Droid computer target')
|
|
108
108
|
.option('--mode <mode>', 'Execution mode (e.g., plan, edit, full)')
|
|
109
|
+
.option('-b, --balanced', 'Shortcut for --strategy balanced. Route the factory run across all healthy accounts.')
|
|
110
|
+
.option('--strategy <strategy>', 'Account selection strategy for the factory: balanced. Sends all healthy accounts so the factory pod rotates between them on rate-limit.')
|
|
111
|
+
.option('--upload-account-tokens', 'Upload Claude OAuth credentials to Rush Cloud on first dispatch (consent recorded for future runs).')
|
|
109
112
|
.option('--json', 'Structured JSON output')
|
|
110
113
|
.option('--no-follow', 'Dispatch and exit without streaming output')
|
|
111
114
|
.addHelpText('after', `
|
|
@@ -165,6 +168,11 @@ Examples:
|
|
|
165
168
|
dispatchOptions.providerOptions.computer = options.computer;
|
|
166
169
|
if (options.mode)
|
|
167
170
|
dispatchOptions.providerOptions.mode = options.mode;
|
|
171
|
+
if (options.balanced || options.strategy === 'balanced') {
|
|
172
|
+
dispatchOptions.providerOptions.strategy = 'balanced';
|
|
173
|
+
}
|
|
174
|
+
if (options.uploadAccountTokens)
|
|
175
|
+
dispatchOptions.providerOptions.uploadAccountTokens = true;
|
|
168
176
|
// Dispatch
|
|
169
177
|
const spinner = ora({ text: `Dispatching to ${provider.name}...`, stream: process.stderr }).start();
|
|
170
178
|
let task;
|
|
@@ -4,14 +4,14 @@ import * as fs from 'fs';
|
|
|
4
4
|
import * as os from 'os';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { checkbox } from '@inquirer/prompts';
|
|
7
|
-
import { ALL_AGENT_IDS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
|
|
7
|
+
import { AGENTS, ALL_AGENT_IDS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
|
|
8
8
|
import { cloneRepo } from '../lib/git.js';
|
|
9
|
-
import { discoverCommands, resolveCommandSource, installCommandCentrally,
|
|
9
|
+
import { discoverCommands, resolveCommandSource, installCommandCentrally, listCentralCommands, listInstalledCommandsWithScope, getCommandInfo, diffVersionCommands, iterCommandsCapableVersions, removeCommandFromVersion, } from '../lib/commands.js';
|
|
10
10
|
import { getCommandsDir } from '../lib/state.js';
|
|
11
11
|
import { showResourceList, buildTargetsSection, } from './resource-view.js';
|
|
12
|
-
import { getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, resolveAgentVersionTargets, } from '../lib/versions.js';
|
|
12
|
+
import { getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, resolveAgentVersionTargets, } from '../lib/versions.js';
|
|
13
13
|
import { recordVersionResources } from '../lib/state.js';
|
|
14
|
-
import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, } from './utils.js';
|
|
14
|
+
import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, promptRemovalTargets, } from './utils.js';
|
|
15
15
|
/** Register the `agents commands` command tree (list, add, remove, sync, prune, view). */
|
|
16
16
|
export function registerCommandsCommands(program) {
|
|
17
17
|
const commandsCmd = program
|
|
@@ -271,15 +271,29 @@ Examples:
|
|
|
271
271
|
agents commands remove
|
|
272
272
|
`)
|
|
273
273
|
.action(async (name, options) => {
|
|
274
|
+
const cmdTargetMap = new Map();
|
|
275
|
+
for (const { agent, version } of iterCommandsCapableVersions()) {
|
|
276
|
+
const home = getVersionHomePath(agent, version);
|
|
277
|
+
const commands = listInstalledCommandsWithScope(agent, process.cwd(), { home });
|
|
278
|
+
for (const cmd of commands) {
|
|
279
|
+
if (cmd.scope !== 'user')
|
|
280
|
+
continue;
|
|
281
|
+
const existing = cmdTargetMap.get(cmd.name);
|
|
282
|
+
if (existing) {
|
|
283
|
+
existing.targets.push({ agent, version });
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
cmdTargetMap.set(cmd.name, { name: cmd.name, targets: [{ agent, version }] });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
274
290
|
let commandsToRemove;
|
|
275
291
|
if (name) {
|
|
276
292
|
commandsToRemove = [name];
|
|
277
293
|
}
|
|
278
294
|
else {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (centralCommands.length === 0) {
|
|
282
|
-
console.log(chalk.yellow('No commands installed.'));
|
|
295
|
+
if (cmdTargetMap.size === 0) {
|
|
296
|
+
console.log(chalk.yellow('No commands installed in any version.'));
|
|
283
297
|
return;
|
|
284
298
|
}
|
|
285
299
|
if (!isInteractiveTerminal()) {
|
|
@@ -288,12 +302,16 @@ Examples:
|
|
|
288
302
|
]);
|
|
289
303
|
}
|
|
290
304
|
try {
|
|
305
|
+
const choices = Array.from(cmdTargetMap.values()).map((cmd) => {
|
|
306
|
+
const agents = [...new Set(cmd.targets.map((t) => AGENTS[t.agent].name))];
|
|
307
|
+
return {
|
|
308
|
+
value: cmd.name,
|
|
309
|
+
name: `${cmd.name} (${agents.join(', ')})`,
|
|
310
|
+
};
|
|
311
|
+
});
|
|
291
312
|
const selected = await checkbox({
|
|
292
313
|
message: 'Select commands to remove',
|
|
293
|
-
choices
|
|
294
|
-
value: cmd,
|
|
295
|
-
name: cmd,
|
|
296
|
-
})),
|
|
314
|
+
choices,
|
|
297
315
|
});
|
|
298
316
|
if (selected.length === 0) {
|
|
299
317
|
console.log(chalk.gray('No commands selected.'));
|
|
@@ -309,20 +327,52 @@ Examples:
|
|
|
309
327
|
throw err;
|
|
310
328
|
}
|
|
311
329
|
}
|
|
312
|
-
|
|
313
|
-
? options.agents.split(',')
|
|
314
|
-
: ALL_AGENT_IDS;
|
|
330
|
+
let removed = 0;
|
|
315
331
|
for (const cmdName of commandsToRemove) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
332
|
+
const cmdInfo = cmdTargetMap.get(cmdName);
|
|
333
|
+
if (!cmdInfo || cmdInfo.targets.length === 0) {
|
|
334
|
+
console.log(chalk.yellow(` Command '${cmdName}' not found in any version.`));
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
// Filter by --agents if specified
|
|
338
|
+
let availableTargets = cmdInfo.targets;
|
|
339
|
+
if (options?.agents) {
|
|
340
|
+
const requestedAgents = new Set(options.agents.split(','));
|
|
341
|
+
availableTargets = availableTargets.filter((t) => requestedAgents.has(t.agent));
|
|
342
|
+
}
|
|
343
|
+
if (availableTargets.length === 0) {
|
|
344
|
+
console.log(chalk.yellow(` Command '${cmdName}' not found in specified agents.`));
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const removalTargets = availableTargets.map((t) => ({
|
|
348
|
+
agent: t.agent,
|
|
349
|
+
version: t.version,
|
|
350
|
+
label: `${agentLabel(t.agent)}@${t.version}`,
|
|
351
|
+
}));
|
|
352
|
+
const selectedTargets = await promptRemovalTargets(cmdName, removalTargets, {
|
|
353
|
+
skipPrompt: !!options?.agents,
|
|
354
|
+
});
|
|
355
|
+
if (selectedTargets.length === 0) {
|
|
356
|
+
console.log(chalk.gray(` Skipped '${cmdName}'.`));
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
for (const target of selectedTargets) {
|
|
360
|
+
const result = removeCommandFromVersion(target.agent, target.version, cmdName);
|
|
361
|
+
if (result.success) {
|
|
362
|
+
console.log(` ${chalk.red('-')} ${target.label}: ${cmdName}`);
|
|
320
363
|
removed++;
|
|
321
364
|
}
|
|
365
|
+
else if (result.error) {
|
|
366
|
+
console.log(` ${chalk.yellow('!')} ${target.label}: ${result.error}`);
|
|
367
|
+
}
|
|
322
368
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
369
|
+
}
|
|
370
|
+
if (removed === 0) {
|
|
371
|
+
console.log(chalk.yellow('No commands removed.'));
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
console.log(chalk.green(`\nRemoved ${removed} command(s) from version homes.`));
|
|
375
|
+
console.log(chalk.gray('Central source unchanged. Commands will re-sync on next agent launch.'));
|
|
326
376
|
}
|
|
327
377
|
});
|
|
328
378
|
// `commands sync` is gone — sync runs automatically when the agent launches.
|
package/dist/commands/daemon.js
CHANGED
|
@@ -91,8 +91,8 @@ you never need to start it manually.
|
|
|
91
91
|
.action(async (options) => {
|
|
92
92
|
warnDeprecated('logs', 'agents routines scheduler-logs');
|
|
93
93
|
if (options.follow) {
|
|
94
|
-
const {
|
|
95
|
-
const logPath = path.join(
|
|
94
|
+
const { getDaemonDir } = await import('../lib/state.js');
|
|
95
|
+
const logPath = path.join(getDaemonDir(), 'logs.jsonl');
|
|
96
96
|
const child = spawn('tail', ['-f', logPath], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
97
97
|
child.stdout.pipe(process.stdout);
|
|
98
98
|
child.stderr.pipe(process.stderr);
|
package/dist/commands/exec.js
CHANGED
|
@@ -8,9 +8,22 @@
|
|
|
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 { getSystemAgentsDir, getUserAgentsDir } from '../lib/state.js';
|
|
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
21
|
import { readBundle, resolveBundleEnv, describeBundle } from '../lib/secrets/bundles.js';
|
|
12
22
|
import { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, RUN_STRATEGIES, } from '../lib/rotate.js';
|
|
13
|
-
import { resolveVersionAlias } from '../lib/versions.js';
|
|
23
|
+
import { getGlobalDefault, getVersionHomePath, resolveVersionAlias } from '../lib/versions.js';
|
|
24
|
+
import { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion } from '../lib/plugins.js';
|
|
25
|
+
import * as fs from 'fs';
|
|
26
|
+
import * as path from 'path';
|
|
14
27
|
const VALID_AGENTS = Object.keys(AGENT_COMMANDS);
|
|
15
28
|
/** Type guard that narrows a string to a known AgentId. */
|
|
16
29
|
function isValidAgent(agent) {
|
|
@@ -122,6 +135,62 @@ Examples:
|
|
|
122
135
|
process.exit(1);
|
|
123
136
|
}
|
|
124
137
|
}
|
|
138
|
+
else if (resolveWorkflow(rawAgent)) {
|
|
139
|
+
// Workflow: ~/.agents-system/workflows/<name>/ or ~/.agents/workflows/<name>/
|
|
140
|
+
// Resolution: user repo wins over system repo (same precedence as all resources).
|
|
141
|
+
// Structure:
|
|
142
|
+
// WORKFLOW.md ← orchestrator instructions fed to claude as system prompt
|
|
143
|
+
// subagents/*.md ← flat .md files copied to ~/.claude/agents/ for Agent tool discovery
|
|
144
|
+
const workflowDir = resolveWorkflow(rawAgent);
|
|
145
|
+
agent = 'claude';
|
|
146
|
+
const resolvedVersion = resolveVersionAlias('claude', version);
|
|
147
|
+
const versionHome = getVersionHomePath('claude', resolvedVersion ?? getGlobalDefault('claude') ?? '');
|
|
148
|
+
const claudeAgentsDir = path.join(versionHome, '.claude', 'agents');
|
|
149
|
+
// Copy subagents/*.md into ~/.claude/agents/ so Claude's Agent tool finds them.
|
|
150
|
+
const subagentsDir = path.join(workflowDir, 'subagents');
|
|
151
|
+
if (fs.existsSync(subagentsDir)) {
|
|
152
|
+
fs.mkdirSync(claudeAgentsDir, { recursive: true });
|
|
153
|
+
for (const file of fs.readdirSync(subagentsDir).filter(f => f.endsWith('.md'))) {
|
|
154
|
+
fs.copyFileSync(path.join(subagentsDir, file), path.join(claudeAgentsDir, file));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Feed WORKFLOW.md body (strip frontmatter) as orchestrator system context.
|
|
158
|
+
const workflowMd = path.join(workflowDir, 'WORKFLOW.md');
|
|
159
|
+
const orchestratorBody = fs.existsSync(workflowMd)
|
|
160
|
+
? fs.readFileSync(workflowMd, 'utf-8').replace(/^---[\s\S]*?---\n/, '').trim()
|
|
161
|
+
: '';
|
|
162
|
+
if (orchestratorBody && prompt !== undefined) {
|
|
163
|
+
prompt = `${orchestratorBody}\n\n---\n\n${prompt}`;
|
|
164
|
+
}
|
|
165
|
+
// Sync workflow-scoped skills into the version home's skills dir.
|
|
166
|
+
const workflowSkillsDir = path.join(workflowDir, 'skills');
|
|
167
|
+
if (fs.existsSync(workflowSkillsDir)) {
|
|
168
|
+
const skillsTarget = path.join(claudeAgentsDir, '..', 'skills');
|
|
169
|
+
fs.mkdirSync(skillsTarget, { recursive: true });
|
|
170
|
+
for (const entry of fs.readdirSync(workflowSkillsDir, { withFileTypes: true })) {
|
|
171
|
+
if (!entry.isDirectory())
|
|
172
|
+
continue;
|
|
173
|
+
fs.cpSync(path.join(workflowSkillsDir, entry.name), path.join(skillsTarget, entry.name), { recursive: true });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Sync workflow-scoped plugins into the version home.
|
|
177
|
+
const workflowPluginsDir = path.join(workflowDir, 'plugins');
|
|
178
|
+
if (fs.existsSync(workflowPluginsDir)) {
|
|
179
|
+
for (const entry of fs.readdirSync(workflowPluginsDir, { withFileTypes: true })) {
|
|
180
|
+
if (!entry.isDirectory())
|
|
181
|
+
continue;
|
|
182
|
+
const pluginRoot = path.join(workflowPluginsDir, entry.name);
|
|
183
|
+
const manifest = loadPluginManifest(pluginRoot);
|
|
184
|
+
if (!manifest)
|
|
185
|
+
continue;
|
|
186
|
+
syncPluginToVersion(buildDiscoveredPlugin(pluginRoot, manifest), 'claude', versionHome);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const subagentCount = fs.existsSync(subagentsDir)
|
|
190
|
+
? fs.readdirSync(subagentsDir).filter(f => f.endsWith('.md')).length
|
|
191
|
+
: 0;
|
|
192
|
+
process.stderr.write(chalk.gray(`Workflow '${rawAgent}' → claude (${subagentCount} subagents)\n`));
|
|
193
|
+
}
|
|
125
194
|
else {
|
|
126
195
|
console.error(chalk.red(`Unknown agent: ${rawAgent}`));
|
|
127
196
|
console.error(chalk.gray(`Available agents: ${VALID_AGENTS.join(', ')}`));
|
package/dist/commands/hooks.js
CHANGED
|
@@ -7,10 +7,10 @@ import { checkbox } from '@inquirer/prompts';
|
|
|
7
7
|
import { AGENTS, HOOKS_CAPABLE_AGENTS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
|
|
8
8
|
import { supports } from '../lib/capabilities.js';
|
|
9
9
|
import { cloneRepo } from '../lib/git.js';
|
|
10
|
-
import { discoverHooksFromRepo, installHooksCentrally, listCentralHooks, listInstalledHooksWithScope,
|
|
10
|
+
import { discoverHooksFromRepo, installHooksCentrally, listCentralHooks, listInstalledHooksWithScope, getHookInfo, parseHookManifest, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks.js';
|
|
11
11
|
import { listInstalledVersions, getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, resolveAgentVersionTargets, } from '../lib/versions.js';
|
|
12
12
|
import { recordVersionResources } from '../lib/state.js';
|
|
13
|
-
import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, } from './utils.js';
|
|
13
|
+
import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, promptRemovalTargets, } from './utils.js';
|
|
14
14
|
/** Register the `agents hooks` command tree (list, add, remove, sync, prune, view). */
|
|
15
15
|
export function registerHooksCommands(program) {
|
|
16
16
|
const hooksCmd = program.command('hooks')
|
|
@@ -396,15 +396,29 @@ Examples:
|
|
|
396
396
|
agents hooks remove
|
|
397
397
|
`)
|
|
398
398
|
.action(async (name, options) => {
|
|
399
|
+
const hookTargetMap = new Map();
|
|
400
|
+
for (const { agent, version } of iterHooksCapableVersions()) {
|
|
401
|
+
const home = getVersionHomePath(agent, version);
|
|
402
|
+
const hooks = listInstalledHooksWithScope(agent, process.cwd(), { home });
|
|
403
|
+
for (const hook of hooks) {
|
|
404
|
+
if (hook.scope !== 'user')
|
|
405
|
+
continue;
|
|
406
|
+
const existing = hookTargetMap.get(hook.name);
|
|
407
|
+
if (existing) {
|
|
408
|
+
existing.targets.push({ agent, version });
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
hookTargetMap.set(hook.name, { name: hook.name, targets: [{ agent, version }] });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
399
415
|
let hooksToRemove;
|
|
400
416
|
if (name) {
|
|
401
417
|
hooksToRemove = [name];
|
|
402
418
|
}
|
|
403
419
|
else {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if (centralHooks.length === 0) {
|
|
407
|
-
console.log(chalk.yellow('No hooks installed.'));
|
|
420
|
+
if (hookTargetMap.size === 0) {
|
|
421
|
+
console.log(chalk.yellow('No hooks installed in any version.'));
|
|
408
422
|
return;
|
|
409
423
|
}
|
|
410
424
|
if (!isInteractiveTerminal()) {
|
|
@@ -413,12 +427,16 @@ Examples:
|
|
|
413
427
|
]);
|
|
414
428
|
}
|
|
415
429
|
try {
|
|
430
|
+
const choices = Array.from(hookTargetMap.values()).map((hook) => {
|
|
431
|
+
const agents = [...new Set(hook.targets.map((t) => AGENTS[t.agent].name))];
|
|
432
|
+
return {
|
|
433
|
+
value: hook.name,
|
|
434
|
+
name: `${hook.name} (${agents.join(', ')})`,
|
|
435
|
+
};
|
|
436
|
+
});
|
|
416
437
|
const selected = await checkbox({
|
|
417
438
|
message: 'Select hooks to remove',
|
|
418
|
-
choices
|
|
419
|
-
value: hook.name,
|
|
420
|
-
name: hook.name,
|
|
421
|
-
})),
|
|
439
|
+
choices,
|
|
422
440
|
});
|
|
423
441
|
if (selected.length === 0) {
|
|
424
442
|
console.log(chalk.gray('No hooks selected.'));
|
|
@@ -434,26 +452,53 @@ Examples:
|
|
|
434
452
|
throw err;
|
|
435
453
|
}
|
|
436
454
|
}
|
|
437
|
-
|
|
438
|
-
? options.agents.split(',')
|
|
439
|
-
: Array.from(HOOKS_CAPABLE_AGENTS);
|
|
455
|
+
let removed = 0;
|
|
440
456
|
for (const hookName of hooksToRemove) {
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
457
|
+
const hookInfo = hookTargetMap.get(hookName);
|
|
458
|
+
if (!hookInfo || hookInfo.targets.length === 0) {
|
|
459
|
+
console.log(chalk.yellow(` Hook '${hookName}' not found in any version.`));
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
// Filter by --agents if specified
|
|
463
|
+
let availableTargets = hookInfo.targets;
|
|
464
|
+
if (options?.agents) {
|
|
465
|
+
const requestedAgents = new Set(options.agents.split(','));
|
|
466
|
+
availableTargets = availableTargets.filter((t) => requestedAgents.has(t.agent));
|
|
467
|
+
}
|
|
468
|
+
if (availableTargets.length === 0) {
|
|
469
|
+
console.log(chalk.yellow(` Hook '${hookName}' not found in specified agents.`));
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const removalTargets = availableTargets.map((t) => ({
|
|
473
|
+
agent: t.agent,
|
|
474
|
+
version: t.version,
|
|
475
|
+
label: `${agentLabel(t.agent)}@${t.version}`,
|
|
476
|
+
}));
|
|
477
|
+
const selectedTargets = await promptRemovalTargets(hookName, removalTargets, {
|
|
478
|
+
skipPrompt: !!options?.agents,
|
|
479
|
+
});
|
|
480
|
+
if (selectedTargets.length === 0) {
|
|
481
|
+
console.log(chalk.gray(` Skipped '${hookName}'.`));
|
|
482
|
+
continue;
|
|
452
483
|
}
|
|
453
|
-
|
|
454
|
-
|
|
484
|
+
for (const target of selectedTargets) {
|
|
485
|
+
const result = removeHookFromVersion(target.agent, target.version, hookName);
|
|
486
|
+
if (result.success) {
|
|
487
|
+
console.log(` ${chalk.red('-')} ${target.label}: ${hookName}`);
|
|
488
|
+
removed++;
|
|
489
|
+
}
|
|
490
|
+
else if (result.error) {
|
|
491
|
+
console.log(` ${chalk.yellow('!')} ${target.label}: ${result.error}`);
|
|
492
|
+
}
|
|
455
493
|
}
|
|
456
494
|
}
|
|
495
|
+
if (removed === 0) {
|
|
496
|
+
console.log(chalk.yellow('No hooks removed.'));
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
console.log(chalk.green(`\nRemoved ${removed} hook(s) from version homes.`));
|
|
500
|
+
console.log(chalk.gray('Central source unchanged. Hooks will re-sync on next agent launch.'));
|
|
501
|
+
}
|
|
457
502
|
});
|
|
458
503
|
// `hooks sync` is gone — sync runs automatically when the agent launches.
|
|
459
504
|
hooksCmd
|
package/dist/commands/mcp.js
CHANGED
|
@@ -7,7 +7,7 @@ import { listMcpServerConfigs } from '../lib/mcp.js';
|
|
|
7
7
|
import { getMcpDir } from '../lib/state.js';
|
|
8
8
|
import { getEffectiveHome, getGlobalDefault, listInstalledVersions, getVersionHomePath, resolveInstalledAgentTargets, resolveConfiguredAgentTargets, resolveVersionAlias, } from '../lib/versions.js';
|
|
9
9
|
import { getUserAgentsDir } from '../lib/state.js';
|
|
10
|
-
import { isPromptCancelled, isInteractiveTerminal, requireInteractiveSelection } from './utils.js';
|
|
10
|
+
import { isPromptCancelled, isInteractiveTerminal, requireInteractiveSelection, promptRemovalTargets } from './utils.js';
|
|
11
11
|
import { showResourceList, buildTargetsSection, } from './resource-view.js';
|
|
12
12
|
/** Parse a comma-separated --agents string into validated agent IDs and optional version targets. */
|
|
13
13
|
function parseMcpAgentTargets(value) {
|
|
@@ -219,20 +219,33 @@ Examples:
|
|
|
219
219
|
.action(async (name, options) => {
|
|
220
220
|
const cwd = process.cwd();
|
|
221
221
|
const cliStates = await getAllCliStates();
|
|
222
|
+
const mcpTargetMap = new Map();
|
|
223
|
+
for (const agentId of MCP_CAPABLE_AGENTS) {
|
|
224
|
+
if (!cliStates[agentId]?.installed && listInstalledVersions(agentId).length === 0)
|
|
225
|
+
continue;
|
|
226
|
+
for (const version of listInstalledVersions(agentId)) {
|
|
227
|
+
const home = getVersionHomePath(agentId, version);
|
|
228
|
+
const configPath = getMcpConfigPathForHome(agentId, home);
|
|
229
|
+
const mcps = parseMcpConfig(agentId, configPath);
|
|
230
|
+
for (const mcpName of Object.keys(mcps)) {
|
|
231
|
+
const existing = mcpTargetMap.get(mcpName);
|
|
232
|
+
if (existing) {
|
|
233
|
+
existing.targets.push({ agentId, version, home });
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
mcpTargetMap.set(mcpName, { name: mcpName, targets: [{ agentId, version, home }] });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
222
241
|
let mcpsToRemove;
|
|
223
|
-
let targets;
|
|
224
242
|
if (name) {
|
|
225
243
|
mcpsToRemove = [name];
|
|
226
|
-
const installedAgents = MCP_CAPABLE_AGENTS.filter((agentId) => cliStates[agentId]?.installed || listInstalledVersions(agentId).length > 0);
|
|
227
|
-
targets = options?.agents
|
|
228
|
-
? resolveInstalledAgentTargets(options.agents, MCP_CAPABLE_AGENTS)
|
|
229
|
-
: resolveConfiguredAgentTargets(installedAgents, undefined, MCP_CAPABLE_AGENTS);
|
|
230
244
|
}
|
|
231
245
|
else {
|
|
232
|
-
// Interactive picker
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
console.log(chalk.yellow('No MCP-capable agents installed.'));
|
|
246
|
+
// Interactive picker for MCP selection
|
|
247
|
+
if (mcpTargetMap.size === 0) {
|
|
248
|
+
console.log(chalk.yellow('No MCP servers configured.'));
|
|
236
249
|
return;
|
|
237
250
|
}
|
|
238
251
|
if (!isInteractiveTerminal()) {
|
|
@@ -241,42 +254,22 @@ Examples:
|
|
|
241
254
|
'agents mcp remove my-server --agents codex,claude',
|
|
242
255
|
]);
|
|
243
256
|
}
|
|
244
|
-
// Gather all unique MCPs across agents (with agent info for display)
|
|
245
|
-
const mcpMap = new Map();
|
|
246
|
-
for (const agentId of installedAgents) {
|
|
247
|
-
const mcps = listInstalledMcpsWithScope(agentId, cwd, { home: getEffectiveHome(agentId) });
|
|
248
|
-
for (const mcp of mcps) {
|
|
249
|
-
const existing = mcpMap.get(mcp.name);
|
|
250
|
-
if (existing) {
|
|
251
|
-
existing.agents.push(AGENTS[agentId].name);
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
mcpMap.set(mcp.name, {
|
|
255
|
-
name: mcp.name,
|
|
256
|
-
agents: [AGENTS[agentId].name],
|
|
257
|
-
command: mcp.command,
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
if (mcpMap.size === 0) {
|
|
263
|
-
console.log(chalk.yellow('No MCP servers configured.'));
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
257
|
try {
|
|
267
258
|
const selected = await checkbox({
|
|
268
259
|
message: 'Select MCP servers to remove',
|
|
269
|
-
choices: Array.from(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
260
|
+
choices: Array.from(mcpTargetMap.values()).map((mcp) => {
|
|
261
|
+
const agents = [...new Set(mcp.targets.map((t) => AGENTS[t.agentId].name))];
|
|
262
|
+
return {
|
|
263
|
+
value: mcp.name,
|
|
264
|
+
name: `${mcp.name} (${agents.join(', ')})`,
|
|
265
|
+
};
|
|
266
|
+
}),
|
|
273
267
|
});
|
|
274
268
|
if (selected.length === 0) {
|
|
275
269
|
console.log(chalk.gray('No MCPs selected.'));
|
|
276
270
|
return;
|
|
277
271
|
}
|
|
278
272
|
mcpsToRemove = selected;
|
|
279
|
-
targets = resolveConfiguredAgentTargets(installedAgents, undefined, MCP_CAPABLE_AGENTS);
|
|
280
273
|
}
|
|
281
274
|
catch (err) {
|
|
282
275
|
if (isPromptCancelled(err)) {
|
|
@@ -286,10 +279,59 @@ Examples:
|
|
|
286
279
|
throw err;
|
|
287
280
|
}
|
|
288
281
|
}
|
|
289
|
-
// Execute removals
|
|
282
|
+
// Execute removals with target selection
|
|
290
283
|
let removed = 0;
|
|
291
284
|
for (const mcpName of mcpsToRemove) {
|
|
292
|
-
const
|
|
285
|
+
const mcpInfo = mcpTargetMap.get(mcpName);
|
|
286
|
+
if (!mcpInfo || mcpInfo.targets.length === 0) {
|
|
287
|
+
console.log(chalk.yellow(` MCP '${mcpName}' not found in any agent.`));
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
// If --agents was specified, filter targets
|
|
291
|
+
let availableTargets = mcpInfo.targets;
|
|
292
|
+
if (options?.agents) {
|
|
293
|
+
const requestedTargets = resolveInstalledAgentTargets(options.agents, MCP_CAPABLE_AGENTS);
|
|
294
|
+
const requested = new Set();
|
|
295
|
+
for (const aid of requestedTargets.directAgents) {
|
|
296
|
+
for (const ver of listInstalledVersions(aid)) {
|
|
297
|
+
requested.add(`${aid}@${ver}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
for (const [aid, versions] of requestedTargets.versionSelections) {
|
|
301
|
+
for (const ver of versions) {
|
|
302
|
+
requested.add(`${aid}@${ver}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
availableTargets = availableTargets.filter((t) => requested.has(`${t.agentId}@${t.version}`));
|
|
306
|
+
}
|
|
307
|
+
if (availableTargets.length === 0) {
|
|
308
|
+
console.log(chalk.yellow(` MCP '${mcpName}' not found in specified agents.`));
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
// Show target picker if multiple targets and no --agents flag
|
|
312
|
+
const removalTargets = availableTargets.map((t) => ({
|
|
313
|
+
agent: t.agentId,
|
|
314
|
+
version: t.version,
|
|
315
|
+
label: formatTargetLabel(t.agentId, t.version),
|
|
316
|
+
}));
|
|
317
|
+
const selectedTargets = await promptRemovalTargets(mcpName, removalTargets, {
|
|
318
|
+
skipPrompt: !!options?.agents,
|
|
319
|
+
});
|
|
320
|
+
if (selectedTargets.length === 0) {
|
|
321
|
+
console.log(chalk.gray(` Skipped '${mcpName}'.`));
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
// Build targets structure for unregister
|
|
325
|
+
const versionSelections = new Map();
|
|
326
|
+
for (const t of selectedTargets) {
|
|
327
|
+
const versions = versionSelections.get(t.agent) || [];
|
|
328
|
+
if (!versions.includes(t.version)) {
|
|
329
|
+
versions.push(t.version);
|
|
330
|
+
versionSelections.set(t.agent, versions);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const targetsToRemove = { directAgents: [], versionSelections };
|
|
334
|
+
const results = await unregisterMcpFromTargets(targetsToRemove, mcpName);
|
|
293
335
|
for (const result of results) {
|
|
294
336
|
if (result.success) {
|
|
295
337
|
console.log(` ${chalk.red('-')} ${formatTargetLabel(result.agentId, result.version)}: ${mcpName}`);
|