@phnx-labs/agents-cli 1.22.74 → 1.22.76
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 +152 -0
- package/README.md +21 -9
- package/dist/bootstrap.js +7 -7
- package/dist/cli/command-registry.js +5 -0
- package/dist/commands/artifacts-setup.js +1 -1
- package/dist/commands/artifacts.js +1 -1
- package/dist/commands/auth.js +7 -1
- package/dist/commands/browser.js +104 -10
- package/dist/commands/commands.js +7 -6
- package/dist/commands/computer.d.ts +1 -0
- package/dist/commands/computer.js +26 -7
- package/dist/commands/config.js +27 -4
- package/dist/commands/cost.js +6 -4
- package/dist/commands/doctor.d.ts +6 -5
- package/dist/commands/doctor.js +32 -274
- package/dist/commands/exec.d.ts +2 -0
- package/dist/commands/exec.js +9 -2
- package/dist/commands/harness.d.ts +1 -0
- package/dist/commands/harness.js +11 -3
- package/dist/commands/hooks.js +7 -6
- package/dist/commands/mcp.js +7 -6
- package/dist/commands/memory.js +7 -7
- package/dist/commands/monitors.js +3 -2
- package/dist/commands/open.d.ts +25 -12
- package/dist/commands/open.js +24 -10
- package/dist/commands/permissions.js +7 -6
- package/dist/commands/plugins.js +21 -17
- package/dist/commands/route.js +33 -16
- package/dist/commands/rules.js +7 -12
- package/dist/commands/sessions-share.js +1 -1
- package/dist/commands/setup-watchdog.js +2 -2
- package/dist/commands/setup.js +22 -1
- package/dist/commands/share.js +26 -10
- package/dist/commands/skills.js +7 -6
- package/dist/commands/subagents.js +7 -6
- package/dist/commands/sync.js +81 -10
- package/dist/commands/view.js +4 -1
- package/dist/commands/watchdog.d.ts +1 -1
- package/dist/commands/watchdog.js +10 -10
- package/dist/commands/webhook.d.ts +4 -0
- package/dist/commands/webhook.js +22 -4
- package/dist/commands/workflows.js +7 -6
- package/dist/lib/account-registry.js +27 -6
- package/dist/lib/accounting/rotate.d.ts +3 -1
- package/dist/lib/accounting/rotate.js +8 -4
- package/dist/lib/auth-health.d.ts +2 -0
- package/dist/lib/auth-health.js +2 -0
- package/dist/lib/browser/chrome.d.ts +21 -0
- package/dist/lib/browser/chrome.js +60 -3
- package/dist/lib/browser/drivers/local.d.ts +21 -0
- package/dist/lib/browser/drivers/local.js +102 -9
- package/dist/lib/browser/profiles.d.ts +29 -1
- package/dist/lib/browser/profiles.js +50 -1
- package/dist/lib/browser/types.d.ts +18 -0
- package/dist/lib/computer/computer-rpc.d.ts +6 -1
- package/dist/lib/computer/computer-rpc.js +23 -3
- package/dist/lib/computer/des.d.ts +1 -0
- package/dist/lib/computer/des.js +114 -0
- package/dist/lib/computer/rfb-client.d.ts +53 -0
- package/dist/lib/computer/rfb-client.js +562 -0
- package/dist/lib/config-keys.d.ts +7 -2
- package/dist/lib/config-keys.js +17 -2
- package/dist/lib/daemon/auth-sync-service.js +3 -0
- package/dist/lib/daemon/daemon.js +17 -10
- package/dist/lib/daemon/session-summarizer-service.d.ts +24 -0
- package/dist/lib/daemon/session-summarizer-service.js +39 -0
- package/dist/lib/daemon/usage-sync-service.js +3 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/daemon-ticks.d.ts +2 -2
- package/dist/lib/daemon-ticks.js +2 -1
- package/dist/lib/daemon-webhooks.js +15 -2
- package/dist/lib/deeplink/register.js +10 -9
- package/dist/lib/deeplink/url.d.ts +4 -4
- package/dist/lib/deeplink/url.js +4 -4
- package/dist/lib/device-config.js +25 -0
- package/dist/lib/devices/doctor-findings.d.ts +4 -4
- package/dist/lib/devices/doctor-findings.js +14 -8
- package/dist/lib/devices/registry.js +2 -0
- package/dist/lib/devices/stats-cache.d.ts +4 -0
- package/dist/lib/devices/stats-cache.js +19 -0
- package/dist/lib/drift-sync.d.ts +3 -1
- package/dist/lib/drift-sync.js +16 -5
- package/dist/lib/exec.d.ts +2 -0
- package/dist/lib/exec.js +16 -1
- package/dist/lib/fleet-shared-repo-sync.d.ts +12 -0
- package/dist/lib/fleet-shared-repo-sync.js +101 -4
- package/dist/lib/fleet-shared-state.d.ts +8 -0
- package/dist/lib/heal.d.ts +4 -3
- package/dist/lib/heal.js +5 -4
- package/dist/lib/hosts/ready.d.ts +1 -1
- package/dist/lib/hosts/ready.js +16 -4
- package/dist/lib/hosts/reconnect.js +4 -2
- package/dist/lib/identity/client.d.ts +6 -0
- package/dist/lib/identity/index.d.ts +16 -0
- package/dist/lib/identity/index.js +25 -1
- package/dist/lib/profiles.d.ts +2 -0
- package/dist/lib/profiles.js +28 -9
- package/dist/lib/reconcile-and-repair.d.ts +109 -0
- package/dist/lib/reconcile-and-repair.js +267 -0
- package/dist/lib/routers.d.ts +12 -1
- package/dist/lib/routers.js +30 -1
- package/dist/lib/scheduling/routines.js +8 -2
- package/dist/lib/session/active.d.ts +13 -0
- package/dist/lib/session/db.d.ts +47 -7
- package/dist/lib/session/db.js +114 -12
- package/dist/lib/session/mirror.js +58 -0
- package/dist/lib/session/remote/remote-list.d.ts +2 -0
- package/dist/lib/session/remote/remote-list.js +4 -0
- package/dist/lib/session/remote/watch.js +22 -2
- package/dist/lib/session/session-cache.d.ts +19 -0
- package/dist/lib/session/session-cache.js +46 -0
- package/dist/lib/session/types.d.ts +34 -0
- package/dist/lib/share/backend.d.ts +6 -4
- package/dist/lib/share/backend.js +10 -8
- package/dist/lib/share/config.d.ts +4 -3
- package/dist/lib/share/config.js +10 -1
- package/dist/lib/share/delete.d.ts +1 -1
- package/dist/lib/share/delete.js +1 -1
- package/dist/lib/share/html.d.ts +1 -1
- package/dist/lib/share/html.js +1 -1
- package/dist/lib/share/provision.d.ts +1 -1
- package/dist/lib/share/provision.js +2 -2
- package/dist/lib/share/publish.d.ts +23 -7
- package/dist/lib/share/publish.js +58 -12
- package/dist/lib/share/worker-template.js +221 -60
- package/dist/lib/startup/command-registry.js +2 -2
- package/dist/lib/state.d.ts +15 -0
- package/dist/lib/state.js +29 -7
- package/dist/lib/summarizer/config.d.ts +46 -0
- package/dist/lib/summarizer/config.js +83 -0
- package/dist/lib/summarizer/pass.d.ts +45 -0
- package/dist/lib/summarizer/pass.js +112 -0
- package/dist/lib/summarizer/summarize.d.ts +68 -0
- package/dist/lib/summarizer/summarize.js +120 -0
- package/dist/lib/teams/agents.d.ts +4 -3
- package/dist/lib/teams/agents.js +12 -4
- package/dist/lib/teams/scheduler.d.ts +4 -2
- package/dist/lib/teams/scheduler.js +6 -6
- package/dist/lib/tmux/session.d.ts +2 -0
- package/dist/lib/tmux/session.js +7 -1
- package/dist/lib/types.d.ts +20 -0
- package/dist/lib/verbs.d.ts +23 -0
- package/dist/lib/verbs.js +24 -0
- package/dist/lib/view-types.d.ts +4 -0
- package/dist/lib/watchdog/rotate.d.ts +1 -1
- package/dist/lib/watchdog/rotate.js +1 -1
- package/package.json +1 -1
|
@@ -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, resolvePeersPath, resolveTcpEndpoint, loadComputerAllowList, loadDefaultPeers, writeComputerPolicy, writeComputerPeers, } from '../lib/computer/computer-rpc.js';
|
|
6
|
+
import { openComputerClient, resolveHelperApp, resolveHelperExec, resolveSocketPath, resolveLogPath, resolvePolicyPath, resolvePeersPath, resolveTcpEndpoint, resolveVncEndpoint, loadComputerAllowList, loadDefaultPeers, writeComputerPolicy, writeComputerPeers, } from '../lib/computer/computer-rpc.js';
|
|
7
7
|
import { setupRemoteHelper, startRemoteTunnel, stopRemoteHelper, hydrateRemoteEnvFromState, readRemoteState, resolveRemoteDevice, REMOTE_TASK_NAME, WIN_HELPER_EXE, } from '../lib/computer/ssh-tunnel.js';
|
|
8
8
|
import { sshExec } from '../lib/ssh-exec.js';
|
|
9
9
|
import { encodePowershell } from '../lib/hosts/remote-cmd.js';
|
|
@@ -41,6 +41,8 @@ export function shouldBlockOffPlatform(opts) {
|
|
|
41
41
|
return false;
|
|
42
42
|
if (opts.tcpConfigured)
|
|
43
43
|
return false; // remote (Windows) daemon over a tunnel
|
|
44
|
+
if (opts.vncConfigured)
|
|
45
|
+
return false; // RFB/VNC desktop (Linux GUI over the wire)
|
|
44
46
|
if (opts.device)
|
|
45
47
|
return false; // remote path resolves its own endpoint
|
|
46
48
|
return true;
|
|
@@ -84,20 +86,37 @@ export function reconcileScreenshotExt(outPath, buf) {
|
|
|
84
86
|
export function registerComputerCommand(program) {
|
|
85
87
|
const computer = program
|
|
86
88
|
.command('computer')
|
|
87
|
-
.description('Drive macOS apps via Accessibility, or a remote Windows device with --device —
|
|
89
|
+
.description('Drive macOS apps via Accessibility, a Linux GUI desktop with --vnc, or a remote Windows device with --device — screenshot, click, type')
|
|
90
|
+
// A VNC/RFB desktop is driven over the wire (--vnc host:port, e.g. an x11vnc
|
|
91
|
+
// server on a headless Linux box or an LXD container). Set it before the gate.
|
|
92
|
+
.option('--vnc <host:port>', 'Drive a GUI desktop over VNC/RFB (x11vnc/Xvnc; port defaults to 5901) instead of a native helper')
|
|
93
|
+
.option('--vnc-password <password>', 'VNC password for --vnc (or set COMPUTER_HELPER_VNC_PASSWORD)')
|
|
88
94
|
// The whole subsystem is macOS Accessibility / TCC for LOCAL driving. Off
|
|
89
|
-
// macOS it still works against a remote daemon (COMPUTER_HELPER_TCP set,
|
|
90
|
-
// a `--device <name>` invocation). Fail fast with a clear
|
|
91
|
-
//
|
|
95
|
+
// macOS it still works against a remote daemon (COMPUTER_HELPER_TCP set, a
|
|
96
|
+
// --vnc desktop, or a `--device <name>` invocation). Fail fast with a clear
|
|
97
|
+
// message only when no remote path is available, instead of a downstream error.
|
|
92
98
|
.hook('preAction', async (_thisCommand, actionCommand) => {
|
|
93
|
-
const
|
|
99
|
+
const globals = actionCommand.optsWithGlobals();
|
|
100
|
+
// --vnc selects the RFB transport for every verb under this command.
|
|
101
|
+
if (globals.vnc) {
|
|
102
|
+
process.env.COMPUTER_HELPER_VNC = globals.vnc;
|
|
103
|
+
if (globals.vncPassword)
|
|
104
|
+
process.env.COMPUTER_HELPER_VNC_PASSWORD = globals.vncPassword;
|
|
105
|
+
}
|
|
106
|
+
const device = globals.device;
|
|
94
107
|
// Verbs with --device reconnect to the tunnel `start --device` recorded;
|
|
95
108
|
// this sets COMPUTER_HELPER_TCP so the shared client picks the TCP transport.
|
|
96
109
|
if (device && !REMOTE_LIFECYCLE.has(actionCommand.name())) {
|
|
97
110
|
hydrateRemoteEnvFromState(device);
|
|
98
111
|
}
|
|
99
|
-
if (shouldBlockOffPlatform({
|
|
112
|
+
if (shouldBlockOffPlatform({
|
|
113
|
+
platform: process.platform,
|
|
114
|
+
tcpConfigured: resolveTcpEndpoint() != null,
|
|
115
|
+
vncConfigured: resolveVncEndpoint() != null,
|
|
116
|
+
device,
|
|
117
|
+
})) {
|
|
100
118
|
console.error('agents computer: macOS only for local driving — it uses the macOS Accessibility API.');
|
|
119
|
+
console.error('For a Linux GUI desktop over VNC: `agents computer --vnc <host:port> screenshot`.');
|
|
101
120
|
console.error('For a remote Windows device: register it with `agents devices`, then use --device (or set COMPUTER_HELPER_TCP).');
|
|
102
121
|
process.exit(1);
|
|
103
122
|
}
|
package/dist/commands/config.js
CHANGED
|
@@ -45,6 +45,8 @@ function parseValue(key, parsed, raw) {
|
|
|
45
45
|
return raw.trim();
|
|
46
46
|
case 'project':
|
|
47
47
|
return raw.trim();
|
|
48
|
+
case 'summarizer':
|
|
49
|
+
return parsed.property === 'enabled' ? parseBool(raw, key) : raw.trim();
|
|
48
50
|
case 'device': {
|
|
49
51
|
const property = parsed.property;
|
|
50
52
|
switch (property) {
|
|
@@ -126,6 +128,10 @@ function setConfig(parsed, value) {
|
|
|
126
128
|
case 'project':
|
|
127
129
|
setProjectRoot(value);
|
|
128
130
|
return;
|
|
131
|
+
case 'summarizer': {
|
|
132
|
+
setConfigValue(`summarizer.${parsed.property}`, value);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
129
135
|
case 'device': {
|
|
130
136
|
const configName = devicePropertyToConfigName(parsed.property);
|
|
131
137
|
if (parsed.property === 'notes') {
|
|
@@ -176,10 +182,18 @@ function unsetConfig(parsed) {
|
|
|
176
182
|
}
|
|
177
183
|
case 'project': {
|
|
178
184
|
const had = getProjectRoot() !== undefined;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
185
|
+
// `projectRoot` is a machine-local key that `writeMeta` persists to the
|
|
186
|
+
// device doc; it only clears that doc when the key is PRESENT-but-falsy
|
|
187
|
+
// (`writesProjectRoot` needs the own-property, state.ts). Dropping the key
|
|
188
|
+
// from the returned object left the device-doc value in place, so
|
|
189
|
+
// `config get` still returned it after unset. Set it undefined instead.
|
|
190
|
+
updateMeta((meta) => ({ ...meta, projectRoot: undefined }));
|
|
191
|
+
return had;
|
|
192
|
+
}
|
|
193
|
+
case 'summarizer': {
|
|
194
|
+
const name = `summarizer.${parsed.property}`;
|
|
195
|
+
const had = getConfigValue(name).value !== undefined;
|
|
196
|
+
unsetConfigValue(name);
|
|
183
197
|
return had;
|
|
184
198
|
}
|
|
185
199
|
case 'device': {
|
|
@@ -212,6 +226,8 @@ function getConfig(parsed) {
|
|
|
212
226
|
}
|
|
213
227
|
case 'project':
|
|
214
228
|
return getProjectRoot();
|
|
229
|
+
case 'summarizer':
|
|
230
|
+
return getConfigValue(`summarizer.${parsed.property}`).value;
|
|
215
231
|
case 'device': {
|
|
216
232
|
const configName = devicePropertyToConfigName(parsed.property);
|
|
217
233
|
return getConfigValue(configName, { device: parsed.device }).value;
|
|
@@ -287,6 +303,13 @@ function* listCentralConfigEntries() {
|
|
|
287
303
|
const key = 'browser.device';
|
|
288
304
|
yield { key, value: browserDevice, hint: configKeyStorageHint(parseConfigKey(key)) };
|
|
289
305
|
}
|
|
306
|
+
// Session-summarizer keys (PHNX-3939) — user scope, syncs fleet-wide.
|
|
307
|
+
for (const key of ['summarizer.enabled', 'summarizer.baseUrl', 'summarizer.model']) {
|
|
308
|
+
const value = getConfigValue(key).value;
|
|
309
|
+
if (value !== undefined) {
|
|
310
|
+
yield { key, value, hint: configKeyStorageHint(parseConfigKey(key)) };
|
|
311
|
+
}
|
|
312
|
+
}
|
|
290
313
|
}
|
|
291
314
|
/** Collect device-scope config entries. */
|
|
292
315
|
function* listDeviceConfigEntries(device) {
|
package/dist/commands/cost.js
CHANGED
|
@@ -11,12 +11,13 @@ export function registerCostCommand(insightsCmd) {
|
|
|
11
11
|
.description('Roll up $ cost and duration across local agent sessions')
|
|
12
12
|
.option('--json', 'Output the rollup as JSON')
|
|
13
13
|
.option('--since <time>', 'Only sessions newer than this (e.g., 7d, 4w, or ISO date)')
|
|
14
|
-
.option('--by <dimension>', 'Group the breakdown by: agent (default), project, day, or account (the Claude org that produced each session)')
|
|
14
|
+
.option('--by <dimension>', 'Group the breakdown by: agent (default), project, day, model, or account (the Claude org that produced each session)')
|
|
15
15
|
.addHelpText('after', `
|
|
16
16
|
Examples:
|
|
17
17
|
agents insights cost Daily histogram + top sessions + per-agent breakdown
|
|
18
18
|
agents insights cost --since 30d Last 30 days only
|
|
19
19
|
agents insights cost --by project Break down by project instead of agent
|
|
20
|
+
agents insights cost --by model --json Per-model output tokens and recorded cost
|
|
20
21
|
agents insights cost --by day --json Machine-readable daily rollup
|
|
21
22
|
|
|
22
23
|
Cost is computed offline from a versioned per-model price table (${PRICING_VERSION}).
|
|
@@ -35,9 +36,9 @@ Cost is computed offline from a versioned per-model price table (${PRICING_VERSI
|
|
|
35
36
|
function resolveGroup(by) {
|
|
36
37
|
if (by === undefined)
|
|
37
38
|
return 'agent';
|
|
38
|
-
if (by === 'agent' || by === 'project' || by === 'day' || by === 'account')
|
|
39
|
+
if (by === 'agent' || by === 'project' || by === 'day' || by === 'model' || by === 'account')
|
|
39
40
|
return by;
|
|
40
|
-
console.error(chalk.red('error: --by must be one of: agent, project, day, account'));
|
|
41
|
+
console.error(chalk.red('error: --by must be one of: agent, project, day, model, account'));
|
|
41
42
|
process.exit(1);
|
|
42
43
|
}
|
|
43
44
|
async function costAction(options) {
|
|
@@ -111,7 +112,8 @@ async function costAction(options) {
|
|
|
111
112
|
const groupLabel = groupBy === 'agent' ? 'agent'
|
|
112
113
|
: groupBy === 'project' ? 'project'
|
|
113
114
|
: groupBy === 'account' ? 'account'
|
|
114
|
-
: '
|
|
115
|
+
: groupBy === 'model' ? 'model'
|
|
116
|
+
: 'day';
|
|
115
117
|
out.push(chalk.bold(`By ${groupLabel}`));
|
|
116
118
|
const cols = terminalWidth();
|
|
117
119
|
const costW2 = Math.max(...breakdown.map(r => formatUsd(r.costUsd).length), 4);
|
|
@@ -15,11 +15,12 @@
|
|
|
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
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* `agents
|
|
22
|
-
*
|
|
18
|
+
* Read-only always: doctor diagnoses, it never mutates. To fix the gaps it
|
|
19
|
+
* finds — install missing resources, repair Claude-invalid plugin manifests,
|
|
20
|
+
* refresh stale plugins, reconcile drift, repair hook runtime shims — run
|
|
21
|
+
* `agents sync <agent>@all` (the one fixer, a superset of the old `doctor
|
|
22
|
+
* --fix`). Run `agents prune cleanup` to act on orphan readouts. `--fix` is a
|
|
23
|
+
* deprecated no-op that now errors with a pointer to `agents sync`.
|
|
23
24
|
*/
|
|
24
25
|
import type { Command } from 'commander';
|
|
25
26
|
import { type FleetInventory } from '../lib/devices/fleet-divergence.js';
|
package/dist/commands/doctor.js
CHANGED
|
@@ -4,7 +4,7 @@ import { addHostOption } from '../lib/hosts/option.js';
|
|
|
4
4
|
import { buildRemoteAgentsInvocation } from '../lib/hosts/remote-cmd.js';
|
|
5
5
|
import { loadDevices } from '../lib/devices/registry.js';
|
|
6
6
|
import { fanOutDevices, planFleetTargets, remoteFleetTargets } from '../lib/devices/fleet.js';
|
|
7
|
-
import { enterDoctorOverviewGate,
|
|
7
|
+
import { enterDoctorOverviewGate, writeDoctorOverviewCache } from '../lib/devices/doctor-overview-cache.js';
|
|
8
8
|
import { fleetDialTarget } from '../lib/devices/connect.js';
|
|
9
9
|
import { compareFleetInventories, FLEET_HOOK_RUNTIME_STATES } from '../lib/devices/fleet-divergence.js';
|
|
10
10
|
import { collectLocalFleetInventory } from '../lib/devices/fleet-inventory.js';
|
|
@@ -23,7 +23,7 @@ import { getVersionHomePath, listInstalledVersions, } from '../lib/installations
|
|
|
23
23
|
import { resolveAgentTargets, AgentSpecError } from '../lib/agent-spec/index.js';
|
|
24
24
|
import { loadManifest, isStale } from '../lib/staleness/index.js';
|
|
25
25
|
import { diffVersionResources, DOCTOR_ALL_KINDS, } from '../lib/doctor-diff.js';
|
|
26
|
-
import {
|
|
26
|
+
import { inspectDuplicateVersionHooks } from '../lib/hooks/install.js';
|
|
27
27
|
import { inspectReservedAuthBundle } from '../lib/secrets/bundles.js';
|
|
28
28
|
import { isVersionIsolated } from '../lib/installations/versions.js';
|
|
29
29
|
import { computeDrift, checkSyncStatus, countOrphans, computeSourceBehind } from '../lib/drift.js';
|
|
@@ -33,18 +33,13 @@ import { probeOwnerSink } from '../lib/channels/owner-sink.js';
|
|
|
33
33
|
import { unifiedDiff, colorizeUnifiedDiff } from '../lib/diff-text.js';
|
|
34
34
|
import { listCliStatus, listCliStatusAsync } from '../lib/cli-resources.js';
|
|
35
35
|
import { setHelpSections } from '../lib/help.js';
|
|
36
|
-
import { heal, healChangedAnything } from '../lib/heal.js';
|
|
37
36
|
import { getEffectiveExecutionPolicy } from '../lib/platform/winpath.js';
|
|
38
37
|
import { auditWindowsSshEnrollment, diagnoseWindowsSshFailure } from '../lib/devices/windows-ssh-enrollment.js';
|
|
39
38
|
import { scanUserRcFiles, masterPassphraseInEnv } from '../lib/secrets/rc-hygiene.js';
|
|
40
39
|
import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
|
|
41
40
|
import { readRepoBehindMarkers } from '../lib/auto-pull.js';
|
|
42
41
|
import { detectAgentsBinaryShadows } from '../lib/binary-shadow.js';
|
|
43
|
-
import { remediateStaleAgentsCliInstalls, resolveRunningPackageRoot, } from '../lib/self-update.js';
|
|
44
42
|
import * as fs from 'fs';
|
|
45
|
-
import * as path from 'path';
|
|
46
|
-
import { fileURLToPath } from 'url';
|
|
47
|
-
const __doctorDirname = path.dirname(fileURLToPath(import.meta.url));
|
|
48
43
|
const AGENT_NAMES = Object.fromEntries(ALL_AGENT_IDS.map((id) => [id, AGENTS[id].name]));
|
|
49
44
|
// ─── overview mode (no target) ────────────────────────────────────────────────
|
|
50
45
|
function collapseWhitespace(s) {
|
|
@@ -521,7 +516,7 @@ function parseTargetArg(arg) {
|
|
|
521
516
|
const agent = resolveAgentName(agentPart);
|
|
522
517
|
if (!agent)
|
|
523
518
|
return { error: formatAgentError(agentPart) };
|
|
524
|
-
// No qualifier →
|
|
519
|
+
// No qualifier → diagnose every installed version of the agent.
|
|
525
520
|
if (!qualifier) {
|
|
526
521
|
const versions = listInstalledVersions(agent);
|
|
527
522
|
if (versions.length === 0)
|
|
@@ -659,11 +654,8 @@ function readExpectedForDiff(kind, row) {
|
|
|
659
654
|
return null;
|
|
660
655
|
return safeRead(row.sourcePath);
|
|
661
656
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
const HOOK_WIRING_FIX_AGENTS = ['claude', 'droid'];
|
|
665
|
-
/** Categories `--fix` reconciles (vs. `agents repo pull` for a behind source, or
|
|
666
|
-
* `agents prune cleanup` for an orphan). Drives the heal footer. */
|
|
657
|
+
/** Categories `agents sync` reconciles (vs. `agents repo pull` for a behind
|
|
658
|
+
* source, or `agents prune cleanup` for an orphan). Drives the heal footer. */
|
|
667
659
|
const AUTO_FIXABLE_CATEGORIES = new Set([
|
|
668
660
|
'hook-runtime-broken', 'unwired-hook', 'settings-missing', 'settings-unparseable', 'missing', 'divergent', 'stale', 'never-synced',
|
|
669
661
|
]);
|
|
@@ -693,8 +685,10 @@ function missingResourceSeverity(kind) {
|
|
|
693
685
|
export function computeVerdict(report) {
|
|
694
686
|
const issues = [];
|
|
695
687
|
const idLabel = `${report.agent}@${report.version}`;
|
|
696
|
-
|
|
697
|
-
|
|
688
|
+
// doctor diagnoses; `agents sync` fixes. Every auto-fixable finding points at
|
|
689
|
+
// the one fixer — a command sync can actually deliver (asserted in the tests).
|
|
690
|
+
const fixCmd = `agents sync ${idLabel} --yes`;
|
|
691
|
+
const syncCmd = fixCmd;
|
|
698
692
|
// ── critical: settings.json / unwired hooks (silent breakage) ──
|
|
699
693
|
const w = report.hookWiring;
|
|
700
694
|
for (const issue of w?.runtimeBroken ?? []) {
|
|
@@ -897,7 +891,7 @@ export function computeOverviewHealth(syncRows, orphanRows, repoBehindMarkers, d
|
|
|
897
891
|
issues.push({
|
|
898
892
|
severity: 'critical', category: 'hook-runtime-broken', subject: label,
|
|
899
893
|
impact: `${brokenRuntime} generated hook wrapper${brokenRuntime === 1 ? '' : 's'} unusable; affected hooks cannot run`,
|
|
900
|
-
fix: `agents
|
|
894
|
+
fix: `agents sync ${row.agent}@${row.version} --yes`,
|
|
901
895
|
text: `${label} ${brokenRuntime} hook runtime broken`, color: 'red',
|
|
902
896
|
});
|
|
903
897
|
}
|
|
@@ -931,7 +925,7 @@ export function computeOverviewHealth(syncRows, orphanRows, repoBehindMarkers, d
|
|
|
931
925
|
issues.push({
|
|
932
926
|
severity: 'warning', category: 'stale', subject: label,
|
|
933
927
|
impact: 'sources changed since last sync',
|
|
934
|
-
fix: `agents
|
|
928
|
+
fix: `agents sync ${row.agent}@${row.version} --yes`,
|
|
935
929
|
text: `${label} stale`, color: 'yellow',
|
|
936
930
|
});
|
|
937
931
|
}
|
|
@@ -1040,247 +1034,16 @@ function renderTargetText(report, options) {
|
|
|
1040
1034
|
}
|
|
1041
1035
|
console.log();
|
|
1042
1036
|
const verdict = computeVerdict(report);
|
|
1043
|
-
// A source layer behind origin is healed by `agents repo pull`, not
|
|
1037
|
+
// A source layer behind origin is healed by `agents repo pull`, not by sync —
|
|
1044
1038
|
// the per-issue fix already names the right command, so the heal footer only
|
|
1045
|
-
// shows when something is genuinely
|
|
1039
|
+
// shows when something is genuinely sync-fixable (never in the source-behind or
|
|
1046
1040
|
// orphan-only case, where it would mislead).
|
|
1047
1041
|
const hooksWired = report.hookWiring?.supported ? ' · hooks wired' : '';
|
|
1048
1042
|
renderHealthBlock(verdict, {
|
|
1049
1043
|
healthySummary: `${verdict.reconciled} resource${verdict.reconciled === 1 ? '' : 's'} reconciled${hooksWired} · sources current`,
|
|
1050
|
-
healFix: verdictIsAutoFixable(verdict) ? `agents
|
|
1044
|
+
healFix: verdictIsAutoFixable(verdict) ? `agents sync ${report.agent}@${report.version} --yes` : undefined,
|
|
1051
1045
|
});
|
|
1052
1046
|
}
|
|
1053
|
-
// ─── fix / heal mode ───────────────────────────────────────────────────────────
|
|
1054
|
-
function renderHealText(result) {
|
|
1055
|
-
for (const r of result.repairedManifests) {
|
|
1056
|
-
console.log(` ${chalk.green('repair')} plugin ${chalk.bold(r.plugin)} ${chalk.gray(`— dropped invalid ${r.droppedFields.join(', ')} field`)}`);
|
|
1057
|
-
}
|
|
1058
|
-
for (const r of result.refreshedPlugins) {
|
|
1059
|
-
console.log(` ${chalk.green('refresh')} plugin ${chalk.bold(r.plugin)} ${chalk.gray(`${r.from} → ${r.to}`)}`);
|
|
1060
|
-
}
|
|
1061
|
-
for (const s of result.skippedPlugins) {
|
|
1062
|
-
const why = s.reason === 'modified'
|
|
1063
|
-
? `locally modified — left as-is (run \`agents plugins update ${s.plugin}\` to force)`
|
|
1064
|
-
: `no baseline recorded — left as-is (run \`agents plugins update ${s.plugin}\` to adopt)`;
|
|
1065
|
-
console.log(` ${chalk.yellow('hold ')} plugin ${chalk.bold(s.plugin)} ${chalk.gray(`${s.from} → ${s.upstream} available; ${why}`)}`);
|
|
1066
|
-
}
|
|
1067
|
-
for (const v of result.versions) {
|
|
1068
|
-
const label = `${AGENT_NAMES[v.agent] || v.agent}@${v.version}`;
|
|
1069
|
-
if (v.healed.length === 0 && v.skipped.length === 0)
|
|
1070
|
-
continue;
|
|
1071
|
-
const byKind = new Map();
|
|
1072
|
-
for (const h of v.healed)
|
|
1073
|
-
byKind.set(h.kind, (byKind.get(h.kind) ?? 0) + 1);
|
|
1074
|
-
const parts = Array.from(byKind, ([k, n]) => `${n} ${k}`);
|
|
1075
|
-
if (v.healed.length > 0) {
|
|
1076
|
-
console.log(` ${chalk.green('fixed ')} ${label} ${chalk.gray(parts.join(', '))}`);
|
|
1077
|
-
}
|
|
1078
|
-
const drift = v.skipped.filter((s) => s.reason === 'drift');
|
|
1079
|
-
const unres = v.skipped.filter((s) => s.reason === 'unreconcilable');
|
|
1080
|
-
if (drift.length > 0) {
|
|
1081
|
-
console.log(` ${chalk.yellow('drift ')} ${label} ${chalk.gray(`${drift.length} hand-edited — left as-is (use \`--diff\` to inspect)`)}`);
|
|
1082
|
-
}
|
|
1083
|
-
if (unres.length > 0) {
|
|
1084
|
-
const names = unres.map((s) => `${s.kind}/${s.name}`).join(', ');
|
|
1085
|
-
console.log(` ${chalk.yellow('hold ')} ${label} ${chalk.gray(`${unres.length} couldn't reconcile (${names}) — source/home mismatch the writer can't satisfy`)}`);
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
console.log();
|
|
1089
|
-
const healed = result.versions.reduce((n, v) => n + v.healed.length, 0);
|
|
1090
|
-
const touchedVersions = result.versions.filter((v) => v.healed.length > 0).length;
|
|
1091
|
-
if (!healChangedAnything(result)) {
|
|
1092
|
-
console.log(chalk.green('✓ Everything in sync — nothing to heal.'));
|
|
1093
|
-
}
|
|
1094
|
-
else {
|
|
1095
|
-
const bits = [];
|
|
1096
|
-
if (healed > 0)
|
|
1097
|
-
bits.push(`${healed} resource${healed === 1 ? '' : 's'} across ${touchedVersions} version${touchedVersions === 1 ? '' : 's'}`);
|
|
1098
|
-
if (result.repairedManifests.length > 0)
|
|
1099
|
-
bits.push(`${result.repairedManifests.length} manifest${result.repairedManifests.length === 1 ? '' : 's'} repaired`);
|
|
1100
|
-
if (result.refreshedPlugins.length > 0)
|
|
1101
|
-
bits.push(`${result.refreshedPlugins.length} plugin${result.refreshedPlugins.length === 1 ? '' : 's'} refreshed`);
|
|
1102
|
-
console.log(chalk.green(`✓ Healed ${bits.join(', ')}.`));
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
/**
|
|
1106
|
-
* Re-wire hooks that reconcile as files but are absent from settings.json.
|
|
1107
|
-
*
|
|
1108
|
-
* heal() only re-syncs resources the diff flags missing/diff; a hook whose file
|
|
1109
|
-
* is byte-identical to source but never referenced in settings.json is neither,
|
|
1110
|
-
* so heal walks past it. registerHooksToSettings (the same call `agents sync`
|
|
1111
|
-
* makes at versions.ts) regenerates the wiring, so run it for any Claude-family
|
|
1112
|
-
* version this fix targets that has unwired hooks. Only claude/droid — the set
|
|
1113
|
-
* checkVersionHookWiring can verify.
|
|
1114
|
-
*/
|
|
1115
|
-
function rewireUnwiredHooks(parsed) {
|
|
1116
|
-
const out = [];
|
|
1117
|
-
const agents = parsed?.agent
|
|
1118
|
-
? (HOOK_WIRING_FIX_AGENTS.includes(parsed.agent) ? [parsed.agent] : [])
|
|
1119
|
-
: HOOK_WIRING_FIX_AGENTS;
|
|
1120
|
-
for (const agent of agents) {
|
|
1121
|
-
// A named version is explicit consent; a sweep excludes isolated copies,
|
|
1122
|
-
// mirroring heal().
|
|
1123
|
-
const versions = parsed?.agent && parsed.versionExplicit
|
|
1124
|
-
? parsed.versions
|
|
1125
|
-
: listInstalledVersions(agent).filter((v) => !isVersionIsolated(agent, v));
|
|
1126
|
-
for (const version of versions) {
|
|
1127
|
-
const before = checkVersionHookWiring(agent, version);
|
|
1128
|
-
if (!before.supported)
|
|
1129
|
-
continue;
|
|
1130
|
-
const need = before.unwired.length + (before.settingsMissing ? (before.expected ?? 0) : 0);
|
|
1131
|
-
if (need === 0)
|
|
1132
|
-
continue;
|
|
1133
|
-
try {
|
|
1134
|
-
const registration = registerHooksToSettings(agent, getVersionHomePath(agent, version));
|
|
1135
|
-
if (registration.errors.length > 0) {
|
|
1136
|
-
out.push({ agent, version, rewired: 0, remaining: need, failure: 'register-failed' });
|
|
1137
|
-
continue;
|
|
1138
|
-
}
|
|
1139
|
-
const after = checkVersionHookWiring(agent, version);
|
|
1140
|
-
const remaining = after.unwired.length + (after.settingsMissing ? (after.expected ?? 0) : 0);
|
|
1141
|
-
out.push({ agent, version, rewired: Math.max(0, need - remaining), remaining });
|
|
1142
|
-
}
|
|
1143
|
-
catch {
|
|
1144
|
-
// A shim write can fail before the native config writer gets to return
|
|
1145
|
-
// its own errors. Record the same stable class and keep doctor --fix
|
|
1146
|
-
// moving to the one bounded runtime repair pass below.
|
|
1147
|
-
out.push({ agent, version, rewired: 0, remaining: need, failure: 'register-failed' });
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
return out;
|
|
1152
|
-
}
|
|
1153
|
-
function renderHookRewireText(rewired) {
|
|
1154
|
-
for (const r of rewired) {
|
|
1155
|
-
const label = `${AGENT_NAMES[r.agent] || r.agent}@${r.version}`;
|
|
1156
|
-
if (r.failure) {
|
|
1157
|
-
console.log(` ${chalk.red('hold ')} ${label} ${chalk.gray('native hook wiring could not be updated')}`);
|
|
1158
|
-
}
|
|
1159
|
-
else if (r.remaining === 0) {
|
|
1160
|
-
console.log(` ${chalk.green('rewired')} ${label} ${chalk.gray(`${r.rewired} hook${r.rewired === 1 ? '' : 's'} wired into settings.json`)}`);
|
|
1161
|
-
}
|
|
1162
|
-
else {
|
|
1163
|
-
console.log(` ${chalk.yellow('hold ')} ${label} ${chalk.gray(`${r.remaining} hook${r.remaining === 1 ? '' : 's'} still unwired — run \`agents sync ${r.agent}@${r.version} --yes\``)}`);
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
function runtimeRepairFilter(parsed) {
|
|
1168
|
-
if (!parsed)
|
|
1169
|
-
return undefined;
|
|
1170
|
-
// A concrete target can be narrowed to that version. Broad selectors (@all,
|
|
1171
|
-
// @latest, agent-only) still make one bounded repair pass for that harness.
|
|
1172
|
-
return {
|
|
1173
|
-
agent: parsed.agent,
|
|
1174
|
-
...(parsed.versionExplicit && parsed.versions.length === 1 ? { version: parsed.versions[0] } : {}),
|
|
1175
|
-
};
|
|
1176
|
-
}
|
|
1177
|
-
function renderHookRuntimeRepairText(repair) {
|
|
1178
|
-
for (const fixed of repair.fixed) {
|
|
1179
|
-
console.log(` ${chalk.green('fixed ')} ${chalk.gray(fixed)}`);
|
|
1180
|
-
}
|
|
1181
|
-
for (const unresolved of repair.needsAttention) {
|
|
1182
|
-
console.log(` ${chalk.red('hold ')} ${chalk.gray(unresolved)}`);
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
/**
|
|
1186
|
-
* RUSH-2415: delete npx-cache / unsafe-legacy / pre-1.22.30 agents-cli copies
|
|
1187
|
-
* when a fixed peer already exists. Bare `doctor --fix` is the remediation
|
|
1188
|
-
* surface the multi-install warning points at; a targeted
|
|
1189
|
-
* `doctor <agent>@<version> --fix` only heals that version home and must not
|
|
1190
|
-
* touch other CLI installs on the box.
|
|
1191
|
-
*/
|
|
1192
|
-
function purgeStaleAgentsCliCopies(_opts) {
|
|
1193
|
-
let runningRoot;
|
|
1194
|
-
try {
|
|
1195
|
-
// dist/commands/doctor.js (or src/commands/doctor.ts under vitest) —
|
|
1196
|
-
// resolveRunningPackageRoot walks up until package.json names this package.
|
|
1197
|
-
runningRoot = resolveRunningPackageRoot(__doctorDirname);
|
|
1198
|
-
}
|
|
1199
|
-
catch {
|
|
1200
|
-
return null;
|
|
1201
|
-
}
|
|
1202
|
-
return remediateStaleAgentsCliInstalls({
|
|
1203
|
-
runningRoot,
|
|
1204
|
-
runningVersion: getCliVersion(),
|
|
1205
|
-
});
|
|
1206
|
-
}
|
|
1207
|
-
function renderStaleInstallPurgeText(purge) {
|
|
1208
|
-
if (purge.removed.length === 0 && purge.failed.length === 0 && purge.unresolved.length === 0)
|
|
1209
|
-
return;
|
|
1210
|
-
console.log(chalk.bold('\nStale agents-cli installs'));
|
|
1211
|
-
for (const r of purge.removed) {
|
|
1212
|
-
const why = r.reasons.join(', ');
|
|
1213
|
-
console.log(` ${chalk.green('purged')} ${chalk.gray(`${r.packageRoot} ${r.version} (${why})`)}`);
|
|
1214
|
-
}
|
|
1215
|
-
for (const f of purge.failed) {
|
|
1216
|
-
console.log(` ${chalk.red('hold ')} ${chalk.gray(`${f.packageRoot} ${f.version} — ${f.error}`)}`);
|
|
1217
|
-
}
|
|
1218
|
-
// RUSH-2705/2713: a duplicate --fix cannot auto-purge — either a healthy
|
|
1219
|
-
// >=1.22.30 peer, OR a pre-1.22.30 copy left alone only because no fixed peer
|
|
1220
|
-
// exists to fall back to (that one is NOT healthy). Either way, hand back the
|
|
1221
|
-
// command that removes it instead of ending on a bare "everything in sync".
|
|
1222
|
-
// Don't call it "healthy" — that would understate a genuinely vulnerable copy.
|
|
1223
|
-
for (const u of purge.unresolved) {
|
|
1224
|
-
console.log(` ${chalk.yellow('manual')} ${chalk.gray(`${u.packageRoot} ${u.version} — --fix will not delete this copy; remove it with:`)}`);
|
|
1225
|
-
console.log(` ${chalk.bold(u.manualRemoveCommand)}`);
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
async function runFix(parsed, opts) {
|
|
1229
|
-
// Heal targets the global install — project layer is irrelevant, so cwd is
|
|
1230
|
-
// left to heal's neutral default rather than process.cwd().
|
|
1231
|
-
if (!opts.json)
|
|
1232
|
-
console.log(chalk.bold('Healing…'));
|
|
1233
|
-
// `agents doctor <agent> --fix` is a SWEEP, same as the bare form — leave the
|
|
1234
|
-
// version list to heal() so it applies its isolated-copy filter. Passing the
|
|
1235
|
-
// enumerated list here would smuggle isolated versions past that filter.
|
|
1236
|
-
// A named version (`<agent>@<version>`) is explicit consent and is passed through.
|
|
1237
|
-
const result = await heal({
|
|
1238
|
-
mode: 'full',
|
|
1239
|
-
agent: parsed?.agent,
|
|
1240
|
-
versions: parsed?.versionExplicit ? parsed.versions : undefined,
|
|
1241
|
-
});
|
|
1242
|
-
// Re-wire hooks the diff-driven heal leaves behind (present file, not wired).
|
|
1243
|
-
const rewired = rewireUnwiredHooks(parsed);
|
|
1244
|
-
// One inspect→generate→verify pass, after normal resource and native-wiring
|
|
1245
|
-
// repair have settled. This routine never calls sync/register and never
|
|
1246
|
-
// retries; unresolved wrappers remain an explicit non-zero doctor outcome.
|
|
1247
|
-
const hookRuntimeRepair = repairManagedHookRuntimeArtifacts({ filter: runtimeRepairFilter(parsed) });
|
|
1248
|
-
// Bare doctor --fix also purges latent pre-fix / legacy agents-cli copies
|
|
1249
|
-
// that only warn today (RUSH-2415). A scoped agent@version fix leaves them
|
|
1250
|
-
// alone — the multi-install surface is machine-wide, not per-agent.
|
|
1251
|
-
const staleInstallPurge = parsed === null ? purgeStaleAgentsCliCopies(opts) : null;
|
|
1252
|
-
const rewireFailed = rewired.some((entry) => entry.failure !== undefined);
|
|
1253
|
-
if (healChangedAnything(result) ||
|
|
1254
|
-
rewired.some((entry) => entry.rewired > 0 || entry.remaining > 0 || entry.failure !== undefined) ||
|
|
1255
|
-
hookRuntimeRepair.attempts.length > 0 ||
|
|
1256
|
-
(staleInstallPurge !== null && (staleInstallPurge.removed.length > 0 || staleInstallPurge.failed.length > 0))) {
|
|
1257
|
-
invalidateDoctorOverviewCache();
|
|
1258
|
-
}
|
|
1259
|
-
if (opts.json) {
|
|
1260
|
-
console.log(JSON.stringify({
|
|
1261
|
-
...result,
|
|
1262
|
-
hookRewire: rewired,
|
|
1263
|
-
hookRuntimeRepair,
|
|
1264
|
-
...(staleInstallPurge ? { staleInstallPurge } : {}),
|
|
1265
|
-
}, null, 2));
|
|
1266
|
-
if (rewireFailed
|
|
1267
|
-
|| hookRuntimeRepair.needsAttention.length > 0
|
|
1268
|
-
|| (staleInstallPurge !== null && staleInstallPurge.failed.length > 0)) {
|
|
1269
|
-
process.exitCode = 1;
|
|
1270
|
-
}
|
|
1271
|
-
return;
|
|
1272
|
-
}
|
|
1273
|
-
renderHealText(result);
|
|
1274
|
-
renderHookRewireText(rewired);
|
|
1275
|
-
renderHookRuntimeRepairText(hookRuntimeRepair);
|
|
1276
|
-
if (staleInstallPurge)
|
|
1277
|
-
renderStaleInstallPurgeText(staleInstallPurge);
|
|
1278
|
-
if (rewireFailed
|
|
1279
|
-
|| hookRuntimeRepair.needsAttention.length > 0
|
|
1280
|
-
|| (staleInstallPurge !== null && staleInstallPurge.failed.length > 0)) {
|
|
1281
|
-
process.exitCode = 1;
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
1047
|
function checkLabel(row) {
|
|
1285
1048
|
return `${AGENT_NAMES[row.agent] || row.agent}@${row.version}`;
|
|
1286
1049
|
}
|
|
@@ -1360,7 +1123,7 @@ function runCheckGate(opts, cwd) {
|
|
|
1360
1123
|
}
|
|
1361
1124
|
const hints = [];
|
|
1362
1125
|
if (drift.staleCount > 0 || drift.neverSyncedCount > 0 || drift.unwiredHookVersions > 0 || drift.brokenHookRuntimeVersions > 0) {
|
|
1363
|
-
hints.push('`agents
|
|
1126
|
+
hints.push('`agents sync <agent>@all` (or `agents sync <agent>@<version>`)');
|
|
1364
1127
|
}
|
|
1365
1128
|
if (drift.sourceBehind.length > 0)
|
|
1366
1129
|
hints.push('`agents repo pull <alias>` for a source layer behind origin');
|
|
@@ -1447,7 +1210,7 @@ async function runDevicesCheck(opts, cwd) {
|
|
|
1447
1210
|
: [`${d.stale} stale`, `${d.neverSynced} never-synced`].filter((p) => !p.startsWith('0 ')).join(', ');
|
|
1448
1211
|
console.error(` ${chalk.yellow(d.device.padEnd(18))} ${detail || 'drift'}`);
|
|
1449
1212
|
}
|
|
1450
|
-
console.error(chalk.gray('\nReconcile each device with `agents
|
|
1213
|
+
console.error(chalk.gray('\nReconcile each device with `agents sync <agent>@all` or `agents repo pull user`.'));
|
|
1451
1214
|
}
|
|
1452
1215
|
process.exit(1);
|
|
1453
1216
|
}
|
|
@@ -1457,7 +1220,7 @@ export function registerDoctorCommand(program) {
|
|
|
1457
1220
|
.description('Diagnose CLI availability, sync status, and resource divergence (optionally for a specific agent[@version]).')
|
|
1458
1221
|
.option('--json', 'Output machine-readable JSON')
|
|
1459
1222
|
.option('--diff', 'In target mode, include unified diffs for divergent files')
|
|
1460
|
-
.option('--fix', '
|
|
1223
|
+
.option('--fix', '(deprecated) doctor is diagnose-only — this errors with a pointer to `agents sync`, the one fixer')
|
|
1461
1224
|
.option('--kind <kinds>', 'Restrict to comma-separated resource kinds (commands,skills,hooks,rules,mcp,permissions,subagents,plugins,workflows,memory)')
|
|
1462
1225
|
.option('--cwd <path>', 'Resolution cwd for project layer detection (default: process.cwd())')
|
|
1463
1226
|
.option('--adopt <agent>', "Take over the agent's native launcher that shadows the shim (symlink it to the version-managed shim; reversible with --release)")
|
|
@@ -1488,11 +1251,11 @@ export function registerDoctorCommand(program) {
|
|
|
1488
1251
|
# Inspect only rules and hooks, with full diffs
|
|
1489
1252
|
agents doctor claude@default --kind rules,hooks --diff
|
|
1490
1253
|
|
|
1491
|
-
#
|
|
1492
|
-
agents
|
|
1254
|
+
# Fix every gap across all installed versions (the one fixer)
|
|
1255
|
+
agents sync claude@all
|
|
1493
1256
|
|
|
1494
|
-
#
|
|
1495
|
-
agents
|
|
1257
|
+
# Fix just one version
|
|
1258
|
+
agents sync claude@2.1.207
|
|
1496
1259
|
|
|
1497
1260
|
# Fleet: agent readiness + cross-device divergence (missing plugins/skills,
|
|
1498
1261
|
# agent-version gaps, .agents/.system repo drift) vs this machine
|
|
@@ -1503,7 +1266,7 @@ export function registerDoctorCommand(program) {
|
|
|
1503
1266
|
agents doctor --check --quiet # just the verdict line
|
|
1504
1267
|
agents doctor --check --json # machine-readable, for scripting
|
|
1505
1268
|
agents doctor --check --devices # gate every registered device
|
|
1506
|
-
agents doctor --check || { echo "resources drifted — run 'agents
|
|
1269
|
+
agents doctor --check || { echo "resources drifted — run 'agents sync <agent>@all'"; exit 1; }
|
|
1507
1270
|
`,
|
|
1508
1271
|
});
|
|
1509
1272
|
doctorCmd.action(async (target, opts) => {
|
|
@@ -1587,20 +1350,15 @@ export function registerDoctorCommand(program) {
|
|
|
1587
1350
|
}
|
|
1588
1351
|
return;
|
|
1589
1352
|
}
|
|
1590
|
-
// --fix
|
|
1591
|
-
//
|
|
1353
|
+
// `doctor --fix` moved to `agents sync` — doctor is diagnose-only now.
|
|
1354
|
+
// Keep the flag for one release as a signpost: it never heals, it points at
|
|
1355
|
+
// the one fixer and exits non-zero so scripts and muscle memory get a clear
|
|
1356
|
+
// redirect instead of a silent no-op.
|
|
1592
1357
|
if (opts.fix) {
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
console.error(chalk.red(parsed.error));
|
|
1598
|
-
process.exit(1);
|
|
1599
|
-
}
|
|
1600
|
-
scope = parsed;
|
|
1601
|
-
}
|
|
1602
|
-
await runFix(scope, opts);
|
|
1603
|
-
return;
|
|
1358
|
+
const where = target ? `agents sync ${target}` : 'agents sync <agent>@all';
|
|
1359
|
+
console.error(chalk.yellow('`agents doctor --fix` has moved to `agents sync`.'));
|
|
1360
|
+
console.error(chalk.gray(` doctor now only diagnoses. To fix, run: ${where}`));
|
|
1361
|
+
process.exit(1);
|
|
1604
1362
|
}
|
|
1605
1363
|
if (!target) {
|
|
1606
1364
|
// Singleflight + short-TTL cache for the bare `doctor --json` overview.
|
|
@@ -1666,8 +1424,8 @@ export function registerDoctorCommand(program) {
|
|
|
1666
1424
|
// A MANAGED agent (installed versions) whose binary won't resolve is a
|
|
1667
1425
|
// real critical — an unmanaged-and-absent agent is not.
|
|
1668
1426
|
const cliMissing = ALL_AGENT_IDS.filter((a) => listInstalledVersions(a).length > 0 && clis[a] && !clis[a].installed);
|
|
1669
|
-
// An isolated copy is skipped by the agent-wide
|
|
1670
|
-
// findings must never fold into a collapsed cross-version row.
|
|
1427
|
+
// An isolated copy is skipped by the agent-wide `agents sync` sweep, so
|
|
1428
|
+
// its findings must never fold into a collapsed cross-version row.
|
|
1671
1429
|
const isolatedVersions = reports
|
|
1672
1430
|
.filter((r) => isVersionIsolated(r.agent, r.version))
|
|
1673
1431
|
.map((r) => `${r.agent}@${r.version}`);
|
package/dist/commands/exec.d.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import { type Command } from 'commander';
|
|
9
9
|
import type { ExecEffort } from '../lib/exec.js';
|
|
10
10
|
import { RUN_AUTO_KEYWORD } from '../lib/types.js';
|
|
11
|
+
/** Validate a caller-supplied session id before it reaches tmux, paths, indexes, or remote dispatch. */
|
|
12
|
+
export declare function parseExplicitSessionId(value: string): string;
|
|
11
13
|
export interface RunAccountPickerRequest {
|
|
12
14
|
requested: boolean;
|
|
13
15
|
normalizedAgentSpec: string;
|