@phnx-labs/agents-cli 1.20.0 → 1.20.4
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 +81 -0
- package/README.md +4 -4
- package/dist/commands/cli.js +3 -3
- package/dist/commands/cloud.js +1 -1
- package/dist/commands/commands.js +24 -7
- package/dist/commands/exec.js +36 -16
- package/dist/commands/feedback.d.ts +7 -0
- package/dist/commands/feedback.js +89 -0
- package/dist/commands/helper.d.ts +12 -0
- package/dist/commands/helper.js +87 -0
- package/dist/commands/hooks.js +86 -7
- package/dist/commands/import.js +90 -37
- package/dist/commands/mcp.js +166 -10
- package/dist/commands/packages.js +196 -27
- package/dist/commands/permissions.js +21 -6
- package/dist/commands/profiles.d.ts +8 -0
- package/dist/commands/profiles.js +117 -4
- package/dist/commands/pull.js +4 -4
- package/dist/commands/routines.js +6 -6
- package/dist/commands/rules.js +8 -4
- package/dist/commands/secrets-migrate.d.ts +24 -0
- package/dist/commands/secrets-migrate.js +198 -0
- package/dist/commands/secrets-sync.d.ts +11 -0
- package/dist/commands/secrets-sync.js +155 -0
- package/dist/commands/secrets.js +74 -39
- package/dist/commands/skills.js +22 -5
- package/dist/commands/subagents.js +69 -49
- package/dist/commands/teams.js +48 -10
- package/dist/commands/utils.d.ts +33 -0
- package/dist/commands/utils.js +139 -0
- package/dist/commands/versions.js +4 -4
- package/dist/commands/view.d.ts +6 -0
- package/dist/commands/view.js +169 -8
- package/dist/commands/workflows.js +29 -6
- package/dist/index.js +4 -0
- package/dist/lib/acp/client.js +6 -1
- package/dist/lib/agents.d.ts +4 -0
- package/dist/lib/agents.js +41 -17
- package/dist/lib/auto-pull-worker.js +18 -1
- package/dist/lib/browser/chrome.js +4 -0
- package/dist/lib/browser/drivers/ssh.js +1 -1
- package/dist/lib/browser/profiles.d.ts +3 -3
- package/dist/lib/browser/profiles.js +3 -3
- package/dist/lib/browser/service.js +19 -0
- package/dist/lib/browser/types.d.ts +4 -4
- package/dist/lib/cli-resources.d.ts +36 -8
- package/dist/lib/cli-resources.js +268 -46
- package/dist/lib/cloud/factory.d.ts +1 -1
- package/dist/lib/cloud/factory.js +1 -1
- package/dist/lib/events.d.ts +16 -2
- package/dist/lib/events.js +33 -2
- package/dist/lib/exec.d.ts +39 -11
- package/dist/lib/exec.js +90 -31
- package/dist/lib/help.js +11 -5
- package/dist/lib/hooks/cache.d.ts +38 -0
- package/dist/lib/hooks/cache.js +242 -0
- package/dist/lib/hooks/profile.d.ts +33 -0
- package/dist/lib/hooks/profile.js +129 -0
- package/dist/lib/hooks.d.ts +0 -10
- package/dist/lib/hooks.js +68 -15
- package/dist/lib/import.d.ts +21 -0
- package/dist/lib/import.js +55 -2
- package/dist/lib/mcp.d.ts +15 -0
- package/dist/lib/mcp.js +40 -0
- package/dist/lib/permissions.d.ts +13 -0
- package/dist/lib/permissions.js +51 -1
- package/dist/lib/plugin-marketplace.d.ts +10 -0
- package/dist/lib/plugin-marketplace.js +47 -1
- package/dist/lib/plugins.js +15 -1
- package/dist/lib/profiles-presets.d.ts +26 -0
- package/dist/lib/profiles-presets.js +187 -8
- package/dist/lib/profiles.d.ts +34 -0
- package/dist/lib/profiles.js +112 -1
- package/dist/lib/pty-server.js +27 -3
- package/dist/lib/routines-format.d.ts +17 -5
- package/dist/lib/routines-format.js +37 -16
- package/dist/lib/routines.d.ts +1 -1
- package/dist/lib/routines.js +2 -2
- package/dist/lib/runner.js +64 -10
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
- package/dist/lib/secrets/bundles.d.ts +18 -22
- package/dist/lib/secrets/bundles.js +75 -99
- package/dist/lib/secrets/index.d.ts +51 -27
- package/dist/lib/secrets/index.js +147 -156
- package/dist/lib/secrets/install-helper.d.ts +45 -0
- package/dist/lib/secrets/install-helper.js +165 -0
- package/dist/lib/secrets/linux.js +4 -4
- package/dist/lib/secrets/sync.d.ts +56 -0
- package/dist/lib/secrets/sync.js +180 -0
- package/dist/lib/session/render.js +4 -4
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/shims.d.ts +4 -1
- package/dist/lib/shims.js +5 -35
- package/dist/lib/state.d.ts +14 -1
- package/dist/lib/state.js +49 -5
- package/dist/lib/teams/agents.d.ts +5 -4
- package/dist/lib/teams/agents.js +47 -21
- package/dist/lib/teams/api.d.ts +2 -1
- package/dist/lib/teams/api.js +4 -3
- package/dist/lib/types.d.ts +57 -1
- package/dist/lib/types.js +2 -0
- package/dist/lib/usage.d.ts +27 -2
- package/dist/lib/usage.js +100 -17
- package/dist/lib/versions.d.ts +35 -1
- package/dist/lib/versions.js +288 -64
- package/package.json +13 -12
- package/scripts/install-helper.js +97 -0
- package/scripts/postinstall.js +16 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
package/dist/commands/hooks.js
CHANGED
|
@@ -8,9 +8,9 @@ import { AGENTS, HOOKS_CAPABLE_AGENTS, resolveAgentName, formatAgentError, agent
|
|
|
8
8
|
import { supports } from '../lib/capabilities.js';
|
|
9
9
|
import { cloneRepo } from '../lib/git.js';
|
|
10
10
|
import { discoverHooksFromRepo, installHooksCentrally, listCentralHooks, listInstalledHooksWithScope, getHookInfo, parseHookManifest, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks.js';
|
|
11
|
-
import { listInstalledVersions, getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath,
|
|
11
|
+
import { listInstalledVersions, getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, resolveInstalledAgentTargets, } from '../lib/versions.js';
|
|
12
12
|
import { recordVersionResources } from '../lib/state.js';
|
|
13
|
-
import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, promptRemovalTargets, } from './utils.js';
|
|
13
|
+
import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, promptRemovalTargets, resolveAgentTargetsAutoInstalling, } 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')
|
|
@@ -340,13 +340,17 @@ Examples:
|
|
|
340
340
|
let versionSelections;
|
|
341
341
|
const hooksCapableAgents = Array.from(HOOKS_CAPABLE_AGENTS);
|
|
342
342
|
if (options.agents) {
|
|
343
|
-
const result =
|
|
343
|
+
const result = await resolveAgentTargetsAutoInstalling(options.agents, hooksCapableAgents, { yes: options.yes });
|
|
344
|
+
if (!result) {
|
|
345
|
+
console.log(chalk.gray('Cancelled.'));
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
344
348
|
selectedAgents = result.selectedAgents;
|
|
345
349
|
versionSelections = result.versionSelections;
|
|
346
350
|
}
|
|
347
351
|
else {
|
|
348
352
|
const result = await promptAgentVersionSelection(hooksCapableAgents, {
|
|
349
|
-
skipPrompts: options.yes
|
|
353
|
+
skipPrompts: options.yes,
|
|
350
354
|
});
|
|
351
355
|
selectedAgents = result.selectedAgents;
|
|
352
356
|
versionSelections = result.versionSelections;
|
|
@@ -459,11 +463,24 @@ Examples:
|
|
|
459
463
|
console.log(chalk.yellow(` Hook '${hookName}' not found in any version.`));
|
|
460
464
|
continue;
|
|
461
465
|
}
|
|
462
|
-
// Filter by --agents if specified
|
|
466
|
+
// Filter by --agents if specified. Routes through resolveInstalledAgentTargets
|
|
467
|
+
// so the same selector syntax used everywhere else (agent, agent@default,
|
|
468
|
+
// agent@x.y.z, agent@all, literal all) works here too.
|
|
463
469
|
let availableTargets = hookInfo.targets;
|
|
464
470
|
if (options?.agents) {
|
|
465
|
-
const
|
|
466
|
-
|
|
471
|
+
const requestedTargets = resolveInstalledAgentTargets(options.agents, [...HOOKS_CAPABLE_AGENTS]);
|
|
472
|
+
const requested = new Set();
|
|
473
|
+
for (const aid of requestedTargets.directAgents) {
|
|
474
|
+
for (const ver of listInstalledVersions(aid)) {
|
|
475
|
+
requested.add(`${aid}@${ver}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
for (const [aid, versions] of requestedTargets.versionSelections) {
|
|
479
|
+
for (const ver of versions) {
|
|
480
|
+
requested.add(`${aid}@${ver}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
availableTargets = availableTargets.filter((t) => requested.has(`${t.agent}@${t.version}`));
|
|
467
484
|
}
|
|
468
485
|
if (availableTargets.length === 0) {
|
|
469
486
|
console.log(chalk.yellow(` Hook '${hookName}' not found in specified agents.`));
|
|
@@ -579,4 +596,66 @@ Examples:
|
|
|
579
596
|
printWithPager(output, contentLines.length);
|
|
580
597
|
}
|
|
581
598
|
});
|
|
599
|
+
hooksCmd
|
|
600
|
+
.command('profile')
|
|
601
|
+
.description('Per-hook timing + cache stats from recent invocations')
|
|
602
|
+
.option('--days <n>', 'Number of days of logs to read', '7')
|
|
603
|
+
.option('--warn-ms <n>', 'p99 threshold above which a hook is flagged as slow', '2000')
|
|
604
|
+
.option('--json', 'Emit raw JSON rows instead of the table')
|
|
605
|
+
.addHelpText('after', `
|
|
606
|
+
Shows aggregated stats for every hook that emitted a hook.fire event into
|
|
607
|
+
~/.agents/.cache/logs/events-YYYY-MM-DD.jsonl. Only hooks with \`cache:\` in
|
|
608
|
+
their manifest are instrumented today — the generated shim writes the events.
|
|
609
|
+
|
|
610
|
+
Examples:
|
|
611
|
+
agents hooks profile # last 7 days, table form
|
|
612
|
+
agents hooks profile --days 30 # roll up the full month
|
|
613
|
+
agents hooks profile --json | jq # pipe somewhere
|
|
614
|
+
|
|
615
|
+
A hook whose p99 exceeds --warn-ms gets flagged in the cache column. Add
|
|
616
|
+
'cache: 5m' or 'cache: 5m-bg' to its hooks.yaml entry to fix it.
|
|
617
|
+
`)
|
|
618
|
+
.action(async (options) => {
|
|
619
|
+
const { aggregateHookProfile, loadHookFireEvents, formatMs, formatCacheColumn, DEFAULT_SLOW_HOOK_WARN_MS } = await import('../lib/hooks/profile.js');
|
|
620
|
+
const days = Math.max(1, parseInt(options.days, 10) || 7);
|
|
621
|
+
const warnMs = Math.max(0, parseInt(options.warnMs, 10) || DEFAULT_SLOW_HOOK_WARN_MS);
|
|
622
|
+
const rows = aggregateHookProfile(loadHookFireEvents(days));
|
|
623
|
+
if (options.json) {
|
|
624
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (rows.length === 0) {
|
|
628
|
+
console.log(chalk.gray(`No hook.fire events in the last ${days} day${days === 1 ? '' : 's'}.`));
|
|
629
|
+
console.log(chalk.gray('Add \'cache: 5m\' to a hook in hooks.yaml to start collecting stats.'));
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
const widths = { hook: 36, n: 5, p50: 7, p99: 7, mean: 7, max: 7, cache: 30 };
|
|
633
|
+
const pad = (s, w) => (s.length >= w ? s.slice(0, w) : s + ' '.repeat(w - s.length));
|
|
634
|
+
const header = [
|
|
635
|
+
pad('HOOK', widths.hook),
|
|
636
|
+
pad('N', widths.n),
|
|
637
|
+
pad('P50', widths.p50),
|
|
638
|
+
pad('P99', widths.p99),
|
|
639
|
+
pad('MEAN', widths.mean),
|
|
640
|
+
pad('MAX', widths.max),
|
|
641
|
+
pad('CACHE', widths.cache),
|
|
642
|
+
].join(' ');
|
|
643
|
+
console.log(chalk.bold(header));
|
|
644
|
+
console.log(chalk.gray('─'.repeat(header.length)));
|
|
645
|
+
for (const r of rows) {
|
|
646
|
+
const slow = r.p99Ms > warnMs;
|
|
647
|
+
const cacheCol = formatCacheColumn(r);
|
|
648
|
+
const warning = slow && r.cacheHitPct + r.cacheStalePct === 0 ? ' ← add cache: 5m' : '';
|
|
649
|
+
const line = [
|
|
650
|
+
pad(r.hook, widths.hook),
|
|
651
|
+
pad(String(r.n), widths.n),
|
|
652
|
+
pad(formatMs(r.p50Ms), widths.p50),
|
|
653
|
+
pad(formatMs(r.p99Ms), widths.p99),
|
|
654
|
+
pad(formatMs(r.meanMs), widths.mean),
|
|
655
|
+
pad(formatMs(r.maxMs), widths.max),
|
|
656
|
+
pad(cacheCol, widths.cache),
|
|
657
|
+
].join(' ') + warning;
|
|
658
|
+
console.log(slow ? chalk.yellow(line) : line);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
582
661
|
}
|
package/dist/commands/import.js
CHANGED
|
@@ -23,12 +23,13 @@
|
|
|
23
23
|
import chalk from 'chalk';
|
|
24
24
|
import ora from 'ora';
|
|
25
25
|
import * as fs from 'fs';
|
|
26
|
+
import * as os from 'os';
|
|
26
27
|
import * as path from 'path';
|
|
27
28
|
import { confirm } from '@inquirer/prompts';
|
|
28
29
|
import { ALL_AGENT_IDS } from '../lib/agents.js';
|
|
29
30
|
import { AGENTS, getCliPath, getCliVersion, agentLabel } from '../lib/agents.js';
|
|
30
31
|
import { getVersionDir } from '../lib/versions.js';
|
|
31
|
-
import { finalizeImport, importAgentBinary, importAgentConfig, isValidImportVersion, resolvePackageDirFromBinary, } from '../lib/import.js';
|
|
32
|
+
import { finalizeImport, importAgentBinary, importAgentConfig, importInstallScriptBinary, isValidImportVersion, resolvePackageDirFromBinary, } from '../lib/import.js';
|
|
32
33
|
import { isPromptCancelled, isInteractiveTerminal } from './utils.js';
|
|
33
34
|
function isValidAgentId(value) {
|
|
34
35
|
return ALL_AGENT_IDS.includes(value);
|
|
@@ -41,54 +42,94 @@ async function runImport(agentArg, opts) {
|
|
|
41
42
|
}
|
|
42
43
|
const agentId = agentArg;
|
|
43
44
|
const agent = AGENTS[agentId];
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
console.error(chalk.gray(`Use \`agents add ${agentId}\` to install via its native script.`));
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
45
|
+
// installScript-based agents (Grok, Antigravity, Cursor, Kiro, Goose, Roo)
|
|
46
|
+
// don't have an npm package; their binary lives wherever the curl/brew
|
|
47
|
+
// installer dropped it. We adopt by symlinking that PATH binary directly
|
|
48
|
+
// into the version's `node_modules/.bin/`. No package.json walk.
|
|
49
|
+
const isInstallScriptAgent = !agent.npmPackage;
|
|
52
50
|
let globalPath = null;
|
|
51
|
+
let installScriptBinary = null;
|
|
53
52
|
if (opts.fromPath) {
|
|
54
53
|
globalPath = path.resolve(opts.fromPath);
|
|
55
54
|
if (!fs.existsSync(globalPath)) {
|
|
56
55
|
console.error(chalk.red(`Path does not exist: ${globalPath}`));
|
|
57
56
|
process.exit(1);
|
|
58
57
|
}
|
|
58
|
+
if (isInstallScriptAgent) {
|
|
59
|
+
// With --from-path on an installScript agent, the path is the binary
|
|
60
|
+
// itself (or a directory containing it). Accept either.
|
|
61
|
+
if (fs.statSync(globalPath).isDirectory()) {
|
|
62
|
+
const candidate = path.join(globalPath, agent.cliCommand);
|
|
63
|
+
if (!fs.existsSync(candidate)) {
|
|
64
|
+
console.error(chalk.red(`No "${agent.cliCommand}" in ${globalPath}`));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
installScriptBinary = candidate;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
installScriptBinary = globalPath;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
59
73
|
}
|
|
60
74
|
else {
|
|
61
75
|
const binary = await getCliPath(agentId);
|
|
62
76
|
if (!binary) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const installName = agent.npmPackage || agent.cliCommand;
|
|
77
|
+
const installHint = isInstallScriptAgent
|
|
78
|
+
? `Run \`agents add ${agentId}\` to install via the official script, or pass --from-path.`
|
|
79
|
+
: `Install it first (e.g. \`npm i -g ${agent.npmPackage || agent.cliCommand}\`) or pass --from-path.`;
|
|
67
80
|
console.error(chalk.red(`No "${agent.cliCommand}" found on PATH.`));
|
|
68
|
-
console.error(chalk.gray(
|
|
81
|
+
console.error(chalk.gray(installHint));
|
|
69
82
|
process.exit(1);
|
|
70
83
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
if (isInstallScriptAgent) {
|
|
85
|
+
installScriptBinary = binary;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
globalPath = resolvePackageDirFromBinary(binary);
|
|
89
|
+
if (!globalPath) {
|
|
90
|
+
console.error(chalk.red(`Could not resolve npm package for binary: ${binary}`));
|
|
91
|
+
console.error(chalk.gray('Pass --from-path <dir> with the package directory explicitly.'));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// For Grok, the binary on PATH is typically `~/.grok/bin/grok` (a moving
|
|
97
|
+
// pointer to the latest install). Prefer the exact versioned file in
|
|
98
|
+
// `~/.grok/downloads/` so the v<x.y.z> alias is pinned to that file and
|
|
99
|
+
// doesn't drift when the user upgrades externally.
|
|
100
|
+
if (isInstallScriptAgent && agentId === 'grok' && !opts.fromPath) {
|
|
101
|
+
const detected = await getCliVersion(agentId);
|
|
102
|
+
if (detected) {
|
|
103
|
+
const downloads = path.join(os.homedir(), '.grok', 'downloads');
|
|
104
|
+
try {
|
|
105
|
+
const entries = fs.readdirSync(downloads);
|
|
106
|
+
const exact = entries.find((e) => e.startsWith('grok-') && e.includes(detected));
|
|
107
|
+
if (exact) {
|
|
108
|
+
installScriptBinary = path.join(downloads, exact);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* fall back to PATH binary already set above */
|
|
113
|
+
}
|
|
76
114
|
}
|
|
77
115
|
}
|
|
78
116
|
let version = opts.version;
|
|
79
117
|
if (!version) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
118
|
+
if (!isInstallScriptAgent && globalPath) {
|
|
119
|
+
try {
|
|
120
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(globalPath, 'package.json'), 'utf8'));
|
|
121
|
+
version = typeof pkg.version === 'string' ? pkg.version : undefined;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
/* fall through */
|
|
125
|
+
}
|
|
86
126
|
}
|
|
87
127
|
// Only fall back to running the PATH binary's --version when we're
|
|
88
|
-
// auto-detecting. With --from-path, the PATH binary may
|
|
89
|
-
// different install entirely; reporting its version here
|
|
90
|
-
// mis-attribute the imported version.
|
|
91
|
-
|
|
128
|
+
// auto-detecting. With --from-path on an npm agent, the PATH binary may
|
|
129
|
+
// belong to a different install entirely; reporting its version here
|
|
130
|
+
// would silently mis-attribute the imported version. installScript agents
|
|
131
|
+
// always use `<bin> --version` since they have no package.json to read.
|
|
132
|
+
if (!version && (isInstallScriptAgent || !opts.fromPath)) {
|
|
92
133
|
const detected = await getCliVersion(agentId);
|
|
93
134
|
version = detected ?? undefined;
|
|
94
135
|
}
|
|
@@ -104,8 +145,9 @@ async function runImport(agentArg, opts) {
|
|
|
104
145
|
process.exit(1);
|
|
105
146
|
}
|
|
106
147
|
const versionDir = getVersionDir(agentId, version);
|
|
148
|
+
const fromLabel = isInstallScriptAgent ? installScriptBinary : globalPath;
|
|
107
149
|
console.log(chalk.bold(`\nImport ${agentLabel(agentId)} v${version}`));
|
|
108
|
-
console.log(` from: ${chalk.gray(
|
|
150
|
+
console.log(` from: ${chalk.gray(fromLabel)}`);
|
|
109
151
|
console.log(` into: ${chalk.gray(versionDir)}`);
|
|
110
152
|
const configDirExists = fs.existsSync(agent.configDir);
|
|
111
153
|
let configAlreadyManaged = false;
|
|
@@ -146,7 +188,8 @@ async function runImport(agentArg, opts) {
|
|
|
146
188
|
const cfgSpinner = ora(`Importing config dir for ${agentLabel(agentId)} v${version}...`).start();
|
|
147
189
|
const cfgResult = await importAgentConfig(agentId, version);
|
|
148
190
|
if (cfgResult.success) {
|
|
149
|
-
|
|
191
|
+
const relConfig = path.relative(os.homedir(), agent.configDir);
|
|
192
|
+
cfgSpinner.succeed(`Config imported (${agent.configDir} -> ${versionDir}/home/${relConfig})`);
|
|
150
193
|
}
|
|
151
194
|
else if (cfgResult.skipped) {
|
|
152
195
|
cfgSpinner.warn(`Config: ${cfgResult.error}`);
|
|
@@ -157,9 +200,11 @@ async function runImport(agentArg, opts) {
|
|
|
157
200
|
}
|
|
158
201
|
}
|
|
159
202
|
const binSpinner = ora(`Registering ${agentLabel(agentId)} v${version} binary...`).start();
|
|
160
|
-
const binResult =
|
|
203
|
+
const binResult = isInstallScriptAgent
|
|
204
|
+
? importInstallScriptBinary({ agentId, npmPackage: agent.npmPackage, cliCommand: agent.cliCommand }, version, installScriptBinary, versionDir)
|
|
205
|
+
: importAgentBinary({ agentId, npmPackage: agent.npmPackage, cliCommand: agent.cliCommand }, version, globalPath, versionDir);
|
|
161
206
|
if (binResult.success) {
|
|
162
|
-
binSpinner.succeed(`Binary registered (${agent.cliCommand} -> ${
|
|
207
|
+
binSpinner.succeed(`Binary registered (${agent.cliCommand} -> ${binResult.resolvedFromPath})`);
|
|
163
208
|
}
|
|
164
209
|
else if (binResult.skipped) {
|
|
165
210
|
binSpinner.warn(`Binary: ${binResult.error}`);
|
|
@@ -198,11 +243,19 @@ Examples:
|
|
|
198
243
|
$ agents import openclaw --version 2026.3.8 Pin a version label
|
|
199
244
|
$ agents import openclaw --from-path /opt/homebrew/lib/node_modules/openclaw
|
|
200
245
|
|
|
246
|
+
# installScript-based agents (curl/brew installers, no npm package):
|
|
247
|
+
$ agents import grok Adopt ~/.grok/downloads/grok-<ver>
|
|
248
|
+
$ agents import antigravity Adopt ~/.local/bin/agy
|
|
249
|
+
$ agents import cursor Adopt ~/.local/bin/cursor-agent
|
|
250
|
+
$ agents import antigravity --from-path ~/.local/bin/agy
|
|
251
|
+
|
|
201
252
|
When to use:
|
|
202
|
-
When an agent CLI is already installed globally
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
253
|
+
When an agent CLI is already installed globally and you want to bring it
|
|
254
|
+
under agents-cli management without reinstalling. Creates a symlink farm
|
|
255
|
+
pointing at the existing install — nothing is copied or moved (except the
|
|
256
|
+
agent's config dir, which is moved into the version's home). Works for both
|
|
257
|
+
npm-style packages (claude, codex, gemini, opencode, openclaw) and
|
|
258
|
+
installScript-based agents (grok, antigravity, cursor, kiro, goose, roo).
|
|
206
259
|
`)
|
|
207
260
|
.action(runImport);
|
|
208
261
|
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -3,20 +3,49 @@ import ora from 'ora';
|
|
|
3
3
|
import { checkbox } from '@inquirer/prompts';
|
|
4
4
|
import { AGENTS, MCP_CAPABLE_AGENTS, getAllCliStates, resolveAgentName, formatAgentError, registerMcpToTargets, unregisterMcpFromTargets, listInstalledMcpsWithScope, parseMcpConfig, getMcpConfigPathForHome, agentLabel, } from '../lib/agents.js';
|
|
5
5
|
import { readManifest, writeManifest, createDefaultManifest } from '../lib/manifest.js';
|
|
6
|
-
import { listMcpServerConfigs } from '../lib/mcp.js';
|
|
6
|
+
import { listMcpServerConfigs, discoverMcpConfigsFromRepo, installMcpConfigCentrally, } from '../lib/mcp.js';
|
|
7
|
+
import { cloneRepo } from '../lib/git.js';
|
|
7
8
|
import { getMcpDir } from '../lib/state.js';
|
|
8
|
-
import { getEffectiveHome, getGlobalDefault, listInstalledVersions, getVersionHomePath, resolveInstalledAgentTargets, resolveConfiguredAgentTargets, resolveVersionAlias, } from '../lib/versions.js';
|
|
9
|
+
import { getEffectiveHome, getGlobalDefault, listInstalledVersions, getVersionHomePath, resolveInstalledAgentTargets, resolveConfiguredAgentTargets, resolveVersionAlias, syncResourcesToVersion, } from '../lib/versions.js';
|
|
9
10
|
import { getUserAgentsDir } from '../lib/state.js';
|
|
10
|
-
import { isPromptCancelled, isInteractiveTerminal, requireInteractiveSelection, promptRemovalTargets } from './utils.js';
|
|
11
|
+
import { isPromptCancelled, isInteractiveTerminal, requireInteractiveSelection, promptRemovalTargets, parseCommaSeparatedList, ensureAgentVersionsInstalled, resolveAgentTargetsAutoInstalling, resolveInstalledAgentTargetsAutoInstalling, VersionNotInstalledError, } from './utils.js';
|
|
11
12
|
import { showResourceList, buildTargetsSection, } from './resource-view.js';
|
|
12
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Parse a comma-separated --agents string into validated agent IDs and
|
|
15
|
+
* optional version targets in the manifest shape.
|
|
16
|
+
*
|
|
17
|
+
* Supports the same selector syntax as resolveAgentVersionTargets:
|
|
18
|
+
* - bare `agent` → manifest agents:[agent] (no version pin)
|
|
19
|
+
* - `agent@default` → manifest agents:[agent] (no version pin)
|
|
20
|
+
* - `agent@x.y.z` → manifest agentVersions[agent] = ['x.y.z']
|
|
21
|
+
* - `agent@all` → manifest agentVersions[agent] = every installed version
|
|
22
|
+
* - literal `all` → expand to all MCP-capable agents (each as `@all`)
|
|
23
|
+
*
|
|
24
|
+
* Throws VersionNotInstalledError for unknown specific versions so callers
|
|
25
|
+
* can prompt-and-install before retrying.
|
|
26
|
+
*/
|
|
13
27
|
function parseMcpAgentTargets(value) {
|
|
14
28
|
const agents = [];
|
|
15
29
|
const agentVersions = {};
|
|
16
|
-
const
|
|
30
|
+
const rawTargets = value
|
|
17
31
|
.split(',')
|
|
18
32
|
.map((item) => item.trim())
|
|
19
33
|
.filter(Boolean);
|
|
34
|
+
// Expand literal `all` / `all@all` into per-agent @all. Skip agents with no
|
|
35
|
+
// installed versions so `all` is lenient — mirrors resolveAgentVersionTargets.
|
|
36
|
+
const targets = [];
|
|
37
|
+
for (const t of rawTargets) {
|
|
38
|
+
if (t === 'all' || t === 'all@all') {
|
|
39
|
+
for (const a of MCP_CAPABLE_AGENTS) {
|
|
40
|
+
if (listInstalledVersions(a).length > 0) {
|
|
41
|
+
targets.push(`${a}@all`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
targets.push(t);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
20
49
|
for (const target of targets) {
|
|
21
50
|
const atIndex = target.indexOf('@');
|
|
22
51
|
const agentToken = (atIndex === -1 ? target : target.slice(0, atIndex)).trim();
|
|
@@ -25,7 +54,7 @@ function parseMcpAgentTargets(value) {
|
|
|
25
54
|
continue;
|
|
26
55
|
}
|
|
27
56
|
if (atIndex !== -1 && !versionToken) {
|
|
28
|
-
throw new Error(`Missing version in --agents entry '${target}'. Use agent@x.y.z or agent@
|
|
57
|
+
throw new Error(`Missing version in --agents entry '${target}'. Use agent@x.y.z, agent@default, or agent@all.`);
|
|
29
58
|
}
|
|
30
59
|
const agentId = resolveAgentName(agentToken);
|
|
31
60
|
if (!agentId || !MCP_CAPABLE_AGENTS.includes(agentId)) {
|
|
@@ -50,11 +79,23 @@ function parseMcpAgentTargets(value) {
|
|
|
50
79
|
if (installedVersions.length === 0) {
|
|
51
80
|
throw new Error(`No managed versions are installed for ${AGENTS[agentId].name}. Run: agents add ${agentId}@latest`);
|
|
52
81
|
}
|
|
82
|
+
if (versionToken === 'all') {
|
|
83
|
+
const versions = agentVersions[agentId] || [];
|
|
84
|
+
for (const ver of installedVersions) {
|
|
85
|
+
if (!versions.includes(ver))
|
|
86
|
+
versions.push(ver);
|
|
87
|
+
}
|
|
88
|
+
agentVersions[agentId] = versions;
|
|
89
|
+
if (!agents.includes(agentId)) {
|
|
90
|
+
agents.push(agentId);
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
53
94
|
const resolvedVersion = versionToken === 'latest'
|
|
54
95
|
? installedVersions[installedVersions.length - 1]
|
|
55
96
|
: versionToken;
|
|
56
97
|
if (!installedVersions.includes(resolvedVersion)) {
|
|
57
|
-
throw new
|
|
98
|
+
throw new VersionNotInstalledError(agentId, resolvedVersion, installedVersions);
|
|
58
99
|
}
|
|
59
100
|
const versions = agentVersions[agentId] || [];
|
|
60
101
|
if (!versions.includes(resolvedVersion)) {
|
|
@@ -140,6 +181,8 @@ When to use:
|
|
|
140
181
|
.option('-a, --agents <list>', 'Targets: claude, codex@0.116.0', MCP_CAPABLE_AGENTS.join(','))
|
|
141
182
|
.option('-s, --scope <scope>', 'user (global) or project (repo-specific)', 'user')
|
|
142
183
|
.option('-t, --transport <type>', 'stdio (default) or http', 'stdio')
|
|
184
|
+
.option('--names <list>', 'When source is a repo: MCP server names to install (comma-separated)')
|
|
185
|
+
.option('-y, --yes', 'Auto-install any missing agent versions without prompting')
|
|
143
186
|
.option('-H, --header <header>', 'HTTP header as name:value (repeatable)', (val, acc) => {
|
|
144
187
|
acc.push(val);
|
|
145
188
|
return acc;
|
|
@@ -154,8 +197,22 @@ Examples:
|
|
|
154
197
|
|
|
155
198
|
# Add to manifest only (register later)
|
|
156
199
|
agents mcp add db-server -- uvx postgres-mcp
|
|
200
|
+
|
|
201
|
+
# Install all MCP server configs from a repo's mcp/*.yaml
|
|
202
|
+
agents mcp add gh:user/repo --agents claude@all
|
|
203
|
+
|
|
204
|
+
# Install specific servers by name
|
|
205
|
+
agents mcp add gh:phnx-labs/.agents-system --names notion,figma --agents claude
|
|
157
206
|
`)
|
|
158
207
|
.action(async (name, commandOrUrl, options) => {
|
|
208
|
+
// Repo-source form: `agents mcp add gh:user/repo [--names a,b] [--agents …]`
|
|
209
|
+
// Mirrors `agents skills add gh:…`. Discovers <repoPath>/mcp/*.yaml,
|
|
210
|
+
// copies to ~/.agents/mcp/, and syncs to selected agent versions.
|
|
211
|
+
const isRepoSource = /^(gh:|git:|ssh:|https?:\/\/)/.test(name);
|
|
212
|
+
if (isRepoSource && commandOrUrl.length === 0) {
|
|
213
|
+
await installMcpsFromRepoSource(name, options);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
159
216
|
// Registry resolution: if the user just typed `agents mcp add <name>`,
|
|
160
217
|
// try looking up `<name>` in any configured MCP registry (by default the
|
|
161
218
|
// official MCP Registry at registry.modelcontextprotocol.io) and derive
|
|
@@ -195,6 +252,13 @@ Examples:
|
|
|
195
252
|
const localPath = getUserAgentsDir();
|
|
196
253
|
const manifest = readManifest(localPath) || createDefaultManifest();
|
|
197
254
|
manifest.mcp = manifest.mcp || {};
|
|
255
|
+
// Pre-flight: prompt-and-install any requested agent@version that isn't
|
|
256
|
+
// installed yet, before parseMcpAgentTargets validates the selector.
|
|
257
|
+
const okInstall = await ensureAgentVersionsInstalled(options.agents, MCP_CAPABLE_AGENTS, { yes: options.yes });
|
|
258
|
+
if (!okInstall) {
|
|
259
|
+
console.log(chalk.gray('Cancelled.'));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
198
262
|
const targetConfig = parseMcpAgentTargets(options.agents);
|
|
199
263
|
if (transport === 'http') {
|
|
200
264
|
const url = commandOrUrl[0];
|
|
@@ -459,6 +523,7 @@ Examples:
|
|
|
459
523
|
.command('register [name]')
|
|
460
524
|
.description('Apply MCP servers from manifest to agent config files')
|
|
461
525
|
.option('-a, --agents <list>', 'Override manifest targets: claude, codex@0.116.0')
|
|
526
|
+
.option('-y, --yes', 'Auto-install any missing agent versions without prompting')
|
|
462
527
|
.addHelpText('after', `
|
|
463
528
|
Examples:
|
|
464
529
|
# Register all servers from manifest
|
|
@@ -495,9 +560,18 @@ Examples:
|
|
|
495
560
|
continue;
|
|
496
561
|
}
|
|
497
562
|
console.log(`\n ${chalk.cyan(mcpName)}:`);
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
563
|
+
let targets;
|
|
564
|
+
if (options.agents) {
|
|
565
|
+
const resolved = await resolveInstalledAgentTargetsAutoInstalling(options.agents, MCP_CAPABLE_AGENTS, { yes: options.yes });
|
|
566
|
+
if (!resolved) {
|
|
567
|
+
console.log(chalk.gray(' Cancelled.'));
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
targets = resolved;
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
targets = resolveConfiguredAgentTargets(config.agents, config.agentVersions, MCP_CAPABLE_AGENTS);
|
|
574
|
+
}
|
|
501
575
|
const results = await registerMcpToTargets(targets, mcpName, commandOrUrl, config.scope || 'user', transport, { headers: config.headers });
|
|
502
576
|
for (const result of results) {
|
|
503
577
|
if (result.success) {
|
|
@@ -513,6 +587,88 @@ Examples:
|
|
|
513
587
|
}
|
|
514
588
|
});
|
|
515
589
|
}
|
|
590
|
+
async function installMcpsFromRepoSource(source, options) {
|
|
591
|
+
const spinner = ora('Cloning repository...').start();
|
|
592
|
+
let localPath;
|
|
593
|
+
try {
|
|
594
|
+
const cloneResult = await cloneRepo(source);
|
|
595
|
+
localPath = cloneResult.localPath;
|
|
596
|
+
}
|
|
597
|
+
catch (err) {
|
|
598
|
+
spinner.fail(`Failed to clone: ${err.message}`);
|
|
599
|
+
process.exit(1);
|
|
600
|
+
}
|
|
601
|
+
spinner.succeed('Repository cloned');
|
|
602
|
+
let discovered = discoverMcpConfigsFromRepo(localPath);
|
|
603
|
+
if (discovered.length === 0) {
|
|
604
|
+
console.log(chalk.yellow('No MCP server configs found (looking for mcp/*.yaml)'));
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const requestedNames = parseCommaSeparatedList(options.names);
|
|
608
|
+
if (requestedNames.length > 0) {
|
|
609
|
+
const discoveredNames = new Set(discovered.map((s) => s.name));
|
|
610
|
+
const missing = requestedNames.filter((n) => !discoveredNames.has(n));
|
|
611
|
+
if (missing.length > 0) {
|
|
612
|
+
console.log(chalk.red(`\nMCP server(s) not found in source: ${missing.join(', ')}`));
|
|
613
|
+
console.log(chalk.gray(`Available: ${[...discoveredNames].join(', ')}`));
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
616
|
+
discovered = discovered.filter((s) => requestedNames.includes(s.name));
|
|
617
|
+
}
|
|
618
|
+
console.log(chalk.bold(`\nFound ${discovered.length} MCP server config(s):`));
|
|
619
|
+
for (const s of discovered) {
|
|
620
|
+
const summary = s.config.transport === 'stdio'
|
|
621
|
+
? `${s.config.command}${s.config.args?.length ? ' ' + s.config.args.join(' ') : ''}`
|
|
622
|
+
: s.config.url ?? '';
|
|
623
|
+
console.log(` ${chalk.cyan(s.name)}: ${chalk.gray(summary)}`);
|
|
624
|
+
}
|
|
625
|
+
const installSpinner = ora('Installing MCP configs to ~/.agents/mcp/...').start();
|
|
626
|
+
let installed = 0;
|
|
627
|
+
for (const s of discovered) {
|
|
628
|
+
const result = installMcpConfigCentrally(s.path);
|
|
629
|
+
if (result.success) {
|
|
630
|
+
installed++;
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
installSpinner.stop();
|
|
634
|
+
console.log(chalk.red(` Failed to install ${s.name}: ${result.error}`));
|
|
635
|
+
installSpinner.start();
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
installSpinner.succeed(`Installed ${installed} MCP config(s) to ~/.agents/mcp/`);
|
|
639
|
+
// Agent/version selection — same default as the non-repo form: every
|
|
640
|
+
// MCP-capable agent. Routes through resolveAgentTargetsAutoInstalling so
|
|
641
|
+
// a typo'd `claude@2.1.999` prompts to install (and --yes auto-installs).
|
|
642
|
+
const agentsValue = options.agents ?? MCP_CAPABLE_AGENTS.join(',');
|
|
643
|
+
let targets;
|
|
644
|
+
try {
|
|
645
|
+
const resolved = await resolveAgentTargetsAutoInstalling(agentsValue, MCP_CAPABLE_AGENTS, { yes: options.yes });
|
|
646
|
+
if (!resolved) {
|
|
647
|
+
console.log(chalk.gray('\nCancelled.'));
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
targets = resolved;
|
|
651
|
+
}
|
|
652
|
+
catch (err) {
|
|
653
|
+
console.log(chalk.red(err.message));
|
|
654
|
+
process.exit(1);
|
|
655
|
+
}
|
|
656
|
+
if (targets.versionSelections.size === 0) {
|
|
657
|
+
console.log(chalk.gray('\nStored centrally; no agent versions selected for sync.'));
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const syncSpinner = ora('Syncing to agent versions...').start();
|
|
661
|
+
const mcpNames = discovered.map((s) => s.name);
|
|
662
|
+
let synced = 0;
|
|
663
|
+
for (const [agentId, versions] of targets.versionSelections) {
|
|
664
|
+
for (const version of versions) {
|
|
665
|
+
const result = syncResourcesToVersion(agentId, version, { mcp: mcpNames });
|
|
666
|
+
if (result.mcp.length > 0)
|
|
667
|
+
synced++;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
syncSpinner.succeed(`Synced MCP configs to ${synced} agent version(s).`);
|
|
671
|
+
}
|
|
516
672
|
/** Enumerate (agent, version) pairs that support MCP and have a version home. */
|
|
517
673
|
function iterMcpCapableVersions(filter) {
|
|
518
674
|
const out = [];
|