@phnx-labs/agents-cli 1.22.25 → 1.22.27
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 +427 -0
- package/README.md +35 -3
- package/dist/bin/agents +0 -0
- package/dist/browser.js +14 -4
- package/dist/commands/apply.js +52 -8
- package/dist/commands/bench.d.ts +2 -0
- package/dist/commands/bench.js +101 -0
- package/dist/commands/browser.js +35 -0
- package/dist/commands/doctor.js +32 -10
- package/dist/commands/exec.js +17 -16
- package/dist/commands/insights.d.ts +25 -19
- package/dist/commands/insights.js +146 -38
- package/dist/commands/output.js +100 -25
- package/dist/commands/reconnect.d.ts +46 -0
- package/dist/commands/reconnect.js +109 -0
- package/dist/commands/resume.d.ts +11 -0
- package/dist/commands/resume.js +51 -0
- package/dist/commands/routines.js +2 -2
- package/dist/commands/secrets.d.ts +2 -8
- package/dist/commands/secrets.js +29 -105
- package/dist/commands/sessions-picker.js +12 -7
- package/dist/commands/sessions.d.ts +47 -1
- package/dist/commands/sessions.js +349 -37
- package/dist/commands/setup-secrets.d.ts +1 -0
- package/dist/commands/setup-secrets.js +1 -1
- package/dist/commands/setup.d.ts +26 -3
- package/dist/commands/setup.js +105 -46
- package/dist/commands/ssh.js +184 -11
- package/dist/commands/teams.d.ts +6 -0
- package/dist/commands/teams.js +46 -3
- package/dist/commands/trends.d.ts +8 -0
- package/dist/commands/trends.js +10 -156
- package/dist/index.js +17 -21
- package/dist/lib/agents.d.ts +11 -0
- package/dist/lib/agents.js +29 -2
- package/dist/lib/analytics/dashboard.d.ts +10 -6
- package/dist/lib/analytics/dashboard.js +6 -4
- package/dist/lib/analytics/mix-commands.d.ts +53 -0
- package/dist/lib/analytics/mix-commands.js +229 -0
- package/dist/lib/analytics/recipes.d.ts +19 -14
- package/dist/lib/analytics/recipes.js +4 -2
- package/dist/lib/auth-health.d.ts +47 -3
- package/dist/lib/auth-health.js +74 -15
- package/dist/lib/bench/index.d.ts +4 -0
- package/dist/lib/bench/index.js +4 -0
- package/dist/lib/bench/runner.d.ts +16 -0
- package/dist/lib/bench/runner.js +111 -0
- package/dist/lib/bench/schema.d.ts +5 -0
- package/dist/lib/bench/schema.js +91 -0
- package/dist/lib/bench/storage.d.ts +5 -0
- package/dist/lib/bench/storage.js +32 -0
- package/dist/lib/bench/types.d.ts +40 -0
- package/dist/lib/bench/types.js +1 -0
- package/dist/lib/browser/ipc.d.ts +26 -0
- package/dist/lib/browser/ipc.js +139 -24
- package/dist/lib/browser/profiles.d.ts +11 -0
- package/dist/lib/browser/profiles.js +1 -1
- package/dist/lib/browser/stream.d.ts +14 -0
- package/dist/lib/browser/stream.js +71 -0
- package/dist/lib/channels/owner-sink.d.ts +27 -0
- package/dist/lib/channels/owner-sink.js +93 -0
- package/dist/lib/claude-account-token.d.ts +2 -0
- package/dist/lib/claude-account-token.js +27 -1
- package/dist/lib/crabbox/cli.d.ts +2 -0
- package/dist/lib/crabbox/cli.js +2 -0
- package/dist/lib/crabbox/lease.js +7 -1
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/devices/connect.d.ts +2 -0
- package/dist/lib/devices/connect.js +7 -0
- package/dist/lib/devices/doctor-findings.d.ts +10 -1
- package/dist/lib/devices/doctor-findings.js +47 -1
- package/dist/lib/devices/harness-inventory.d.ts +97 -0
- package/dist/lib/devices/harness-inventory.js +0 -0
- package/dist/lib/devices/registry.d.ts +2 -0
- package/dist/lib/devices/ssh-config.js +3 -0
- package/dist/lib/devices/windows-ssh-enrollment.d.ts +20 -0
- package/dist/lib/devices/windows-ssh-enrollment.js +98 -0
- package/dist/lib/exec.bench.d.ts +1 -0
- package/dist/lib/exec.bench.js +186 -0
- package/dist/lib/exec.js +18 -8
- package/dist/lib/fleet/apply.d.ts +59 -3
- package/dist/lib/fleet/apply.js +191 -12
- package/dist/lib/fleet/remote-login.d.ts +4 -3
- package/dist/lib/fleet/remote-login.js +11 -9
- package/dist/lib/fleet/types.d.ts +21 -2
- package/dist/lib/gemini-settings.d.ts +0 -1
- package/dist/lib/gemini-settings.js +12 -7
- package/dist/lib/hooks/cache.js +15 -0
- package/dist/lib/hooks.d.ts +8 -5
- package/dist/lib/hooks.js +10 -6
- package/dist/lib/hosts/dispatch.js +7 -3
- package/dist/lib/hosts/passthrough.d.ts +45 -0
- package/dist/lib/hosts/passthrough.js +59 -8
- package/dist/lib/hosts/progress.d.ts +2 -1
- package/dist/lib/hosts/progress.js +4 -3
- package/dist/lib/hosts/providers/devices.js +1 -0
- package/dist/lib/hosts/ready.d.ts +6 -2
- package/dist/lib/hosts/ready.js +33 -8
- package/dist/lib/hosts/reconcile.d.ts +1 -1
- package/dist/lib/hosts/reconcile.js +16 -7
- package/dist/lib/hosts/reconnect.d.ts +56 -21
- package/dist/lib/hosts/reconnect.js +128 -71
- package/dist/lib/hosts/registry.d.ts +2 -2
- package/dist/lib/hosts/registry.js +3 -5
- package/dist/lib/hosts/remote-cmd.d.ts +17 -0
- package/dist/lib/hosts/remote-cmd.js +29 -0
- package/dist/lib/hosts/tasks.d.ts +2 -0
- package/dist/lib/hosts/types.d.ts +1 -0
- package/dist/lib/hosts/types.js +3 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +9 -6
- package/dist/lib/menubar/install-menubar.js +20 -9
- package/dist/lib/models.d.ts +30 -1
- package/dist/lib/models.js +42 -144
- package/dist/lib/pricing/cost.d.ts +9 -0
- package/dist/lib/pricing/cost.js +24 -0
- package/dist/lib/pricing/index.d.ts +1 -1
- package/dist/lib/pricing/index.js +1 -1
- package/dist/lib/redact.js +8 -3
- package/dist/lib/remote-agents-json.d.ts +32 -0
- package/dist/lib/remote-agents-json.js +47 -16
- package/dist/lib/resource-profiles.js +1 -2
- package/dist/lib/routine-notify-owner.d.ts +102 -0
- package/dist/lib/routine-notify-owner.js +232 -0
- package/dist/lib/routines.d.ts +10 -0
- package/dist/lib/routines.js +14 -2
- package/dist/lib/runner.d.ts +9 -4
- package/dist/lib/runner.js +182 -30
- package/dist/lib/sandbox.d.ts +0 -2
- package/dist/lib/sandbox.js +2 -19
- 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/agent.js +3 -2
- package/dist/lib/secrets/lease.d.ts +25 -0
- package/dist/lib/secrets/lease.js +44 -0
- package/dist/lib/secrets/push.d.ts +94 -0
- package/dist/lib/secrets/push.js +145 -0
- package/dist/lib/secrets/reaper.d.ts +15 -1
- package/dist/lib/secrets/reaper.js +30 -3
- package/dist/lib/self-update.d.ts +20 -5
- package/dist/lib/self-update.js +93 -16
- package/dist/lib/session/db.d.ts +38 -3
- package/dist/lib/session/db.js +273 -16
- package/dist/lib/session/discover.d.ts +22 -1
- package/dist/lib/session/discover.js +239 -51
- package/dist/lib/session/insights.d.ts +37 -0
- package/dist/lib/session/insights.js +219 -9
- package/dist/lib/session/recovery.d.ts +17 -1
- package/dist/lib/session/recovery.js +111 -4
- package/dist/lib/session/remote-list.d.ts +10 -9
- package/dist/lib/session/remote-list.js +25 -23
- package/dist/lib/session/resume-owner.d.ts +55 -0
- package/dist/lib/session/resume-owner.js +69 -0
- package/dist/lib/session/team-filter.d.ts +65 -0
- package/dist/lib/session/team-filter.js +98 -3
- package/dist/lib/session/tool-index.js +133 -22
- package/dist/lib/session/tool-store.d.ts +26 -2
- package/dist/lib/session/tool-store.js +36 -17
- package/dist/lib/session/types.d.ts +33 -0
- package/dist/lib/smart-launch.d.ts +31 -5
- package/dist/lib/smart-launch.js +43 -7
- package/dist/lib/ssh-exec.d.ts +27 -0
- package/dist/lib/ssh-exec.js +42 -3
- package/dist/lib/ssh-tunnel.d.ts +3 -2
- package/dist/lib/ssh-tunnel.js +25 -16
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +6 -0
- package/dist/lib/teams/agents.d.ts +13 -0
- package/dist/lib/teams/agents.js +75 -7
- package/dist/lib/teams/placement-probe.d.ts +21 -0
- package/dist/lib/teams/placement-probe.js +135 -0
- package/dist/lib/teams/scheduler.d.ts +74 -1
- package/dist/lib/teams/scheduler.js +187 -10
- package/dist/lib/tmux/session.d.ts +8 -0
- package/dist/lib/tmux/session.js +22 -0
- package/dist/lib/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/commands/apply.js
CHANGED
|
@@ -15,6 +15,7 @@ import chalk from 'chalk';
|
|
|
15
15
|
import { setHelpSections } from '../lib/help.js';
|
|
16
16
|
import { machineId } from '../lib/session/sync/config.js';
|
|
17
17
|
import { loadDevices, isControlDevice } from '../lib/devices/registry.js';
|
|
18
|
+
import { isHostPinned, managedKnownHostsPath } from '../lib/devices/known-hosts.js';
|
|
18
19
|
import { ensureDevicesRegistered } from '../lib/devices/sync.js';
|
|
19
20
|
import { readFleetFile, resolveDesired } from '../lib/fleet/manifest.js';
|
|
20
21
|
import { snapshotAuth, materializeAuth, parseAuthBundle, KEYCHAIN_BOUND_ON_MAC, isCredentialSafeToPropagate } from '../lib/fleet/auth-sync.js';
|
|
@@ -82,7 +83,10 @@ function renderPlan(plan) {
|
|
|
82
83
|
}
|
|
83
84
|
return chalk.cyan('↑ ' + acts.map((a) => a.agent ?? a.kind.replace('-cli', '')).join(','));
|
|
84
85
|
};
|
|
85
|
-
|
|
86
|
+
// Only show the secrets column when the manifest declares any — an all-`-`
|
|
87
|
+
// column on every fleet that uses no bundles is noise.
|
|
88
|
+
const anySecrets = rows.some((r) => r.actions.some((a) => a.kind === 'push-secret' || a.kind === 'needs-secret'));
|
|
89
|
+
const header = ` ${'device'.padEnd(nameWidth)} ${'agents-cli'.padEnd(12)}${'agents'.padEnd(20)}${'config'.padEnd(10)}${anySecrets ? 'login'.padEnd(18) + 'secrets' : 'login'}`;
|
|
86
90
|
console.log(chalk.gray(header));
|
|
87
91
|
for (const row of rows) {
|
|
88
92
|
const cli = row.probe.reachable
|
|
@@ -100,10 +104,33 @@ function renderPlan(plan) {
|
|
|
100
104
|
? (row.actions.some((a) => a.kind === 'sync-config') ? chalk.cyan('↑ sync') : chalk.green('ok'))
|
|
101
105
|
: chalk.gray('-');
|
|
102
106
|
const loginCell = cell(row, ['push-login', 'needs-login'], `${row.desired.agents.length}/${row.desired.agents.length}`);
|
|
103
|
-
|
|
107
|
+
const secretsCell = (() => {
|
|
108
|
+
if (!anySecrets)
|
|
109
|
+
return '';
|
|
110
|
+
if (!row.probe.reachable)
|
|
111
|
+
return chalk.gray('- offline');
|
|
112
|
+
const push = row.actions.filter((a) => a.kind === 'push-secret').length;
|
|
113
|
+
const blocked = row.actions.filter((a) => a.kind === 'needs-secret').length;
|
|
114
|
+
if (push === 0 && blocked === 0)
|
|
115
|
+
return chalk.green('ok');
|
|
116
|
+
if (push > 0 && blocked === 0)
|
|
117
|
+
return chalk.cyan(`↑ push ${push}`);
|
|
118
|
+
if (push === 0)
|
|
119
|
+
return chalk.yellow(`blocked ${blocked}`);
|
|
120
|
+
return chalk.yellow(`↑ push ${push} · blocked ${blocked}`);
|
|
121
|
+
})();
|
|
122
|
+
const loginPart = anySecrets ? stripPad(loginCell, 18) + secretsCell : loginCell;
|
|
123
|
+
console.log(` ${row.device.padEnd(nameWidth)} ${stripPad(cli, 12)}${stripPad(agentsCell, 20)}${stripPad(configCell, 10)}${loginPart}`);
|
|
104
124
|
}
|
|
105
125
|
console.log();
|
|
106
126
|
console.log(chalk.gray(` ${plan.actions.length} action(s) across ${rows.filter((r) => r.probe.reachable).length} reachable device(s)`));
|
|
127
|
+
// The capability is opt-in, so when it is OFF and the manifest declares
|
|
128
|
+
// bundles, say that it exists. Otherwise an operator reads "manual recreate"
|
|
129
|
+
// and concludes there is no supported path — which is exactly the conclusion
|
|
130
|
+
// that led to a master key being hand-exported across the fleet (RUSH-1968).
|
|
131
|
+
if (anySecrets && !rows.some((r) => r.actions.some((a) => a.kind === 'push-secret'))) {
|
|
132
|
+
console.log(chalk.gray(' secrets: not pushed. `--provision-secrets` pushes declared bundles to devices whose host key is pinned.'));
|
|
133
|
+
}
|
|
107
134
|
// Distinguish *why* a login can't be propagated: macOS keychain-bound,
|
|
108
135
|
// single-use rotating refresh token (never copied), or the source simply not
|
|
109
136
|
// being signed in to that agent (no portable file).
|
|
@@ -130,14 +157,15 @@ function renderPlan(plan) {
|
|
|
130
157
|
if (noToken.length > 0) {
|
|
131
158
|
console.log(chalk.yellow(` manual login needed (no portable token on source): ${noToken.join(', ')}`));
|
|
132
159
|
}
|
|
133
|
-
// Secrets bundles are declared once for the fleet; surface the distinct set
|
|
134
|
-
//
|
|
160
|
+
// Secrets bundles are declared once for the fleet; surface the distinct set the
|
|
161
|
+
// gate did NOT push, so a refusal is never silent. "never pushed" used to be
|
|
162
|
+
// literally true here and no longer is — `--provision-secrets` pushes them.
|
|
135
163
|
const bundles = [...new Set(rows.flatMap((r) => r.secretsNeeded))];
|
|
136
164
|
if (bundles.length > 0) {
|
|
137
165
|
const shown = bundles.slice(0, 12);
|
|
138
166
|
const more = bundles.length - shown.length;
|
|
139
167
|
const list = shown.join(', ') + (more > 0 ? `, +${more} more` : '');
|
|
140
|
-
console.log(chalk.yellow(` ${bundles.length} secrets bundle(s)
|
|
168
|
+
console.log(chalk.yellow(` ${bundles.length} secrets bundle(s) not pushed — recreate where missing: ${list}`));
|
|
141
169
|
}
|
|
142
170
|
}
|
|
143
171
|
/** padEnd on the visible width, ignoring chalk color codes. Exported for tests. */
|
|
@@ -218,11 +246,24 @@ async function runApply(opts) {
|
|
|
218
246
|
// Probe every target device in parallel.
|
|
219
247
|
const nameToProfile = new Map(desired.map((d) => [d.device, registry[d.device]]));
|
|
220
248
|
const withVersions = rosterNeedsVersions(desired);
|
|
249
|
+
// One extra `secrets list --json` per device, and only when it can change the
|
|
250
|
+
// plan: the manifest declares bundles AND provisioning is on. Same cost
|
|
251
|
+
// discipline as `withVersions` — a fleet that uses no bundles never pays it.
|
|
252
|
+
const withSecrets = opts.provisionSecrets === true && (manifest.secrets?.bundles?.length ?? 0) > 0;
|
|
221
253
|
console.log(chalk.gray(`Probing ${desired.length} device(s)…`));
|
|
222
|
-
const probeList = await pool(desired, 6, async (d) => probeDevice(nameToProfile.get(d.device), { withVersions }));
|
|
254
|
+
const probeList = await pool(desired, 6, async (d) => probeDevice(nameToProfile.get(d.device), { withVersions, withSecrets }));
|
|
223
255
|
const probes = new Map(probeList.map((p) => [p.device, p]));
|
|
224
256
|
const targetCliVersion = localCliVersion();
|
|
225
|
-
let plan = diffFleet(desired, probes, {
|
|
257
|
+
let plan = diffFleet(desired, probes, {
|
|
258
|
+
targetCliVersion,
|
|
259
|
+
sourceAuth,
|
|
260
|
+
secretsBundles: manifest.secrets?.bundles,
|
|
261
|
+
provisionSecrets: opts.provisionSecrets === true,
|
|
262
|
+
forceSecrets: opts.force === true,
|
|
263
|
+
// Same bar as `exec --copy-creds` (EXEC-34): credential values only ever go
|
|
264
|
+
// to a host whose key we already pinned.
|
|
265
|
+
isHostPinned: (device) => isHostPinned(device, managedKnownHostsPath()),
|
|
266
|
+
});
|
|
226
267
|
// --only filter.
|
|
227
268
|
if (opts.only) {
|
|
228
269
|
const keep = new Set();
|
|
@@ -242,7 +283,8 @@ async function runApply(opts) {
|
|
|
242
283
|
return;
|
|
243
284
|
// `needs-login`/`needs-secret` are surfaced manual reminders, not executable
|
|
244
285
|
// mutations — exclude them so an otherwise-converged fleet still says "nothing
|
|
245
|
-
// to do" instead of looping forever on un-actionable surfacing.
|
|
286
|
+
// to do" instead of looping forever on un-actionable surfacing. `push-secret`
|
|
287
|
+
// IS executable and deliberately stays counted.
|
|
246
288
|
if (plan.actions.filter((a) => a.kind !== 'needs-login' && a.kind !== 'needs-secret').length === 0) {
|
|
247
289
|
console.log(chalk.green('\nNothing to do — fleet already matches the profile.'));
|
|
248
290
|
return;
|
|
@@ -301,6 +343,8 @@ export function configureApplyCommand(cmd) {
|
|
|
301
343
|
.option('--agent <specs...>', 'Override the roster for targeted device(s): install these specs instead of the manifest\'s. Use `claude@all` to replicate every version installed on this machine.')
|
|
302
344
|
.option('--only <dims>', 'Limit to dimensions: comma list of agents,config,login')
|
|
303
345
|
.option('--no-login', 'Do not propagate logins')
|
|
346
|
+
.option('--provision-secrets', "Push the manifest's declared secrets bundles to each device (OFF by default; moves credential values over SSH, and only to a device whose host key is already pinned)")
|
|
347
|
+
.option('--force', 'With --provision-secrets: re-push a bundle the device already has')
|
|
304
348
|
.addOption(new Option('--recv-auth', 'internal: receive an auth bundle on stdin').hideHelp())
|
|
305
349
|
.action(async (opts) => {
|
|
306
350
|
try {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { detectSignedInRuntimes } from "../lib/crabbox/runtimes.js";
|
|
5
|
+
import { listRuns, loadRun, loadTask, runCells, saveRun, } from "../lib/bench/index.js";
|
|
6
|
+
import { setHelpSections } from "../lib/help.js";
|
|
7
|
+
const TASKS_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../bench/tasks");
|
|
8
|
+
function csv(value) {
|
|
9
|
+
return (value
|
|
10
|
+
?.split(",")
|
|
11
|
+
.map((item) => item.trim())
|
|
12
|
+
.filter(Boolean) ?? []);
|
|
13
|
+
}
|
|
14
|
+
function taskIds(root = TASKS_ROOT) {
|
|
15
|
+
if (!fs.existsSync(root))
|
|
16
|
+
return [];
|
|
17
|
+
return fs
|
|
18
|
+
.readdirSync(root, { withFileTypes: true })
|
|
19
|
+
.filter((entry) => entry.isDirectory() &&
|
|
20
|
+
fs.existsSync(path.join(root, entry.name, "task.json")))
|
|
21
|
+
.map((entry) => entry.name)
|
|
22
|
+
.sort();
|
|
23
|
+
}
|
|
24
|
+
function renderResult(result) {
|
|
25
|
+
console.log(`Run ${result.run_id}${result.task_id ? ` · ${result.task_id}` : ""}`);
|
|
26
|
+
for (const cell of result.cells)
|
|
27
|
+
console.log(`${cell.status === "passed" ? "PASS" : "FAIL"} ${cell.agent}${cell.model ? `/${cell.model}` : ""} ${cell.wall_ms} ms exit ${cell.exit ?? "spawn-error"}`);
|
|
28
|
+
}
|
|
29
|
+
export function registerBenchCommand(program) {
|
|
30
|
+
const bench = program
|
|
31
|
+
.command("bench")
|
|
32
|
+
.description("Run the same task across agent and model cells, with isolated fixtures and durable JSON results.");
|
|
33
|
+
setHelpSections(bench, {
|
|
34
|
+
examples: `agents bench list\nagents bench run hello-repo --agent claude,codex --model cheap,default\nagents bench results --json`,
|
|
35
|
+
notes: `Task definitions live under apps/cli/bench/tasks/<id>/task.json. Custom harness names accepted by agents run are valid --agent values.`,
|
|
36
|
+
});
|
|
37
|
+
bench
|
|
38
|
+
.command("list")
|
|
39
|
+
.description("List available benchmark tasks.")
|
|
40
|
+
.option("--json", "Emit JSON.")
|
|
41
|
+
.action((options) => {
|
|
42
|
+
const tasks = taskIds();
|
|
43
|
+
if (options.json)
|
|
44
|
+
console.log(JSON.stringify(tasks, null, 2));
|
|
45
|
+
else if (tasks.length === 0)
|
|
46
|
+
console.log("No benchmark tasks installed.");
|
|
47
|
+
else
|
|
48
|
+
tasks.forEach((id) => console.log(id));
|
|
49
|
+
});
|
|
50
|
+
bench
|
|
51
|
+
.command("results [run-id]")
|
|
52
|
+
.description("Show one saved run, or list saved runs newest first.")
|
|
53
|
+
.option("--json", "Emit JSON.")
|
|
54
|
+
.action((runId, options) => {
|
|
55
|
+
const value = runId ? loadRun(runId) : listRuns();
|
|
56
|
+
if (options.json)
|
|
57
|
+
console.log(JSON.stringify(value, null, 2));
|
|
58
|
+
else if (Array.isArray(value)) {
|
|
59
|
+
if (value.length === 0)
|
|
60
|
+
console.log("No benchmark results yet.");
|
|
61
|
+
else
|
|
62
|
+
value.forEach(renderResult);
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
renderResult(value);
|
|
66
|
+
});
|
|
67
|
+
bench
|
|
68
|
+
.command("run [task-id]")
|
|
69
|
+
.description("Run one task or prompt across an agent × model matrix.")
|
|
70
|
+
.option("--prompt <text>", "Prompt to benchmark instead of a house task.")
|
|
71
|
+
.option("--agent <names>", "Comma-separated native agents or custom harness names. Defaults to signed-in native agents.")
|
|
72
|
+
.option("--model <models>", "Comma-separated model tiers or concrete model ids.")
|
|
73
|
+
.option("--concurrency <n>", "Maximum cells running at once.", "3")
|
|
74
|
+
.option("--json", "Emit the saved JSON result.")
|
|
75
|
+
.action(async (taskId, options) => {
|
|
76
|
+
if (!!taskId === !!options.prompt)
|
|
77
|
+
throw new Error("Pass exactly one of <task-id> or --prompt.");
|
|
78
|
+
const task = taskId ? loadTask(taskId, TASKS_ROOT) : undefined;
|
|
79
|
+
const prompt = options.prompt ?? task.prompt;
|
|
80
|
+
let agents = csv(options.agent);
|
|
81
|
+
if (agents.length === 0)
|
|
82
|
+
agents = (await detectSignedInRuntimes())
|
|
83
|
+
.filter((runtime) => runtime.signedIn)
|
|
84
|
+
.map((runtime) => runtime.id);
|
|
85
|
+
if (agents.length === 0)
|
|
86
|
+
throw new Error("No signed-in native agents found. Pass --agent <name>.");
|
|
87
|
+
const models = csv(options.model);
|
|
88
|
+
const cells = agents.flatMap((agent) => models.length > 0
|
|
89
|
+
? models.map((model) => ({ agent, model }))
|
|
90
|
+
: [{ agent }]);
|
|
91
|
+
const concurrency = Number(options.concurrency);
|
|
92
|
+
const result = await runCells({ task, prompt, cells, concurrency });
|
|
93
|
+
saveRun(result);
|
|
94
|
+
if (options.json)
|
|
95
|
+
console.log(JSON.stringify(result, null, 2));
|
|
96
|
+
else
|
|
97
|
+
renderResult(result);
|
|
98
|
+
if (result.cells.some((cell) => cell.status === "failed"))
|
|
99
|
+
process.exitCode = 1;
|
|
100
|
+
});
|
|
101
|
+
}
|
package/dist/commands/browser.js
CHANGED
|
@@ -20,6 +20,7 @@ import { isInteractiveTerminal } from './utils.js';
|
|
|
20
20
|
import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
21
21
|
import { buildHar } from '../lib/browser/har.js';
|
|
22
22
|
import { getCliVersion } from '../lib/version.js';
|
|
23
|
+
import { runBrowserIPCStream } from '../lib/browser/stream.js';
|
|
23
24
|
/**
|
|
24
25
|
* Resolve which browser task a command targets. Order:
|
|
25
26
|
* 1. `--task <name>` flag (explicit per-command override)
|
|
@@ -48,6 +49,7 @@ const TASK_OPTION_DESC = 'Task name (defaults to $AGENTS_BROWSER_TASK)';
|
|
|
48
49
|
// trailing "Other commands" section automatically.
|
|
49
50
|
const BROWSER_HELP_GROUPS = [
|
|
50
51
|
{ title: 'Session lifecycle', names: ['start', 'done', 'status'] },
|
|
52
|
+
{ title: 'Fast action loop', names: ['stream'] },
|
|
51
53
|
{
|
|
52
54
|
title: 'Drive the page',
|
|
53
55
|
names: ['navigate', 'tabs', 'screenshot', 'evaluate', 'click', 'type', 'press', 'wait'],
|
|
@@ -79,6 +81,9 @@ export function registerBrowserCommand(program) {
|
|
|
79
81
|
agents browser navigate https://example.com
|
|
80
82
|
agents browser screenshot
|
|
81
83
|
|
|
84
|
+
# Keep one process and daemon socket warm for repeated actions
|
|
85
|
+
agents browser stream --task "$AGENTS_BROWSER_TASK"
|
|
86
|
+
|
|
82
87
|
# Drive another machine's browser (needs its consent — see remote-control)
|
|
83
88
|
agents browser start --host zion
|
|
84
89
|
|
|
@@ -654,6 +659,36 @@ function registerTaskCommands(browser) {
|
|
|
654
659
|
? 'Other fleet machines can now drive this browser via `browser --host <this-device>`.'
|
|
655
660
|
: 'Cross-machine `browser --host` drives to this machine are refused.');
|
|
656
661
|
});
|
|
662
|
+
const stream = browser
|
|
663
|
+
.command('stream')
|
|
664
|
+
.description('Keep one process and daemon IPC socket open; read NDJSON requests from stdin and write NDJSON responses')
|
|
665
|
+
.option(TASK_OPTION_FLAG, 'Default task for requests that omit `task` (defaults to $AGENTS_BROWSER_TASK)')
|
|
666
|
+
.action(async (opts) => {
|
|
667
|
+
await runBrowserIPCStream({
|
|
668
|
+
input: process.stdin,
|
|
669
|
+
output: process.stdout,
|
|
670
|
+
task: opts.task ?? process.env.AGENTS_BROWSER_TASK,
|
|
671
|
+
actor: resolveActor().id,
|
|
672
|
+
launchId: process.env.AGENT_LAUNCH_ID,
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
setHelpSections(stream, {
|
|
676
|
+
examples: `
|
|
677
|
+
# Batch two warm actions through one process and one daemon connection
|
|
678
|
+
printf '%s\\n' \\
|
|
679
|
+
'{"action":"screenshot","path":"/tmp/page.jpg"}' \\
|
|
680
|
+
'{"action":"click","atX":320,"atY":540}' \\
|
|
681
|
+
| agents browser stream --task "$AGENTS_BROWSER_TASK"
|
|
682
|
+
|
|
683
|
+
# Keep the command open and send one JSON object per line from a long-lived shell
|
|
684
|
+
agents browser stream --task "$AGENTS_BROWSER_TASK"
|
|
685
|
+
`,
|
|
686
|
+
notes: `
|
|
687
|
+
stdout is protocol-only: one compact JSON response for each non-empty input line.
|
|
688
|
+
Malformed JSON returns an error response without closing the stream.
|
|
689
|
+
The first start response becomes the default task for later lines in the same stream.
|
|
690
|
+
`,
|
|
691
|
+
});
|
|
657
692
|
browser
|
|
658
693
|
.command('start')
|
|
659
694
|
.description('Start a browser task. Pass --profile <name>; omit to use your configured default (`agents browser profiles set-default`), else auto-pick an installed Chromium-family browser.')
|
package/dist/commands/doctor.js
CHANGED
|
@@ -12,7 +12,8 @@ import { buildLocalFindings, fleetDivergenceToFindings, signInToFindings, render
|
|
|
12
12
|
import { getCliVersion } from '../lib/version.js';
|
|
13
13
|
import { resolveHost } from '../lib/hosts/registry.js';
|
|
14
14
|
import { sshExecAsync } from '../lib/ssh-exec.js';
|
|
15
|
-
import { sshTargetFor } from '../lib/hosts/types.js';
|
|
15
|
+
import { hostIdentityArgs, sshTargetFor } from '../lib/hosts/types.js';
|
|
16
|
+
import { deviceIdentityArgs } from '../lib/devices/connect.js';
|
|
16
17
|
import { machineId, normalizeHost } from '../lib/session/sync/config.js';
|
|
17
18
|
import { findAmbiguousDevicePins } from '../lib/routines.js';
|
|
18
19
|
import chalk from 'chalk';
|
|
@@ -26,11 +27,14 @@ import { checkVersionHookWiring, inspectDuplicateVersionHooks, registerHooksToSe
|
|
|
26
27
|
import { isVersionIsolated } from '../lib/versions.js';
|
|
27
28
|
import { computeDrift, checkSyncStatus, countOrphans, computeSourceBehind } from '../lib/drift.js';
|
|
28
29
|
import { readAuthHealthCache, summarizeHostAuth } from '../lib/auth-health.js';
|
|
30
|
+
import { readMeta } from '../lib/state.js';
|
|
31
|
+
import { probeOwnerSink } from '../lib/channels/owner-sink.js';
|
|
29
32
|
import { unifiedDiff, colorizeUnifiedDiff } from '../lib/diff-text.js';
|
|
30
33
|
import { listCliStatus, listCliStatusAsync } from '../lib/cli-resources.js';
|
|
31
34
|
import { setHelpSections } from '../lib/help.js';
|
|
32
35
|
import { heal, healChangedAnything } from '../lib/heal.js';
|
|
33
36
|
import { getEffectiveExecutionPolicy } from '../lib/platform/winpath.js';
|
|
37
|
+
import { auditWindowsSshEnrollment, diagnoseWindowsSshFailure } from '../lib/devices/windows-ssh-enrollment.js';
|
|
34
38
|
import { scanUserRcFiles, masterPassphraseInEnv } from '../lib/secrets/rc-hygiene.js';
|
|
35
39
|
import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
|
|
36
40
|
import { readRepoBehindMarkers } from '../lib/auto-pull.js';
|
|
@@ -81,14 +85,14 @@ function printWrappedLine(prefix, text) {
|
|
|
81
85
|
console.log(chalk.gray(line));
|
|
82
86
|
}
|
|
83
87
|
/**
|
|
84
|
-
* Narrow a remote `findings` array to
|
|
88
|
+
* Narrow a remote `findings` array to rows only that device can observe.
|
|
85
89
|
*
|
|
86
90
|
* Deliberately NOT "forward every remote finding". The aggregator already
|
|
87
91
|
* rebuilds a remote's sign-in rows from its inventory and its divergence rows
|
|
88
92
|
* from the comparator, so forwarding wholesale would double them; and pulling a
|
|
89
93
|
* remote's orphan/drift rows into a fleet readout is a much larger UX change
|
|
90
|
-
* than this fix.
|
|
91
|
-
*
|
|
94
|
+
* than this fix. These kinds are unrecomputable centrally: shell/process secret
|
|
95
|
+
* hygiene and the Windows host's effective OpenSSH key path/content/ACL.
|
|
92
96
|
*
|
|
93
97
|
* **The remote contributes exactly one thing: the KIND.** Severity, message and
|
|
94
98
|
* remediation are all generated HERE. That is not defensiveness for its own
|
|
@@ -109,13 +113,15 @@ function printWrappedLine(prefix, text) {
|
|
|
109
113
|
* file and line. Run `agents doctor` on that box for the specifics — the
|
|
110
114
|
* message says so.
|
|
111
115
|
*/
|
|
112
|
-
const REMOTE_FORWARDED_KINDS = ['rc-secret-export', 'env-secret-export'];
|
|
116
|
+
const REMOTE_FORWARDED_KINDS = ['rc-secret-export', 'env-secret-export', 'ssh-key-enrollment'];
|
|
113
117
|
/** Canonical, locally-authored text for a forwarded kind. Never the remote's. */
|
|
114
118
|
const REMOTE_SECRET_MESSAGE = {
|
|
115
119
|
'rc-secret-export': 'a credential-shaped export was found in this box\'s shell rc files'
|
|
116
120
|
+ ' — run `agents doctor` there for the file and line',
|
|
117
121
|
'env-secret-export': 'AGENTS_SECRETS_PASSPHRASE is set in this box\'s process environment'
|
|
118
122
|
+ ' — run `agents doctor` there for detail',
|
|
123
|
+
'ssh-key-enrollment': 'Windows OpenSSH key enrollment is invalid'
|
|
124
|
+
+ ' — run `agents doctor` on this box for the effective path or ACL failure',
|
|
119
125
|
};
|
|
120
126
|
export function asRemoteSecretFindings(raw, device) {
|
|
121
127
|
if (!Array.isArray(raw))
|
|
@@ -156,11 +162,12 @@ async function resolveFleetTargets(opts) {
|
|
|
156
162
|
name: deviceProfile.name,
|
|
157
163
|
sshTarget: deviceProfile.name,
|
|
158
164
|
os: deviceProfile.platform !== 'unknown' ? deviceProfile.platform : undefined,
|
|
165
|
+
extraSshArgs: deviceIdentityArgs(deviceProfile),
|
|
159
166
|
}];
|
|
160
167
|
}
|
|
161
168
|
const host = await resolveHost(singleName);
|
|
162
169
|
if (host) {
|
|
163
|
-
return [{ name: singleName, sshTarget: sshTargetFor(host), os: host.os }];
|
|
170
|
+
return [{ name: singleName, sshTarget: sshTargetFor(host), os: host.os, extraSshArgs: hostIdentityArgs(host) }];
|
|
164
171
|
}
|
|
165
172
|
console.error(chalk.red(`Unknown host or device '${singleName}'.`));
|
|
166
173
|
process.exit(1);
|
|
@@ -178,6 +185,7 @@ async function resolveFleetTargets(opts) {
|
|
|
178
185
|
name: d.name,
|
|
179
186
|
sshTarget: d.name,
|
|
180
187
|
os: d.platform !== 'unknown' ? d.platform : undefined,
|
|
188
|
+
extraSshArgs: deviceIdentityArgs(d),
|
|
181
189
|
}));
|
|
182
190
|
}
|
|
183
191
|
async function probeFleetTarget(target) {
|
|
@@ -188,12 +196,14 @@ async function probeFleetTarget(target) {
|
|
|
188
196
|
// has it via the install profile, and our single-quote escaping would
|
|
189
197
|
// prevent $HOME expansion there, so skip the bootstrap on Windows.
|
|
190
198
|
isWin ? undefined : { PATH: '$HOME/.agents/.cache/shims:$HOME/.local/bin:$PATH' });
|
|
191
|
-
const res = await sshExecAsync(target.sshTarget, remoteCmd, { timeoutMs: 30000, multiplex: true });
|
|
199
|
+
const res = await sshExecAsync(target.sshTarget, remoteCmd, { timeoutMs: 30000, multiplex: true, extraSshArgs: target.extraSshArgs });
|
|
192
200
|
if (res.code !== 0) {
|
|
193
201
|
return {
|
|
194
202
|
name: target.name,
|
|
195
203
|
online: false,
|
|
196
|
-
error:
|
|
204
|
+
error: isWin
|
|
205
|
+
? diagnoseWindowsSshFailure(res.stderr, res.timedOut)
|
|
206
|
+
: res.timedOut ? 'timed out' : (res.stderr || `exit ${res.code ?? 'unknown'}`),
|
|
197
207
|
agents: {},
|
|
198
208
|
};
|
|
199
209
|
}
|
|
@@ -236,7 +246,11 @@ export const FLEET_INVENTORY_TIMEOUT_MS = 180_000;
|
|
|
236
246
|
async function probeFleetInventory(target) {
|
|
237
247
|
const isWin = /^win/i.test((target.os ?? '').trim());
|
|
238
248
|
const remoteCmd = buildRemoteAgentsInvocation(['doctor', '--json'], undefined, isWin ? 'windows' : undefined, isWin ? undefined : { PATH: '$HOME/.agents/.cache/shims:$HOME/.local/bin:$PATH' });
|
|
239
|
-
const res = await sshExecAsync(target.sshTarget, remoteCmd, {
|
|
249
|
+
const res = await sshExecAsync(target.sshTarget, remoteCmd, {
|
|
250
|
+
timeoutMs: FLEET_INVENTORY_TIMEOUT_MS,
|
|
251
|
+
multiplex: true,
|
|
252
|
+
extraSshArgs: target.extraSshArgs,
|
|
253
|
+
});
|
|
240
254
|
if (res.code !== 0)
|
|
241
255
|
return null;
|
|
242
256
|
try {
|
|
@@ -402,9 +416,13 @@ async function runDevicesDoctor(opts) {
|
|
|
402
416
|
execPolicy: process.platform === 'win32'
|
|
403
417
|
? { platform: process.platform, policy: getEffectiveExecutionPolicy() }
|
|
404
418
|
: undefined,
|
|
419
|
+
windowsSshEnrollment: auditWindowsSshEnrollment(),
|
|
405
420
|
isolatedVersions: localReports
|
|
406
421
|
.filter((rep) => isVersionIsolated(rep.agent, rep.version))
|
|
407
422
|
.map((rep) => `${rep.agent}@${rep.version}`),
|
|
423
|
+
// Can the owner-delivery lane escalate a block from THIS box? Local only —
|
|
424
|
+
// remote boxes self-report it in their own `agents doctor --json`.
|
|
425
|
+
ownerSink: await probeOwnerSink(readMeta()),
|
|
408
426
|
}));
|
|
409
427
|
accounts[localName] = r.inventory?.signIn ?? {};
|
|
410
428
|
continue;
|
|
@@ -1177,7 +1195,7 @@ function checkPayload(device, drift) {
|
|
|
1177
1195
|
async function probeDeviceCheck(target) {
|
|
1178
1196
|
const isWin = /^win/i.test((target.platform ?? '').trim());
|
|
1179
1197
|
const remoteCmd = buildRemoteAgentsInvocation(['doctor', '--check', '--json'], undefined, isWin ? 'windows' : undefined, isWin ? undefined : { PATH: '$HOME/.agents/.cache/shims:$HOME/.local/bin:$PATH' });
|
|
1180
|
-
const res = await sshExecAsync(target.dialTarget, remoteCmd, { timeoutMs: 30000, multiplex: true });
|
|
1198
|
+
const res = await sshExecAsync(target.dialTarget, remoteCmd, { timeoutMs: 30000, multiplex: true, extraSshArgs: target.extraSshArgs });
|
|
1181
1199
|
if (res.code !== 0 && !res.stdout.trim()) {
|
|
1182
1200
|
throw new Error(res.timedOut ? 'timed out' : (res.stderr.trim() || `exit ${res.code ?? 'unknown'}`));
|
|
1183
1201
|
}
|
|
@@ -1206,6 +1224,7 @@ async function runDevicesCheck(opts, cwd) {
|
|
|
1206
1224
|
platform: t.device.platform,
|
|
1207
1225
|
skip: t.skip,
|
|
1208
1226
|
dialTarget: fleetDialTarget(t.device),
|
|
1227
|
+
extraSshArgs: deviceIdentityArgs(t.device),
|
|
1209
1228
|
}));
|
|
1210
1229
|
const remote = await fanOutDevices(remoteTargets, probeDeviceCheck);
|
|
1211
1230
|
const devices = [local];
|
|
@@ -1486,6 +1505,9 @@ export function registerDoctorCommand(program) {
|
|
|
1486
1505
|
? { platform: process.platform, policy: getEffectiveExecutionPolicy() }
|
|
1487
1506
|
: undefined,
|
|
1488
1507
|
isolatedVersions,
|
|
1508
|
+
// Can the owner-delivery lane (feed/notify) escalate a block from this
|
|
1509
|
+
// box? A factory that cannot escalate is not healthy (RUSH-2262).
|
|
1510
|
+
ownerSink: await probeOwnerSink(readMeta()),
|
|
1489
1511
|
});
|
|
1490
1512
|
if (opts.json) {
|
|
1491
1513
|
const overviewPayload = {
|
package/dist/commands/exec.js
CHANGED
|
@@ -554,7 +554,7 @@ export function registerRunCommand(program) {
|
|
|
554
554
|
.option('--until <signal>', 'Loop stop condition. `signal` reads <runDir>/loop-signal.json {continue,reason} each iteration; absent or continue:false stops (fail-closed). Loop only.')
|
|
555
555
|
.option('--interval <dur>', 'Loop delay between iterations ("0" back-to-back, "30m" paces). Loop only.')
|
|
556
556
|
.option('--where <spec>', 'Where this run\'s body executes (one placement door): local | device:<name> | auto | lease[:backend] | cloud[:provider]. Expands to --host/--lease/--cloud. Do not combine with those flags. See docs/00-concepts.md#placement.')
|
|
557
|
-
.option('--host <name>', 'Offload this run onto another machine over SSH — a device name, registered host, or user@host. Pass "auto" to pick
|
|
557
|
+
.option('--host <name>', 'Offload this run onto another machine over SSH — a device name, registered host, or user@host. Pass "auto" to pick the least-loaded reachable device where the requested agent is installed and signed in, keeping the run local when no remote is better. Same as --where device:<name>. See `agents devices`.')
|
|
558
558
|
.option('--device <name>', 'Alias of --host. Pass "auto" for affinity-based device pick (same as --where auto).')
|
|
559
559
|
.option('--remote-cwd <dir>', "Explicit host working directory for --host runs, used VERBATIM (overrides --cwd; usually --cwd suffices — it re-roots a local-home path onto the remote home). Pass a single-quoted '$HOME/…' or a valid remote absolute path; a local ~ expands here and won't exist there (/Users/you vs /home/you).")
|
|
560
560
|
.option('--no-follow', 'With --host, dispatch detached and return immediately (track via `agents hosts ps/logs`).')
|
|
@@ -947,13 +947,18 @@ export function registerRunCommand(program) {
|
|
|
947
947
|
if (!resolvedResumeSource && runAutoDefaultsToAffinity(options))
|
|
948
948
|
options.device = 'auto';
|
|
949
949
|
}
|
|
950
|
-
// --device auto / --host auto (and deprecated --smart):
|
|
950
|
+
// --device auto / --host auto (and deprecated --smart): live fleet pick.
|
|
951
951
|
// Harness is always the agent the user typed — never auto-picked.
|
|
952
952
|
// Affinity failure degrades to local (does not kill the run).
|
|
953
953
|
{
|
|
954
954
|
const { applyDeviceAutoToOptions } = await import('../lib/smart-launch.js');
|
|
955
|
-
const result = applyDeviceAutoToOptions(options, {
|
|
955
|
+
const result = await applyDeviceAutoToOptions(options, {
|
|
956
956
|
accountPickerRequested,
|
|
957
|
+
// `run auto` selects its harness after placement, so do not filter
|
|
958
|
+
// candidates against an arbitrary proxy harness at this stage.
|
|
959
|
+
agent: normalizedAgentSpec.split('@')[0] === RUN_AUTO_KEYWORD
|
|
960
|
+
? undefined
|
|
961
|
+
: (resolveAgentName(normalizedAgentSpec.split('@')[0]) ?? undefined),
|
|
957
962
|
});
|
|
958
963
|
if (!options.quiet && result.deprecationSmart) {
|
|
959
964
|
process.stderr.write(chalk.yellow('[agents] --smart is deprecated; use --device auto\n'));
|
|
@@ -964,7 +969,7 @@ export function registerRunCommand(program) {
|
|
|
964
969
|
if (!options.quiet && result.banner) {
|
|
965
970
|
const { hostLabel, deviceHint, acctNote } = result.banner;
|
|
966
971
|
process.stderr.write(chalk.gray(`[agents] device=auto → ${hostLabel}` +
|
|
967
|
-
(deviceHint ? ` (
|
|
972
|
+
(deviceHint ? ` (load ${deviceHint})` : '') +
|
|
968
973
|
` · ${acctNote}\n`));
|
|
969
974
|
}
|
|
970
975
|
}
|
|
@@ -2268,22 +2273,18 @@ export function registerRunCommand(program) {
|
|
|
2268
2273
|
process.exit(1);
|
|
2269
2274
|
}
|
|
2270
2275
|
version = resolvedRecoveryTarget.version;
|
|
2271
|
-
|
|
2272
|
-
if (canResumeNatively) {
|
|
2276
|
+
if (resolvedRecoveryTarget.mode === 'native') {
|
|
2273
2277
|
version = session.version;
|
|
2274
2278
|
resumeNative = true;
|
|
2275
2279
|
resumeSessionId = session.id;
|
|
2276
|
-
//
|
|
2277
|
-
//
|
|
2278
|
-
//
|
|
2279
|
-
//
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
// the caller passed one (they're overriding on purpose).
|
|
2283
|
-
if (!options.cwd && session.cwd)
|
|
2284
|
-
options.cwd = session.cwd;
|
|
2280
|
+
// The centralized recovery decision proves the transcript belongs to
|
|
2281
|
+
// this exact isolated home and resolves any harness-specific launch cwd.
|
|
2282
|
+
// Claude's indexed `session.cwd` is the first user-turn cwd, which may
|
|
2283
|
+
// differ from the earlier cwd that selected projects/<cwd-key>.
|
|
2284
|
+
if (!options.cwd && resolvedRecoveryTarget.cwd)
|
|
2285
|
+
options.cwd = resolvedRecoveryTarget.cwd;
|
|
2285
2286
|
if (!options.quiet)
|
|
2286
|
-
process.stderr.write(chalk.gray(`Resuming ${agent} ${session.shortId} (native)${version ? ` @${version}` : ''}
|
|
2287
|
+
process.stderr.write(chalk.gray(`Resuming ${agent} ${session.shortId} (native)${version ? ` @${version}` : ''} in ${options.cwd ?? cwd}\n`));
|
|
2287
2288
|
}
|
|
2288
2289
|
else {
|
|
2289
2290
|
// Tier-2: launch fresh with a /continue <id> first message; the agent
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Insights command —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* agents
|
|
9
|
-
*
|
|
10
|
-
* agents
|
|
11
|
-
* agents
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
2
|
+
* Insights command — one observe verb for "how work looks".
|
|
3
|
+
*
|
|
4
|
+
* Two data paths under one name (do not re-split into peer top-level commands):
|
|
5
|
+
*
|
|
6
|
+
* agents insights HOW you work (transcript content: tools, friction,
|
|
7
|
+
* rhythm, edits) — split by Claude account by default
|
|
8
|
+
* agents insights mix COUNTERS (sessions index + usage.db recipes:
|
|
9
|
+
* harness/model mix, token ratios, secrets, browser)
|
|
10
|
+
* agents insights <recipe> One baked mix recipe (harness-mix, tools-per-session, …)
|
|
11
|
+
* agents insights query Raw usage.db rows
|
|
12
|
+
*
|
|
13
|
+
* Sibling observe verbs (stay separate — different questions):
|
|
14
|
+
*
|
|
15
|
+
* agents cost what you spent ($ and duration)
|
|
16
|
+
* agents output what shipped (burn vs PRs and commits)
|
|
17
|
+
* agents usage live quota headroom
|
|
18
|
+
* agents perf latency (hooks, CLI commands, agent.run) — not popularity
|
|
19
|
+
* agents sessions stats which skills/slash-commands were explicitly invoked
|
|
20
|
+
*
|
|
21
|
+
* Why mix lives here (not a second top-level `trends`): two abstract "analytics"
|
|
22
|
+
* nouns taught agents and humans to guess. One verb, two engines — cheap SQL mix
|
|
23
|
+
* vs transcript facets. Latency stays on `perf` so it is never confused with mix.
|
|
21
24
|
*
|
|
22
25
|
* Modelled on Claude Code's `/insights`, with the difference that motivated it: that
|
|
23
26
|
* command reads one account's directory, while `balanced` rotation sprays sessions
|
|
@@ -27,6 +30,9 @@
|
|
|
27
30
|
* The deterministic report makes zero network calls. `--narrative` is opt-in and adds
|
|
28
31
|
* the coaching prose by piping the AGGREGATE (never raw transcripts) through a headless
|
|
29
32
|
* `claude -p`.
|
|
33
|
+
*
|
|
34
|
+
* `agents trends` is a thin deprecated alias of the mix tree only (see commands/trends.ts).
|
|
30
35
|
*/
|
|
31
36
|
import type { Command } from 'commander';
|
|
32
37
|
export declare function registerInsightsCommand(program: Command): void;
|
|
38
|
+
export declare function registerSessionsInsightsCommand(sessions: Command): void;
|