@phnx-labs/agents-cli 1.19.2 → 1.20.3
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 +140 -0
- package/README.md +72 -12
- package/dist/browser.js +0 -0
- package/dist/commands/browser.js +88 -16
- package/dist/commands/cli.d.ts +14 -0
- package/dist/commands/cli.js +244 -0
- package/dist/commands/cloud.js +1 -1
- package/dist/commands/commands.js +27 -10
- package/dist/commands/computer.js +18 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/exec.js +38 -18
- package/dist/commands/factory.d.ts +3 -14
- package/dist/commands/factory.js +3 -3
- 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 +89 -10
- package/dist/commands/mcp.js +166 -10
- package/dist/commands/packages.js +196 -27
- package/dist/commands/permissions.js +21 -6
- package/dist/commands/plugins.js +11 -4
- package/dist/commands/profiles.d.ts +8 -0
- package/dist/commands/profiles.js +118 -5
- package/dist/commands/prune.js +39 -160
- package/dist/commands/pull.js +58 -5
- package/dist/commands/routines.js +107 -14
- 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 +79 -46
- package/dist/commands/sessions.d.ts +28 -0
- package/dist/commands/sessions.js +98 -33
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +37 -28
- package/dist/commands/skills.js +25 -8
- package/dist/commands/subagents.js +69 -49
- package/dist/commands/teams.js +61 -10
- package/dist/commands/utils.d.ts +33 -0
- package/dist/commands/utils.js +139 -0
- package/dist/commands/versions.d.ts +4 -3
- package/dist/commands/versions.js +134 -130
- package/dist/commands/view.d.ts +6 -0
- package/dist/commands/view.js +175 -19
- package/dist/commands/workflows.js +29 -6
- package/dist/computer.js +0 -0
- package/dist/index.js +38 -6
- package/dist/lib/acp/client.js +6 -1
- package/dist/lib/acp/harnesses.js +8 -0
- package/dist/lib/agents.d.ts +4 -0
- package/dist/lib/agents.js +125 -34
- package/dist/lib/auto-pull-worker.js +18 -1
- package/dist/lib/browser/cdp.d.ts +8 -1
- package/dist/lib/browser/cdp.js +40 -3
- package/dist/lib/browser/chrome.d.ts +13 -0
- package/dist/lib/browser/chrome.js +46 -3
- package/dist/lib/browser/domain-skills.d.ts +51 -0
- package/dist/lib/browser/domain-skills.js +157 -0
- package/dist/lib/browser/drivers/local.js +45 -4
- package/dist/lib/browser/drivers/ssh.js +2 -2
- package/dist/lib/browser/ipc.d.ts +8 -1
- package/dist/lib/browser/ipc.js +37 -28
- package/dist/lib/browser/profiles.d.ts +16 -3
- package/dist/lib/browser/profiles.js +44 -4
- package/dist/lib/browser/service.d.ts +3 -0
- package/dist/lib/browser/service.js +40 -5
- package/dist/lib/browser/types.d.ts +11 -4
- package/dist/lib/cli-resources.d.ts +137 -0
- package/dist/lib/cli-resources.js +477 -0
- package/dist/lib/cloud/factory.d.ts +1 -1
- package/dist/lib/cloud/factory.js +1 -1
- package/dist/lib/cloud/rush.js +5 -5
- package/dist/lib/command-skills.js +0 -2
- package/dist/lib/computer-rpc.d.ts +3 -0
- package/dist/lib/computer-rpc.js +53 -0
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/events.d.ts +16 -2
- package/dist/lib/events.js +33 -2
- package/dist/lib/exec.d.ts +42 -13
- package/dist/lib/exec.js +127 -33
- 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 +246 -11
- package/dist/lib/mcp.d.ts +15 -0
- package/dist/lib/mcp.js +46 -0
- package/dist/lib/migrate.js +1 -1
- package/dist/lib/overdue.d.ts +26 -0
- package/dist/lib/overdue.js +101 -0
- package/dist/lib/permissions.d.ts +13 -0
- package/dist/lib/permissions.js +55 -1
- package/dist/lib/plugin-marketplace.js +1 -1
- package/dist/lib/plugins.js +15 -1
- package/dist/lib/profiles-presets.d.ts +26 -0
- package/dist/lib/profiles-presets.js +216 -0
- package/dist/lib/profiles.d.ts +34 -0
- package/dist/lib/profiles.js +112 -1
- package/dist/lib/resources/mcp.js +37 -0
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/rotate.js +10 -4
- package/dist/lib/routines-format.d.ts +47 -0
- package/dist/lib/routines-format.js +194 -0
- package/dist/lib/routines.d.ts +8 -2
- package/dist/lib/routines.js +34 -14
- package/dist/lib/runner.js +83 -15
- package/dist/lib/scheduler.js +8 -1
- 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 +34 -17
- package/dist/lib/secrets/bundles.js +210 -36
- package/dist/lib/secrets/index.d.ts +49 -30
- package/dist/lib/secrets/index.js +126 -115
- 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/active.d.ts +8 -0
- package/dist/lib/session/active.js +3 -2
- package/dist/lib/session/db.d.ts +0 -4
- package/dist/lib/session/db.js +0 -26
- package/dist/lib/session/parse.d.ts +1 -0
- package/dist/lib/session/parse.js +44 -0
- package/dist/lib/session/render.js +4 -4
- package/dist/lib/session/types.d.ts +2 -2
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +5 -2
- package/dist/lib/shims.js +70 -38
- package/dist/lib/state.d.ts +14 -2
- package/dist/lib/state.js +51 -20
- package/dist/lib/teams/agents.d.ts +5 -4
- package/dist/lib/teams/agents.js +48 -22
- package/dist/lib/teams/api.d.ts +2 -1
- package/dist/lib/teams/api.js +4 -3
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +153 -3
- package/dist/lib/teams/summarizer.js +18 -2
- package/dist/lib/teams/worktree.js +14 -3
- package/dist/lib/types.d.ts +63 -4
- package/dist/lib/types.js +8 -3
- package/dist/lib/usage.d.ts +27 -2
- package/dist/lib/usage.js +100 -17
- package/dist/lib/versions.d.ts +45 -3
- package/dist/lib/versions.js +455 -60
- package/package.json +15 -14
- 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/npm-shrinkwrap.json +0 -3162
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents feedback` — frictionless, in-CLI feedback. Opens a Discussion
|
|
3
|
+
* pre-filled with version + OS + agent inventory; falls back to printing the
|
|
4
|
+
* URL when no browser is available.
|
|
5
|
+
*/
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { arch, platform, release } from 'node:os';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
const REPO = 'phnx-labs/agents-cli';
|
|
11
|
+
const DISCUSSION_BASE = `https://github.com/${REPO}/discussions/new`;
|
|
12
|
+
const ISSUE_BASE = `https://github.com/${REPO}/issues/new`;
|
|
13
|
+
function readCliVersion() {
|
|
14
|
+
try {
|
|
15
|
+
const require = createRequire(import.meta.url);
|
|
16
|
+
const pkg = require('../../package.json');
|
|
17
|
+
return pkg.version ?? 'unknown';
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return 'unknown';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function openInBrowser(url) {
|
|
24
|
+
const openers = process.platform === 'darwin'
|
|
25
|
+
? [['open', [url]]]
|
|
26
|
+
: process.platform === 'win32'
|
|
27
|
+
? [['cmd', ['/c', 'start', '""', url]]]
|
|
28
|
+
: [
|
|
29
|
+
['xdg-open', [url]],
|
|
30
|
+
['gnome-open', [url]]
|
|
31
|
+
];
|
|
32
|
+
for (const [cmd, args] of openers) {
|
|
33
|
+
const r = spawnSync(cmd, args, { stdio: 'ignore' });
|
|
34
|
+
if (r.status === 0)
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
function buildPrefill(kind, summary) {
|
|
40
|
+
const version = readCliVersion();
|
|
41
|
+
const os = `${platform()} ${release()} (${arch()})`;
|
|
42
|
+
const node = process.version;
|
|
43
|
+
const body = [
|
|
44
|
+
summary.trim() ? `${summary.trim()}\n` : '<!-- describe what you ran into / what you want -->\n',
|
|
45
|
+
'---',
|
|
46
|
+
'',
|
|
47
|
+
'**Environment**',
|
|
48
|
+
'',
|
|
49
|
+
`- agents-cli: \`${version}\``,
|
|
50
|
+
`- OS: \`${os}\``,
|
|
51
|
+
`- Node: \`${node}\``,
|
|
52
|
+
'',
|
|
53
|
+
'<!-- For bugs: include the exact command you ran and the full output. -->',
|
|
54
|
+
'<!-- For ideas: include a concrete use case. -->'
|
|
55
|
+
].join('\n');
|
|
56
|
+
if (kind === 'bug') {
|
|
57
|
+
const url = `${ISSUE_BASE}?template=bug_report.yml&title=${encodeURIComponent(summary || 'Bug: ')}`;
|
|
58
|
+
return { url, body };
|
|
59
|
+
}
|
|
60
|
+
const category = kind === 'idea' ? 'ideas' : 'q-a';
|
|
61
|
+
const url = `${DISCUSSION_BASE}?category=${category}&title=${encodeURIComponent(summary || '')}&body=${encodeURIComponent(body)}`;
|
|
62
|
+
return { url, body };
|
|
63
|
+
}
|
|
64
|
+
export function registerFeedbackCommand(program) {
|
|
65
|
+
program
|
|
66
|
+
.command('feedback [summary...]')
|
|
67
|
+
.description('Open a pre-filled feedback Discussion or bug report')
|
|
68
|
+
.option('-b, --bug', 'File as a bug report (opens issue tracker)')
|
|
69
|
+
.option('-i, --idea', 'File as a feature idea (Discussions → Ideas)')
|
|
70
|
+
.option('-q, --question', 'Ask a question (Discussions → Q&A)')
|
|
71
|
+
.option('--print', 'Print the URL instead of opening it')
|
|
72
|
+
.action((summary, opts) => {
|
|
73
|
+
const kind = opts.bug ? 'bug' : opts.idea ? 'idea' : 'question';
|
|
74
|
+
const summaryText = (summary ?? []).join(' ').trim();
|
|
75
|
+
const { url } = buildPrefill(kind, summaryText);
|
|
76
|
+
if (opts.print) {
|
|
77
|
+
console.log(url);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const opened = openInBrowser(url);
|
|
81
|
+
if (opened) {
|
|
82
|
+
console.log(chalk.dim(`Opened ${kind} form in your browser:\n ${url}`));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.log(chalk.yellow('Could not auto-open a browser. Paste this URL:'));
|
|
86
|
+
console.log(` ${url}`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents helper` -- install, inspect, and reinstall the signed macOS
|
|
3
|
+
* Keychain helper at the stable user path.
|
|
4
|
+
*
|
|
5
|
+
* The signed `Agents CLI.app` ships inside the npm package, but its keychain
|
|
6
|
+
* ACLs need a stable signature-pinned location to survive `npm i -g` and
|
|
7
|
+
* version bumps. This command copies it to
|
|
8
|
+
* `~/Library/Application Support/agents-cli/` once and lets users force a
|
|
9
|
+
* reinstall when the trusted-app ACL needs to be re-established.
|
|
10
|
+
*/
|
|
11
|
+
import type { Command } from 'commander';
|
|
12
|
+
export declare function registerHelperCommand(program: Command): void;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents helper` -- install, inspect, and reinstall the signed macOS
|
|
3
|
+
* Keychain helper at the stable user path.
|
|
4
|
+
*
|
|
5
|
+
* The signed `Agents CLI.app` ships inside the npm package, but its keychain
|
|
6
|
+
* ACLs need a stable signature-pinned location to survive `npm i -g` and
|
|
7
|
+
* version bumps. This command copies it to
|
|
8
|
+
* `~/Library/Application Support/agents-cli/` once and lets users force a
|
|
9
|
+
* reinstall when the trusted-app ACL needs to be re-established.
|
|
10
|
+
*/
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import { ensureKeychainHelperInstalled, getKeychainHelperPath, getKeychainHelperStatus, } from '../lib/secrets/install-helper.js';
|
|
13
|
+
function requireDarwin() {
|
|
14
|
+
if (process.platform !== 'darwin') {
|
|
15
|
+
console.error(chalk.red('agents helper: macOS only.'));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function registerHelperCommand(program) {
|
|
20
|
+
const cmd = program
|
|
21
|
+
.command('helper')
|
|
22
|
+
.description('Manage the signed macOS Keychain helper (.app) install');
|
|
23
|
+
cmd
|
|
24
|
+
.command('install')
|
|
25
|
+
.description('Copy the bundled .app to ~/Library/Application Support/agents-cli/')
|
|
26
|
+
.action(() => {
|
|
27
|
+
requireDarwin();
|
|
28
|
+
try {
|
|
29
|
+
ensureKeychainHelperInstalled({ forceReinstall: true });
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
console.error(chalk.red(err.message || String(err)));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
const s = getKeychainHelperStatus();
|
|
36
|
+
console.log(chalk.green('Installed:'), s.destination);
|
|
37
|
+
console.log(chalk.dim('codesign:'), s.codesignOk ? chalk.green('ok') : chalk.red(s.codesignOutput));
|
|
38
|
+
console.log(chalk.dim('spctl: '), s.spctlOk ? chalk.green('ok') : chalk.yellow(s.spctlOutput));
|
|
39
|
+
});
|
|
40
|
+
cmd
|
|
41
|
+
.command('update')
|
|
42
|
+
.description('Reinstall the .app, overwriting any existing copy (alias of install)')
|
|
43
|
+
.action(() => {
|
|
44
|
+
requireDarwin();
|
|
45
|
+
try {
|
|
46
|
+
ensureKeychainHelperInstalled({ forceReinstall: true });
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
console.error(chalk.red(err.message || String(err)));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const s = getKeychainHelperStatus();
|
|
53
|
+
console.log(chalk.green('Updated: '), s.destination);
|
|
54
|
+
console.log(chalk.dim('codesign:'), s.codesignOk ? chalk.green('ok') : chalk.red(s.codesignOutput));
|
|
55
|
+
console.log(chalk.dim('spctl: '), s.spctlOk ? chalk.green('ok') : chalk.yellow(s.spctlOutput));
|
|
56
|
+
});
|
|
57
|
+
cmd
|
|
58
|
+
.command('status')
|
|
59
|
+
.description('Show source, destination, codesign and notarization status')
|
|
60
|
+
.action(() => {
|
|
61
|
+
requireDarwin();
|
|
62
|
+
const s = getKeychainHelperStatus();
|
|
63
|
+
console.log(chalk.bold('Source: '), s.source ?? chalk.red('(not found)'));
|
|
64
|
+
console.log(chalk.bold('Destination:'), s.destination);
|
|
65
|
+
console.log(chalk.bold('Installed: '), s.installed ? chalk.green('yes') : chalk.yellow('no'));
|
|
66
|
+
if (s.installed) {
|
|
67
|
+
console.log(chalk.bold('codesign: '), s.codesignOk ? chalk.green('ok') : chalk.red(s.codesignOutput));
|
|
68
|
+
console.log(chalk.bold('spctl: '), s.spctlOk ? chalk.green('ok') : chalk.yellow(s.spctlOutput));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log(chalk.dim('Run `agents helper install` to copy the bundled .app to the destination.'));
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
cmd
|
|
75
|
+
.command('where')
|
|
76
|
+
.description('Print the absolute path to the installed helper executable')
|
|
77
|
+
.action(() => {
|
|
78
|
+
requireDarwin();
|
|
79
|
+
try {
|
|
80
|
+
console.log(getKeychainHelperPath());
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.error(chalk.red(err.message || String(err)));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
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.`));
|
|
@@ -508,17 +525,17 @@ Examples:
|
|
|
508
525
|
.action(() => {
|
|
509
526
|
console.error(chalk.red('"agents hooks sync" is gone.'));
|
|
510
527
|
console.error(chalk.gray('Sync runs automatically when you launch the agent.'));
|
|
511
|
-
console.error(chalk.gray('To remove orphans, use: agents prune hooks'));
|
|
528
|
+
console.error(chalk.gray('To remove orphans, use: agents prune cleanup hooks'));
|
|
512
529
|
process.exit(1);
|
|
513
530
|
});
|
|
514
|
-
// `hooks prune` moved to the top-level `agents prune` command.
|
|
531
|
+
// `hooks prune` moved to the top-level `agents prune cleanup` command.
|
|
515
532
|
hooksCmd
|
|
516
533
|
.command('prune', { hidden: true })
|
|
517
534
|
.allowUnknownOption()
|
|
518
535
|
.allowExcessArguments()
|
|
519
536
|
.action(() => {
|
|
520
537
|
console.error(chalk.red('"agents hooks prune" moved.'));
|
|
521
|
-
console.error(chalk.gray('Use: agents prune hooks (or `agents prune` for everything)'));
|
|
538
|
+
console.error(chalk.gray('Use: agents prune cleanup hooks (or `agents prune cleanup` for everything)'));
|
|
522
539
|
process.exit(1);
|
|
523
540
|
});
|
|
524
541
|
hooksCmd
|
|
@@ -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/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 = [];
|