@phnx-labs/agents-cli 1.19.2 → 1.20.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 +67 -0
- package/README.md +69 -9
- 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/commands.js +3 -3
- 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 +3 -3
- package/dist/commands/factory.d.ts +3 -14
- package/dist/commands/factory.js +3 -3
- package/dist/commands/hooks.js +3 -3
- package/dist/commands/plugins.js +11 -4
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/prune.js +39 -160
- package/dist/commands/pull.js +56 -3
- package/dist/commands/routines.js +106 -13
- package/dist/commands/secrets.js +5 -7
- 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 +3 -3
- package/dist/commands/teams.js +13 -0
- package/dist/commands/versions.d.ts +4 -3
- package/dist/commands/versions.js +131 -127
- package/dist/commands/view.js +12 -12
- package/dist/computer.js +0 -0
- package/dist/index.js +34 -6
- package/dist/lib/acp/harnesses.js +8 -0
- package/dist/lib/agents.js +110 -23
- 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 +42 -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 +1 -1
- package/dist/lib/browser/ipc.d.ts +8 -1
- package/dist/lib/browser/ipc.js +37 -28
- package/dist/lib/browser/profiles.d.ts +13 -0
- package/dist/lib/browser/profiles.js +41 -1
- package/dist/lib/browser/service.d.ts +3 -0
- package/dist/lib/browser/service.js +21 -5
- package/dist/lib/browser/types.d.ts +7 -0
- package/dist/lib/cli-resources.d.ts +109 -0
- package/dist/lib/cli-resources.js +255 -0
- 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/exec.d.ts +3 -2
- package/dist/lib/exec.js +44 -9
- package/dist/lib/hooks.js +182 -0
- package/dist/lib/mcp.js +6 -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.js +5 -1
- package/dist/lib/plugin-marketplace.js +1 -1
- package/dist/lib/profiles-presets.js +37 -0
- 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 +35 -0
- package/dist/lib/routines-format.js +173 -0
- package/dist/lib/routines.d.ts +7 -1
- package/dist/lib/routines.js +32 -12
- package/dist/lib/runner.js +19 -5
- 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/bundles.d.ts +22 -1
- package/dist/lib/secrets/bundles.js +234 -36
- package/dist/lib/secrets/index.d.ts +6 -11
- package/dist/lib/secrets/index.js +107 -87
- 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/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +1 -1
- package/dist/lib/shims.js +66 -4
- package/dist/lib/state.d.ts +0 -1
- package/dist/lib/state.js +2 -15
- package/dist/lib/teams/agents.js +1 -1
- 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 +6 -3
- package/dist/lib/types.js +6 -3
- package/dist/lib/versions.d.ts +10 -2
- package/dist/lib/versions.js +227 -35
- package/package.json +7 -7
- package/npm-shrinkwrap.json +0 -3162
|
@@ -383,17 +383,17 @@ Examples:
|
|
|
383
383
|
.action(() => {
|
|
384
384
|
console.error(chalk.red('"agents commands sync" is gone.'));
|
|
385
385
|
console.error(chalk.gray('Sync runs automatically when you launch the agent.'));
|
|
386
|
-
console.error(chalk.gray('To remove orphans, use: agents prune commands'));
|
|
386
|
+
console.error(chalk.gray('To remove orphans, use: agents prune cleanup commands'));
|
|
387
387
|
process.exit(1);
|
|
388
388
|
});
|
|
389
|
-
// `commands prune` moved to the top-level `agents prune` command.
|
|
389
|
+
// `commands prune` moved to the top-level `agents prune cleanup` command.
|
|
390
390
|
commandsCmd
|
|
391
391
|
.command('prune', { hidden: true })
|
|
392
392
|
.allowUnknownOption()
|
|
393
393
|
.allowExcessArguments()
|
|
394
394
|
.action(() => {
|
|
395
395
|
console.error(chalk.red('"agents commands prune" moved.'));
|
|
396
|
-
console.error(chalk.gray('Use: agents prune commands (or `agents prune` for everything)'));
|
|
396
|
+
console.error(chalk.gray('Use: agents prune cleanup commands (or `agents prune cleanup` for everything)'));
|
|
397
397
|
process.exit(1);
|
|
398
398
|
});
|
|
399
399
|
commandsCmd
|
|
@@ -3,7 +3,7 @@ import * as fs from 'fs';
|
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import { registerCommandGroups } from '../lib/help.js';
|
|
6
|
-
import { openComputerClient, resolveHelperApp, resolveHelperExec, resolveSocketPath, resolveLogPath, resolvePolicyPath, describeTransport, loadComputerAllowList, writeComputerPolicy, } from '../lib/computer-rpc.js';
|
|
6
|
+
import { openComputerClient, resolveHelperApp, resolveHelperExec, resolveSocketPath, resolveLogPath, resolvePolicyPath, resolvePeersPath, describeTransport, loadComputerAllowList, loadDefaultPeers, writeComputerPolicy, writeComputerPeers, } from '../lib/computer-rpc.js';
|
|
7
7
|
// Help groups — mirror `agents browser` so the mental model carries over.
|
|
8
8
|
const COMPUTER_HELP_GROUPS = [
|
|
9
9
|
{ title: 'Installation', names: ['install-helper'] },
|
|
@@ -46,6 +46,8 @@ function registerStatusCommand(program) {
|
|
|
46
46
|
const previewParts = allowed.slice(0, 5);
|
|
47
47
|
const previewSuffix = allowed.length > 5 ? ` (+${allowed.length - 5} more)` : '';
|
|
48
48
|
console.log(`policy: ${allowed.length} app${allowed.length === 1 ? '' : 's'} allowed${allowed.length > 0 ? `: ${previewParts.join(', ')}${previewSuffix}` : ''}`);
|
|
49
|
+
const callers = loadDefaultPeers();
|
|
50
|
+
console.log(`peers: ${callers.length} caller${callers.length === 1 ? '' : 's'} (peer-auth on socket)`);
|
|
49
51
|
if (!installed) {
|
|
50
52
|
console.log('');
|
|
51
53
|
console.log('Run: agents computer install-helper');
|
|
@@ -284,6 +286,15 @@ function registerStartCommand(program) {
|
|
|
284
286
|
console.log(` add to ~/.agents/permissions/groups/<name>.yaml under allow:`);
|
|
285
287
|
console.log(` - "Computer(com.apple.finder)"`);
|
|
286
288
|
}
|
|
289
|
+
// Peer-auth allow list — which caller executables may connect to
|
|
290
|
+
// the socket. Default: this CLI's Node binary, plus Rush.app if
|
|
291
|
+
// installed. A `nc -U socket` from a malicious npm postinstall has
|
|
292
|
+
// a different exec path and gets refused at accept().
|
|
293
|
+
const callers = loadDefaultPeers();
|
|
294
|
+
writeComputerPeers(callers);
|
|
295
|
+
console.log(`peers: ${callers.length} caller${callers.length === 1 ? '' : 's'} allowed (${resolvePeersPath()})`);
|
|
296
|
+
for (const p of callers)
|
|
297
|
+
console.log(` ${p}`);
|
|
287
298
|
// Bootout first to clear any prior registration. Best-effort.
|
|
288
299
|
try {
|
|
289
300
|
execFileSync('/bin/launchctl', ['bootout', domain, plistPath], { stdio: 'pipe' });
|
|
@@ -356,6 +367,12 @@ function registerReloadCommand(program) {
|
|
|
356
367
|
const allowed = loadComputerAllowList();
|
|
357
368
|
writeComputerPolicy(allowed);
|
|
358
369
|
console.log(`policy: ${allowed.length} app${allowed.length === 1 ? '' : 's'} allowed (${resolvePolicyPath()})`);
|
|
370
|
+
// Rewrite peers list too — an upgrade of the npm-global CLI moves
|
|
371
|
+
// its node path; without this the reloaded daemon would reject the
|
|
372
|
+
// very binary that just signaled it.
|
|
373
|
+
const callers = loadDefaultPeers();
|
|
374
|
+
writeComputerPeers(callers);
|
|
375
|
+
console.log(`peers: ${callers.length} caller${callers.length === 1 ? '' : 's'} allowed (${resolvePeersPath()})`);
|
|
359
376
|
// Resolve the daemon's pid via `launchctl list <label>`. The plist
|
|
360
377
|
// output includes a "PID" key when the service is running.
|
|
361
378
|
const uid = process.getuid?.();
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* unified diff body for each divergent file. Mirrors the resolution that
|
|
16
16
|
* the shim drives at runtime: project > user > system > extras.
|
|
17
17
|
*
|
|
18
|
-
* Read-only: doctor never mutates state. Run `agents prune` to act on orphan
|
|
18
|
+
* Read-only: doctor never mutates state. Run `agents prune cleanup` to act on orphan
|
|
19
19
|
* readouts, or just launch the agent to apply pending sync.
|
|
20
20
|
*/
|
|
21
21
|
import type { Command } from 'commander';
|
package/dist/commands/doctor.js
CHANGED
|
@@ -114,7 +114,7 @@ function renderOverviewText(clis, syncRows, orphanRows) {
|
|
|
114
114
|
const label = `${AGENT_NAMES[row.agent] || row.agent}@${row.version}`;
|
|
115
115
|
console.log(` ${chalk.yellow('warn ')} ${label} ${chalk.gray(parts.join(', '))}`);
|
|
116
116
|
}
|
|
117
|
-
console.log(chalk.gray(' Run `agents prune` to remove.'));
|
|
117
|
+
console.log(chalk.gray(' Run `agents prune cleanup` to remove.'));
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
function parseTargetArg(arg) {
|
|
@@ -310,7 +310,7 @@ function renderTargetText(report, options) {
|
|
|
310
310
|
}
|
|
311
311
|
else {
|
|
312
312
|
console.log(` Verdict: ${verdictParts.join(', ')}.`);
|
|
313
|
-
console.log(chalk.gray(` Run \`agents sync --agent ${report.agent} --agent-version ${report.version}\` to reconcile, or \`agents prune\` to drop extras.`));
|
|
313
|
+
console.log(chalk.gray(` Run \`agents sync --agent ${report.agent} --agent-version ${report.version}\` to reconcile, or \`agents prune cleanup\` to drop extras.`));
|
|
314
314
|
}
|
|
315
315
|
}
|
|
316
316
|
// ─── command registration ────────────────────────────────────────────────────
|
package/dist/commands/exec.js
CHANGED
|
@@ -9,7 +9,7 @@ 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
11
|
import { setHelpSections } from '../lib/help.js';
|
|
12
|
-
import {
|
|
12
|
+
import { readAndResolveBundleEnv, describeBundle } from '../lib/secrets/bundles.js';
|
|
13
13
|
import { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, RUN_STRATEGIES, } from '../lib/rotate.js';
|
|
14
14
|
import { getGlobalDefault, getVersionHomePath, resolveVersionAlias } from '../lib/versions.js';
|
|
15
15
|
import { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion } from '../lib/plugins.js';
|
|
@@ -268,7 +268,7 @@ export function registerRunCommand(program) {
|
|
|
268
268
|
let secretsEnv = {};
|
|
269
269
|
for (const bundleName of options.secrets) {
|
|
270
270
|
try {
|
|
271
|
-
const bundle =
|
|
271
|
+
const { bundle, env: bundleEnv } = readAndResolveBundleEnv(bundleName, { caller: `agent ${agent}` });
|
|
272
272
|
const entries = describeBundle(bundle);
|
|
273
273
|
const counts = {};
|
|
274
274
|
for (const e of entries) {
|
|
@@ -276,7 +276,7 @@ export function registerRunCommand(program) {
|
|
|
276
276
|
}
|
|
277
277
|
const breakdown = Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(', ');
|
|
278
278
|
console.log(chalk.gray(`[secrets] Resolved ${bundleName}: ${entries.length} keys (${breakdown})`));
|
|
279
|
-
secretsEnv = { ...secretsEnv, ...
|
|
279
|
+
secretsEnv = { ...secretsEnv, ...bundleEnv };
|
|
280
280
|
}
|
|
281
281
|
catch (err) {
|
|
282
282
|
console.error(chalk.red(err.message));
|
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Software Factory CLI
|
|
2
|
+
* Software Factory CLI — submits Linear issues to a remote orchestrator.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* agents factory submit <linear-ref> POST /factory/submit
|
|
7
|
-
*
|
|
8
|
-
* Everything else (planner pod, worker dispatch, PR-merged/CI-failed
|
|
9
|
-
* webhooks, retry caps, heartbeat reaper) lives server-side in
|
|
10
|
-
* `agents/prix/factory/service/src/factory.ts`, driven by the
|
|
11
|
-
* `factory-tick` k8s CronJob. The laptop is optional after submit.
|
|
12
|
-
*
|
|
13
|
-
* Future verbs (list / status / tail / cancel / message) are intentionally
|
|
14
|
-
* deferred until the matching server endpoints land; they'll be thin
|
|
15
|
-
* clients too. No supervisor, ledger, oracle, or `~/.agents/factory/`
|
|
16
|
-
* registry on the laptop -- ever.
|
|
4
|
+
* Requires FACTORY_FLOOR_URL pointing at a Factory-compatible endpoint.
|
|
5
|
+
* Beta-gated; enable with `agents beta enable factory`.
|
|
17
6
|
*/
|
|
18
7
|
import type { Command } from 'commander';
|
|
19
8
|
export declare function registerFactoryCommands(program: Command): void;
|
package/dist/commands/factory.js
CHANGED
|
@@ -51,8 +51,8 @@ export function registerFactoryCommands(program) {
|
|
|
51
51
|
.description('Software Factory -- submit Linear tickets to the cloud orchestrator.')
|
|
52
52
|
.addHelpText('after', `
|
|
53
53
|
Examples:
|
|
54
|
-
agents factory submit
|
|
55
|
-
agents factory submit https://linear.app/example/issue/
|
|
54
|
+
agents factory submit PROJ-123
|
|
55
|
+
agents factory submit https://linear.app/example/issue/PROJ-123
|
|
56
56
|
`);
|
|
57
57
|
factory.hook('preAction', () => {
|
|
58
58
|
if (enabled)
|
|
@@ -63,7 +63,7 @@ Examples:
|
|
|
63
63
|
});
|
|
64
64
|
factory
|
|
65
65
|
.command('submit <linear-ref>')
|
|
66
|
-
.description('Submit a Linear issue (
|
|
66
|
+
.description('Submit a Linear issue (PROJ-123 or URL) to the Software Factory.')
|
|
67
67
|
.option('--json', 'Output machine-readable JSON')
|
|
68
68
|
.action(async (ref, opts) => {
|
|
69
69
|
const result = await postFactorySubmit(ref);
|
package/dist/commands/hooks.js
CHANGED
|
@@ -508,17 +508,17 @@ Examples:
|
|
|
508
508
|
.action(() => {
|
|
509
509
|
console.error(chalk.red('"agents hooks sync" is gone.'));
|
|
510
510
|
console.error(chalk.gray('Sync runs automatically when you launch the agent.'));
|
|
511
|
-
console.error(chalk.gray('To remove orphans, use: agents prune hooks'));
|
|
511
|
+
console.error(chalk.gray('To remove orphans, use: agents prune cleanup hooks'));
|
|
512
512
|
process.exit(1);
|
|
513
513
|
});
|
|
514
|
-
// `hooks prune` moved to the top-level `agents prune` command.
|
|
514
|
+
// `hooks prune` moved to the top-level `agents prune cleanup` command.
|
|
515
515
|
hooksCmd
|
|
516
516
|
.command('prune', { hidden: true })
|
|
517
517
|
.allowUnknownOption()
|
|
518
518
|
.allowExcessArguments()
|
|
519
519
|
.action(() => {
|
|
520
520
|
console.error(chalk.red('"agents hooks prune" moved.'));
|
|
521
|
-
console.error(chalk.gray('Use: agents prune hooks (or `agents prune` for everything)'));
|
|
521
|
+
console.error(chalk.gray('Use: agents prune cleanup hooks (or `agents prune cleanup` for everything)'));
|
|
522
522
|
process.exit(1);
|
|
523
523
|
});
|
|
524
524
|
hooksCmd
|
package/dist/commands/plugins.js
CHANGED
|
@@ -237,6 +237,7 @@ Examples:
|
|
|
237
237
|
pluginsCmd
|
|
238
238
|
.command('sync <name> [agent]')
|
|
239
239
|
.description('Apply a plugin to the default version of an agent (or all supported agents if none specified)')
|
|
240
|
+
.option('--allow-exec-surfaces', 'Enable the plugin even when it ships hooks/, .mcp.json, bin/, scripts/, settings.json, or permissions/')
|
|
240
241
|
.addHelpText('after', `
|
|
241
242
|
Examples:
|
|
242
243
|
# Sync a plugin to a specific agent (default version)
|
|
@@ -244,8 +245,11 @@ Examples:
|
|
|
244
245
|
|
|
245
246
|
# Sync to all supported agents
|
|
246
247
|
agents plugins sync rush-toolkit
|
|
248
|
+
|
|
249
|
+
# Re-affirm consent for a hooks-bearing plugin
|
|
250
|
+
agents plugins sync hivemind claude --allow-exec-surfaces
|
|
247
251
|
`)
|
|
248
|
-
.action(async (name, agentArg) => {
|
|
252
|
+
.action(async (name, agentArg, options) => {
|
|
249
253
|
const plugin = getPlugin(name);
|
|
250
254
|
if (!plugin) {
|
|
251
255
|
console.log(chalk.red(`Plugin '${name}' not found`));
|
|
@@ -268,6 +272,7 @@ Examples:
|
|
|
268
272
|
else {
|
|
269
273
|
targetAgents = PLUGINS_CAPABLE_AGENTS.filter(a => pluginSupportsAgent(plugin, a));
|
|
270
274
|
}
|
|
275
|
+
const allowExec = options.allowExecSurfaces === true;
|
|
271
276
|
for (const agentId of targetAgents) {
|
|
272
277
|
const versions = listInstalledVersions(agentId);
|
|
273
278
|
if (versions.length === 0)
|
|
@@ -275,9 +280,11 @@ Examples:
|
|
|
275
280
|
const defaultVer = getGlobalDefault(agentId);
|
|
276
281
|
const targetVersions = defaultVer ? [defaultVer] : [versions[versions.length - 1]];
|
|
277
282
|
for (const version of targetVersions) {
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
283
|
+
const didSync = allowExec
|
|
284
|
+
? syncPluginToVersion(plugin, agentId, getVersionHomePath(agentId, version), { allowExecSurfaces: true, version }).success
|
|
285
|
+
: syncResourcesToVersion(agentId, version, { plugins: [name] }).plugins.length > 0;
|
|
286
|
+
if (didSync) {
|
|
287
|
+
console.log(chalk.green(`Synced ${name} to ${agentLabel(agentId)}@${version}${allowExec ? ' (exec surfaces enabled)' : ''}`));
|
|
281
288
|
}
|
|
282
289
|
else {
|
|
283
290
|
console.log(chalk.gray(`${name} already synced to ${agentLabel(agentId)}@${version}`));
|
|
@@ -116,7 +116,7 @@ Examples:
|
|
|
116
116
|
|
|
117
117
|
# Add MiniMax for SWE-bench style fixes; reuses the same OpenRouter key
|
|
118
118
|
agents profiles add minimax
|
|
119
|
-
agents run minimax "investigate
|
|
119
|
+
agents run minimax "investigate PROJ-456 and patch the off-by-one in pagination"
|
|
120
120
|
|
|
121
121
|
# Add DeepSeek for cheap, fast non-reasoning work
|
|
122
122
|
agents profiles add deepseek
|
package/dist/commands/prune.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* `agents prune cleanup` — destructive cleanup across the install.
|
|
3
3
|
*
|
|
4
4
|
* Cleanup targets:
|
|
5
5
|
* - Resource orphans: command/skill/hook files inside a version home that no
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* into the version install).
|
|
8
8
|
* - Version duplicates: older installed versions of an agent that share an
|
|
9
9
|
* account with a newer installed version of the same agent.
|
|
10
|
-
* - Trash
|
|
11
|
-
*
|
|
10
|
+
* - Trash/session targets are retained as no-op compatibility shims: version
|
|
11
|
+
* homes and session history are durable and must not be hard-deleted by
|
|
12
|
+
* agents-cli.
|
|
12
13
|
* - Runs: routine execution logs, keeping only the last N per job.
|
|
13
14
|
*
|
|
14
15
|
* Sync (additive: copy missing/changed files into version homes) is no longer
|
|
@@ -21,7 +22,6 @@
|
|
|
21
22
|
* to widen orphan cleanup to every installed version.
|
|
22
23
|
*/
|
|
23
24
|
import * as fs from 'fs';
|
|
24
|
-
import * as path from 'path';
|
|
25
25
|
import chalk from 'chalk';
|
|
26
26
|
import { confirm } from '@inquirer/prompts';
|
|
27
27
|
import { diffVersionCommands, iterCommandsCapableVersions, removeCommandFromVersion, } from '../lib/commands.js';
|
|
@@ -34,7 +34,6 @@ import { resolveAgentName, formatAgentError } from '../lib/agents.js';
|
|
|
34
34
|
import { pruneDuplicates } from './view.js';
|
|
35
35
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
36
36
|
import { getTrashDir } from '../lib/state.js';
|
|
37
|
-
import { countSessionsOlderThan, deleteSessionsOlderThan } from '../lib/session/db.js';
|
|
38
37
|
import { previewRunsPrune, pruneRuns, countAllRuns } from '../lib/routines.js';
|
|
39
38
|
const RESOURCE_TYPES = ['commands', 'skills', 'hooks', 'plugins', 'subagents'];
|
|
40
39
|
const STATE_TYPES = ['trash', 'sessions', 'runs'];
|
|
@@ -125,12 +124,6 @@ function parseTarget(arg) {
|
|
|
125
124
|
console.log(chalk.gray(formatAgentError(arg)));
|
|
126
125
|
process.exit(1);
|
|
127
126
|
}
|
|
128
|
-
function parseDays(value, defaultDays) {
|
|
129
|
-
const match = value.match(/^(\d+)d?$/);
|
|
130
|
-
if (match)
|
|
131
|
-
return parseInt(match[1], 10);
|
|
132
|
-
return defaultDays;
|
|
133
|
-
}
|
|
134
127
|
function formatBytes(bytes) {
|
|
135
128
|
if (bytes < 1024)
|
|
136
129
|
return `${bytes} B`;
|
|
@@ -140,137 +133,25 @@ function formatBytes(bytes) {
|
|
|
140
133
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
141
134
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
142
135
|
}
|
|
143
|
-
function getDirSize(dirPath) {
|
|
144
|
-
if (!fs.existsSync(dirPath))
|
|
145
|
-
return 0;
|
|
146
|
-
let size = 0;
|
|
147
|
-
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
148
|
-
for (const entry of entries) {
|
|
149
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
150
|
-
if (entry.isDirectory()) {
|
|
151
|
-
size += getDirSize(fullPath);
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
try {
|
|
155
|
-
size += fs.statSync(fullPath).size;
|
|
156
|
-
}
|
|
157
|
-
catch { /* ignore */ }
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return size;
|
|
161
|
-
}
|
|
162
136
|
async function runTrashPrune(options) {
|
|
163
137
|
const trashDir = getTrashDir();
|
|
164
138
|
if (!fs.existsSync(trashDir)) {
|
|
165
139
|
console.log(chalk.green('Trash is empty.'));
|
|
166
140
|
return;
|
|
167
141
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const toPrune = [];
|
|
171
|
-
function scanDir(dir) {
|
|
172
|
-
if (!fs.existsSync(dir))
|
|
173
|
-
return;
|
|
174
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
175
|
-
const fullPath = path.join(dir, entry.name);
|
|
176
|
-
try {
|
|
177
|
-
const stat = fs.statSync(fullPath);
|
|
178
|
-
if (stat.mtimeMs < cutoffMs) {
|
|
179
|
-
toPrune.push({ path: fullPath, mtime: stat.mtimeMs, size: entry.isDirectory() ? getDirSize(fullPath) : stat.size });
|
|
180
|
-
}
|
|
181
|
-
else if (entry.isDirectory()) {
|
|
182
|
-
scanDir(fullPath);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
catch { /* skip inaccessible */ }
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
scanDir(trashDir);
|
|
189
|
-
if (toPrune.length === 0) {
|
|
190
|
-
console.log(chalk.green(`No trash entries older than ${days} days.`));
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
const totalSize = toPrune.reduce((sum, e) => sum + e.size, 0);
|
|
194
|
-
console.log(chalk.bold(`Trash entries older than ${days} days\n`));
|
|
195
|
-
for (const entry of toPrune.slice(0, 20)) {
|
|
196
|
-
const age = Math.floor((Date.now() - entry.mtime) / (24 * 60 * 60 * 1000));
|
|
197
|
-
console.log(` ${chalk.gray(`${age}d ago`)} ${path.relative(trashDir, entry.path)}`);
|
|
198
|
-
}
|
|
199
|
-
if (toPrune.length > 20) {
|
|
200
|
-
console.log(chalk.gray(` ... and ${toPrune.length - 20} more`));
|
|
201
|
-
}
|
|
202
|
-
console.log();
|
|
203
|
-
if (options.dryRun) {
|
|
204
|
-
console.log(chalk.gray(`${toPrune.length} entries (${formatBytes(totalSize)}). Run without --dry-run to delete.`));
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
if (!options.yes) {
|
|
208
|
-
if (!isInteractiveTerminal()) {
|
|
209
|
-
console.log(chalk.yellow('Non-interactive shell: pass -y to confirm, or --dry-run to preview.'));
|
|
210
|
-
process.exit(1);
|
|
211
|
-
}
|
|
212
|
-
let ok = false;
|
|
213
|
-
try {
|
|
214
|
-
ok = await confirm({ message: `Delete ${toPrune.length} entries (${formatBytes(totalSize)})?`, default: false });
|
|
215
|
-
}
|
|
216
|
-
catch (err) {
|
|
217
|
-
if (isPromptCancelled(err)) {
|
|
218
|
-
console.log(chalk.gray('Cancelled'));
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
throw err;
|
|
222
|
-
}
|
|
223
|
-
if (!ok) {
|
|
224
|
-
console.log(chalk.gray('Cancelled'));
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
let deleted = 0;
|
|
229
|
-
for (const entry of toPrune) {
|
|
230
|
-
try {
|
|
231
|
-
fs.rmSync(entry.path, { recursive: true, force: true });
|
|
232
|
-
deleted++;
|
|
233
|
-
}
|
|
234
|
-
catch { /* ignore */ }
|
|
142
|
+
if (options.olderThan || options.yes || options.dryRun) {
|
|
143
|
+
console.log(chalk.gray('Trash expiry flags are accepted for compatibility but do not delete data.'));
|
|
235
144
|
}
|
|
236
|
-
console.log(chalk.
|
|
145
|
+
console.log(chalk.yellow('Trash is durable. agents-cli does not hard-delete soft-deleted version data.'));
|
|
146
|
+
console.log(chalk.gray('Inspect recoverable versions with: agents trash list'));
|
|
147
|
+
console.log(chalk.gray(`Trash path: ${trashDir}`));
|
|
237
148
|
}
|
|
238
149
|
async function runSessionsPrune(options) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const count = countSessionsOlderThan(cutoffMs);
|
|
242
|
-
if (count === 0) {
|
|
243
|
-
console.log(chalk.green(`No sessions older than ${days} days.`));
|
|
244
|
-
return;
|
|
150
|
+
if (options.olderThan || options.yes || options.dryRun) {
|
|
151
|
+
console.log(chalk.gray('Session prune flags are accepted for compatibility but do not delete data.'));
|
|
245
152
|
}
|
|
246
|
-
console.log(chalk.
|
|
247
|
-
|
|
248
|
-
console.log(chalk.gray(`${count} session(s). Run without --dry-run to delete.`));
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
if (!options.yes) {
|
|
252
|
-
if (!isInteractiveTerminal()) {
|
|
253
|
-
console.log(chalk.yellow('Non-interactive shell: pass -y to confirm, or --dry-run to preview.'));
|
|
254
|
-
process.exit(1);
|
|
255
|
-
}
|
|
256
|
-
let ok = false;
|
|
257
|
-
try {
|
|
258
|
-
ok = await confirm({ message: `Delete ${count} session records?`, default: false });
|
|
259
|
-
}
|
|
260
|
-
catch (err) {
|
|
261
|
-
if (isPromptCancelled(err)) {
|
|
262
|
-
console.log(chalk.gray('Cancelled'));
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
throw err;
|
|
266
|
-
}
|
|
267
|
-
if (!ok) {
|
|
268
|
-
console.log(chalk.gray('Cancelled'));
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
const deleted = deleteSessionsOlderThan(cutoffMs);
|
|
273
|
-
console.log(chalk.green(`Pruned ${deleted} session records.`));
|
|
153
|
+
console.log(chalk.yellow('Session history is durable. agents-cli does not hard-delete session records.'));
|
|
154
|
+
console.log(chalk.gray('Browse sessions with: agents sessions'));
|
|
274
155
|
}
|
|
275
156
|
async function runRunsPrune(options) {
|
|
276
157
|
const keep = options.keep ? parseInt(options.keep, 10) : 10;
|
|
@@ -380,13 +261,16 @@ async function runOrphanPrune(resourceTypes, options) {
|
|
|
380
261
|
console.log(chalk.green(summary) + (failures > 0 ? chalk.red(`, ${failures} failed`) : '') + '.');
|
|
381
262
|
}
|
|
382
263
|
export function registerPruneCommand(program) {
|
|
383
|
-
program
|
|
384
|
-
.command('prune
|
|
385
|
-
.description('
|
|
264
|
+
const pruneCmd = program.commands.find((cmd) => cmd.name() === 'prune') ?? program
|
|
265
|
+
.command('prune <specs...>')
|
|
266
|
+
.description('Uninstall agent CLI versions. Moves version data to trash for recovery.');
|
|
267
|
+
pruneCmd
|
|
268
|
+
.command('cleanup [target]')
|
|
269
|
+
.description('Remove orphan resources, old versions, or routine runs')
|
|
386
270
|
.option('--all', 'For orphan cleanup: sweep every installed version (default: current default version per agent)')
|
|
387
271
|
.option('--dry-run', 'Show what would be removed without deleting (default for state targets)')
|
|
388
272
|
.option('-y, --yes', 'Skip confirmation prompt')
|
|
389
|
-
.option('--older-than <days>', '
|
|
273
|
+
.option('--older-than <days>', 'Deprecated for trash/sessions; accepted but no data is deleted')
|
|
390
274
|
.option('--keep <n>', 'For runs: keep the last N runs per job (default: 10)')
|
|
391
275
|
.addHelpText('after', `
|
|
392
276
|
Targets:
|
|
@@ -396,46 +280,40 @@ Targets:
|
|
|
396
280
|
hooks Orphan hook scripts only
|
|
397
281
|
versions Older duplicate version installs only
|
|
398
282
|
<agent> Older duplicate versions for one agent (e.g. 'claude')
|
|
399
|
-
trash
|
|
400
|
-
sessions
|
|
283
|
+
trash No-op compatibility target; trash is durable
|
|
284
|
+
sessions No-op compatibility target; session history is durable
|
|
401
285
|
runs Routine execution logs, keeping only --keep per job (default 10)
|
|
402
286
|
|
|
403
287
|
Examples:
|
|
404
288
|
# Full sweep: orphan resources + duplicate versions for current defaults
|
|
405
|
-
agents prune
|
|
289
|
+
agents prune cleanup
|
|
406
290
|
|
|
407
291
|
# Preview what a full sweep would remove
|
|
408
|
-
agents prune --dry-run
|
|
292
|
+
agents prune cleanup --dry-run
|
|
409
293
|
|
|
410
294
|
# Just orphan skills
|
|
411
|
-
agents prune skills
|
|
295
|
+
agents prune cleanup skills
|
|
412
296
|
|
|
413
297
|
# Just version dedup
|
|
414
|
-
agents prune versions
|
|
298
|
+
agents prune cleanup versions
|
|
415
299
|
|
|
416
300
|
# Deduplicate versions for one agent only
|
|
417
|
-
agents prune claude
|
|
301
|
+
agents prune cleanup claude
|
|
418
302
|
|
|
419
303
|
# Sweep every installed version's orphans, not only the defaults
|
|
420
|
-
agents prune --all
|
|
421
|
-
|
|
422
|
-
# Preview trash entries older than 30 days
|
|
423
|
-
agents prune trash --dry-run
|
|
424
|
-
|
|
425
|
-
# Delete trash entries older than 60 days
|
|
426
|
-
agents prune trash --older-than 60 -y
|
|
304
|
+
agents prune cleanup --all
|
|
427
305
|
|
|
428
|
-
#
|
|
429
|
-
agents prune
|
|
306
|
+
# Show the durable-trash notice
|
|
307
|
+
agents prune cleanup trash --dry-run
|
|
430
308
|
|
|
431
|
-
#
|
|
432
|
-
agents prune sessions --
|
|
309
|
+
# Show the durable-session notice
|
|
310
|
+
agents prune cleanup sessions --dry-run
|
|
433
311
|
|
|
434
312
|
# Preview runs cleanup (keeping last 10)
|
|
435
|
-
agents prune runs --dry-run
|
|
313
|
+
agents prune cleanup runs --dry-run
|
|
436
314
|
|
|
437
315
|
# Keep only the last 5 runs per job
|
|
438
|
-
agents prune runs --keep 5 -y
|
|
316
|
+
agents prune cleanup runs --keep 5 -y
|
|
439
317
|
|
|
440
318
|
What's an orphan?
|
|
441
319
|
A command, skill, or hook present inside a version home but missing from every
|
|
@@ -443,10 +321,11 @@ What's an orphan?
|
|
|
443
321
|
repos). Usually leftovers from a resource that was deleted or moved but never
|
|
444
322
|
reconciled into the version install.
|
|
445
323
|
|
|
446
|
-
|
|
447
|
-
Version directories are NEVER hard-deleted.
|
|
448
|
-
~/.agents/.trash/versions/<agent>/<version>/<timestamp>/.
|
|
449
|
-
|
|
324
|
+
Durability:
|
|
325
|
+
Version directories are NEVER hard-deleted by agents-cli. Version prune and
|
|
326
|
+
cleanup move them to ~/.agents/.history/trash/versions/<agent>/<version>/<timestamp>/.
|
|
327
|
+
Session records are also durable; the sessions target remains only as a no-op
|
|
328
|
+
compatibility shim.
|
|
450
329
|
`)
|
|
451
330
|
.action(async (target, options) => {
|
|
452
331
|
const parsed = parseTarget(target);
|
package/dist/commands/pull.js
CHANGED
|
@@ -14,10 +14,11 @@ import { isGitRepo, pullRepo, isSystemRepoOrigin, } from '../lib/git.js';
|
|
|
14
14
|
import * as fs from 'fs';
|
|
15
15
|
import * as path from 'path';
|
|
16
16
|
import { installVersion, listInstalledVersions, getGlobalDefault, setGlobalDefault, getVersionHomePath, syncResourcesToVersion, getAvailableResources, getActuallySyncedResources, getNewResources, hasNewResources, promptNewResourceSelection, promptResourceSelection, resolveConfiguredAgentTargets, } from '../lib/versions.js';
|
|
17
|
+
import { listCliStatus, installCli, describeMethod, selectInstallMethod, } from '../lib/cli-resources.js';
|
|
17
18
|
import { ensureShimCurrent, isShimsInPath, addShimsToPath, getPathSetupInstructions, switchConfigSymlink, switchHomeFileSymlinks, } from '../lib/shims.js';
|
|
18
19
|
import { parseHookManifest, registerHooksToSettings } from '../lib/hooks.js';
|
|
19
20
|
import { setHelpSections } from '../lib/help.js';
|
|
20
|
-
import { select } from '@inquirer/prompts';
|
|
21
|
+
import { select, confirm } from '@inquirer/prompts';
|
|
21
22
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
22
23
|
/**
|
|
23
24
|
* Old repo layout stored promptcuts under claude/promptcuts.yaml (agent-scoped).
|
|
@@ -244,10 +245,10 @@ export function registerPullCommand(program) {
|
|
|
244
245
|
if (userSelection)
|
|
245
246
|
selection = userSelection;
|
|
246
247
|
}
|
|
247
|
-
else if (hasNewResources(newResources, agentId)) {
|
|
248
|
+
else if (hasNewResources(newResources, agentId, defaultVer)) {
|
|
248
249
|
// Has synced before, but NEW items available
|
|
249
250
|
console.log(chalk.cyan(`\n${agentLabel(agentId)}@${defaultVer}:`));
|
|
250
|
-
const userSelection = await promptNewResourceSelection(agentId, newResources);
|
|
251
|
+
const userSelection = await promptNewResourceSelection(agentId, newResources, defaultVer);
|
|
251
252
|
if (userSelection)
|
|
252
253
|
selection = userSelection;
|
|
253
254
|
}
|
|
@@ -366,6 +367,58 @@ export function registerPullCommand(program) {
|
|
|
366
367
|
console.log(chalk.green(`Set ${agentLabel(agent.id)}@${version} as default`));
|
|
367
368
|
}
|
|
368
369
|
}
|
|
370
|
+
// Report (and optionally install) any declared CLIs that are missing
|
|
371
|
+
// from the host. Skipped under -y so non-interactive pulls don't trigger
|
|
372
|
+
// package-manager prompts.
|
|
373
|
+
try {
|
|
374
|
+
const { statuses, errors } = listCliStatus(process.cwd());
|
|
375
|
+
for (const err of errors) {
|
|
376
|
+
console.log(chalk.yellow(` CLI manifest parse error: ${err.file}: ${err.reason}`));
|
|
377
|
+
}
|
|
378
|
+
const missing = statuses.filter((s) => !s.installed);
|
|
379
|
+
if (missing.length > 0) {
|
|
380
|
+
console.log(chalk.bold('\nDeclared CLIs missing from this host:'));
|
|
381
|
+
for (const s of missing) {
|
|
382
|
+
const method = selectInstallMethod(s.manifest);
|
|
383
|
+
const action = method ? describeMethod(method) : chalk.red('no compatible install method');
|
|
384
|
+
console.log(` ${chalk.cyan(s.manifest.name.padEnd(20))} ${chalk.gray(action)}`);
|
|
385
|
+
}
|
|
386
|
+
console.log('');
|
|
387
|
+
if (!skipPrompts) {
|
|
388
|
+
const proceed = await confirm({ message: `Install ${missing.length} missing CLI(s) now?`, default: true });
|
|
389
|
+
if (proceed) {
|
|
390
|
+
for (const s of missing) {
|
|
391
|
+
console.log(chalk.bold(`\n→ ${s.manifest.name}`));
|
|
392
|
+
const result = installCli(s.manifest);
|
|
393
|
+
if (result.error) {
|
|
394
|
+
console.log(chalk.red(` ${result.error}`));
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (result.installed) {
|
|
398
|
+
console.log(chalk.green(` installed`));
|
|
399
|
+
if (s.manifest.postInstall) {
|
|
400
|
+
console.log(chalk.gray(s.manifest.postInstall.trim().split('\n').map((l) => ' ' + l).join('\n')));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
console.log(chalk.yellow(` install ran but \`${s.manifest.check}\` still fails`));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
console.log(chalk.gray(`Skipped. Run 'agents cli install' later.`));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
console.log(chalk.gray(`Run 'agents cli install' to install them.`));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
catch (err) {
|
|
418
|
+
if (!isPromptCancelled(err)) {
|
|
419
|
+
console.log(chalk.yellow(`CLI install skipped: ${err.message}`));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
369
422
|
console.log(chalk.green('\nPull complete'));
|
|
370
423
|
}
|
|
371
424
|
catch (err) {
|