@phnx-labs/agents-cli 1.22.47 → 1.22.49
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 +401 -17
- package/README.md +66 -32
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +1 -0
- package/dist/cli/command-registry.js +2 -0
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +206 -32
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/browser.js +501 -103
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +0 -20
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/exec.js +52 -15
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +48 -5
- package/dist/commands/go.d.ts +3 -0
- package/dist/commands/go.js +16 -3
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +4 -3
- package/dist/commands/mailboxes.js +4 -3
- package/dist/commands/menubar.js +11 -4
- package/dist/commands/projects.d.ts +16 -1
- package/dist/commands/projects.js +58 -64
- package/dist/commands/resume.js +1 -1
- package/dist/commands/route.js +4 -4
- package/dist/commands/routines.js +3 -2
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-trace.js +1 -1
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +152 -52
- package/dist/commands/ssh.js +54 -7
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +184 -0
- package/dist/commands/view.d.ts +1 -1
- package/dist/commands/view.js +17 -34
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/accounting/usage.d.ts +14 -3
- package/dist/lib/accounting/usage.js +64 -18
- package/dist/lib/agent-spec/agents.js +11 -9
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.js +8 -3
- package/dist/lib/browser/profiles.d.ts +35 -101
- package/dist/lib/browser/profiles.js +130 -301
- package/dist/lib/browser/registry.d.ts +39 -0
- package/dist/lib/browser/registry.js +149 -0
- package/dist/lib/browser/remote-control.d.ts +8 -6
- package/dist/lib/browser/remote-control.js +8 -6
- package/dist/lib/browser/resolve-target.d.ts +68 -0
- package/dist/lib/browser/resolve-target.js +250 -0
- package/dist/lib/browser/runtime-state.d.ts +40 -16
- package/dist/lib/browser/runtime-state.js +92 -25
- package/dist/lib/browser/service.d.ts +28 -9
- package/dist/lib/browser/service.js +139 -62
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- package/dist/lib/byok-usage.js +2 -2
- package/dist/lib/claude-statusline.d.ts +29 -0
- package/dist/lib/claude-statusline.js +139 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +1 -1
- package/dist/lib/cloud/rush.js +1 -1
- package/dist/lib/computer/computer-rpc.js +2 -2
- package/dist/lib/computer/download.d.ts +15 -15
- package/dist/lib/computer/download.js +35 -98
- package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
- package/dist/lib/computer/ssh-tunnel.js +12 -22
- package/dist/lib/config-keys.d.ts +2 -8
- package/dist/lib/config-keys.js +2 -13
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +2 -2
- package/dist/lib/crabbox/lease.d.ts +9 -0
- package/dist/lib/crabbox/lease.js +70 -15
- package/dist/lib/crabbox/runtimes.js +1 -1
- package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
- package/dist/lib/daemon/account-state-daemon-service.js +26 -0
- package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
- package/dist/lib/daemon/browser-ipc-service.js +46 -0
- package/dist/lib/daemon/daemon.d.ts +3 -0
- package/dist/lib/daemon/daemon.js +212 -444
- package/dist/lib/daemon/device-probe-service.d.ts +26 -0
- package/dist/lib/daemon/device-probe-service.js +47 -0
- package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
- package/dist/lib/daemon/keychain-reap-service.js +32 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
- package/dist/lib/daemon/monitor-engine-service.js +29 -0
- package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
- package/dist/lib/daemon/secrets-broker-service.js +66 -0
- package/dist/lib/daemon/self-heal-service.d.ts +27 -0
- package/dist/lib/daemon/self-heal-service.js +48 -0
- package/dist/lib/daemon/service.d.ts +102 -0
- package/dist/lib/daemon/service.js +98 -0
- package/dist/lib/daemon/session-index-service.d.ts +22 -0
- package/dist/lib/daemon/session-index-service.js +40 -0
- package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
- package/dist/lib/daemon/state-dir-check-service.js +63 -0
- package/dist/lib/daemon/supervisor.d.ts +87 -0
- package/dist/lib/daemon/supervisor.js +298 -0
- package/dist/lib/daemon/watchdog-service.d.ts +19 -0
- package/dist/lib/daemon/watchdog-service.js +40 -0
- package/dist/lib/daemon-health.d.ts +15 -0
- package/dist/lib/daemon-health.js +29 -5
- package/dist/lib/daemon-services.d.ts +8 -1
- package/dist/lib/daemon-services.js +55 -0
- package/dist/lib/daemon-ticks.d.ts +13 -2
- package/dist/lib/daemon-ticks.js +20 -18
- package/dist/lib/device-config.d.ts +0 -2
- package/dist/lib/device-config.js +1 -23
- package/dist/lib/devices/registry.d.ts +1 -1
- package/dist/lib/devices/registry.js +1 -1
- package/dist/lib/devices/stats-cache.d.ts +20 -0
- package/dist/lib/devices/stats-cache.js +49 -5
- package/dist/lib/devices/worker-pick.d.ts +48 -0
- package/dist/lib/devices/worker-pick.js +113 -0
- package/dist/lib/exec.d.ts +67 -13
- package/dist/lib/exec.js +85 -29
- package/dist/lib/harness/adapters/cursor.js +8 -11
- package/dist/lib/helper-download.d.ts +108 -0
- package/dist/lib/helper-download.js +194 -0
- package/dist/lib/helper-versions.d.ts +52 -0
- package/dist/lib/helper-versions.js +58 -0
- package/dist/lib/hooks/cache.js +11 -0
- package/dist/lib/hooks/install.js +2 -2
- package/dist/lib/hosts/credentials.d.ts +1 -1
- package/dist/lib/hosts/credentials.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +7 -1
- package/dist/lib/hosts/dispatch.js +24 -5
- package/dist/lib/hosts/passthrough.d.ts +8 -15
- package/dist/lib/hosts/passthrough.js +19 -16
- package/dist/lib/hosts/providers/devices.d.ts +1 -1
- package/dist/lib/hosts/providers/devices.js +1 -1
- package/dist/lib/hosts/reconnect.d.ts +179 -21
- package/dist/lib/hosts/reconnect.js +266 -45
- package/dist/lib/installations/migrate.d.ts +4 -5
- package/dist/lib/installations/migrate.js +6 -7
- package/dist/lib/installations/shims.d.ts +1 -1
- package/dist/lib/installations/shims.js +23 -13
- package/dist/lib/installations/versions.js +25 -2
- package/dist/lib/linear-autoclose.d.ts +1 -1
- package/dist/lib/linear-autoclose.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/menubar/download-menubar.d.ts +47 -0
- package/dist/lib/menubar/download-menubar.js +60 -0
- package/dist/lib/menubar/install-menubar.d.ts +31 -7
- package/dist/lib/menubar/install-menubar.js +79 -21
- package/dist/lib/menubar/notify-desktop.d.ts +3 -3
- package/dist/lib/menubar/notify-desktop.js +6 -6
- package/dist/lib/model-tiers.d.ts +1 -1
- package/dist/lib/project-probe.d.ts +1 -1
- package/dist/lib/project-probe.js +1 -1
- package/dist/lib/project-pull.d.ts +1 -1
- package/dist/lib/project-pull.js +2 -2
- package/dist/lib/projects.d.ts +1 -1
- package/dist/lib/projects.js +1 -1
- package/dist/lib/sandbox.js +6 -7
- package/dist/lib/secrets/download-keychain.d.ts +47 -0
- package/dist/lib/secrets/download-keychain.js +70 -0
- package/dist/lib/secrets/install-helper.d.ts +20 -1
- package/dist/lib/secrets/install-helper.js +44 -3
- package/dist/lib/session/active.js +17 -1
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +138 -6
- package/dist/lib/session/discover.d.ts +21 -1
- package/dist/lib/session/discover.js +33 -5
- package/dist/lib/session/highlights.d.ts +49 -1
- package/dist/lib/session/highlights.js +95 -0
- package/dist/lib/session/host-link.d.ts +16 -2
- package/dist/lib/session/host-link.js +7 -1
- package/dist/lib/session/parse.js +33 -1
- package/dist/lib/session/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.js +11 -2
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +88 -0
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/tool-calls.d.ts +2 -0
- package/dist/lib/session/tool-calls.js +1 -1
- package/dist/lib/session/trajectory-html.d.ts +5 -1
- package/dist/lib/session/trajectory-html.js +42 -22
- package/dist/lib/session/trajectory-text.js +9 -2
- package/dist/lib/session/trajectory.js +53 -4
- package/dist/lib/session/types.d.ts +15 -0
- package/dist/lib/sha256-asset.d.ts +26 -0
- package/dist/lib/sha256-asset.js +41 -0
- package/dist/lib/share/backend.d.ts +95 -0
- package/dist/lib/share/backend.js +139 -0
- package/dist/lib/share/delete.d.ts +6 -2
- package/dist/lib/share/delete.js +8 -9
- package/dist/lib/share/html.d.ts +22 -0
- package/dist/lib/share/html.js +88 -0
- package/dist/lib/share/provision.d.ts +18 -4
- package/dist/lib/share/provision.js +38 -4
- package/dist/lib/share/publish.d.ts +39 -15
- package/dist/lib/share/publish.js +55 -26
- package/dist/lib/share/worker-template.js +171 -11
- package/dist/lib/sqlite.js +16 -3
- package/dist/lib/ssh-exec.d.ts +65 -0
- package/dist/lib/ssh-exec.js +120 -4
- package/dist/lib/startup/command-registry.js +1 -1
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +7 -3
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/terminal/types.js +1 -1
- package/dist/lib/tmux/orphan-reap.js +1 -1
- package/dist/lib/tmux/session.js +31 -0
- package/dist/lib/traces/backend.d.ts +29 -0
- package/dist/lib/traces/backend.js +46 -0
- package/dist/lib/traces/classify.d.ts +52 -0
- package/dist/lib/traces/classify.js +72 -0
- package/dist/lib/traces/config.d.ts +10 -0
- package/dist/lib/traces/config.js +2 -0
- package/dist/lib/traces/provision.d.ts +19 -0
- package/dist/lib/traces/provision.js +30 -0
- package/dist/lib/traces/sync.d.ts +150 -0
- package/dist/lib/traces/sync.js +447 -0
- package/dist/lib/traces/worker-template.d.ts +4 -0
- package/dist/lib/traces/worker-template.js +159 -0
- package/dist/lib/types.d.ts +18 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-refresh.d.ts +1 -1
- package/dist/lib/usage-refresh.js +11 -8
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +4 -6
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -26
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -135
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/usage-fleet.d.ts +0 -32
- package/dist/lib/usage-fleet.js +0 -125
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { setHelpSections } from '../lib/help.js';
|
|
3
|
+
import { syncTraces } from '../lib/traces/sync.js';
|
|
4
|
+
import { readSyncLedger } from '../lib/traces/sync.js';
|
|
5
|
+
import { managedTracesBaseUrl, resolveTracesBackend } from '../lib/traces/backend.js';
|
|
6
|
+
import { showUrl } from '../lib/open-url.js';
|
|
7
|
+
import { DEFAULT_CF_BUNDLE, readCloudflareCreds } from '../lib/share/config.js';
|
|
8
|
+
import { PHOENIX_ID_BASE } from '../lib/identity/client.js';
|
|
9
|
+
import { DEFAULT_BUCKET_NAME, DEFAULT_WORKER_NAME } from '../lib/traces/config.js';
|
|
10
|
+
import { DEFAULT_TRACES_DOMAIN } from '../lib/traces/backend.js';
|
|
11
|
+
import { provisionTraces } from '../lib/traces/provision.js';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Help text
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const SYNC_EXAMPLES = `
|
|
16
|
+
$ agents traces sync
|
|
17
|
+
Push this device's derived, redacted trajectories to your Phoenix account.
|
|
18
|
+
|
|
19
|
+
$ agents traces sync --limit 20
|
|
20
|
+
Sync at most 20 sessions (useful for a first run on a large fleet device).
|
|
21
|
+
|
|
22
|
+
$ agents traces sync --dry-run --out ./trace-preview
|
|
23
|
+
Compute the shards from your real sessions.db and WRITE them locally — no
|
|
24
|
+
Phoenix sign-in, no worker, no upload. Point a console at ./trace-preview to
|
|
25
|
+
see your real trajectories before wiring the hosted path.
|
|
26
|
+
`.trimStart();
|
|
27
|
+
const SYNC_NOTES = `
|
|
28
|
+
Reads from the local sessions.db, computes a derived SessionTrajectory (steps +
|
|
29
|
+
gaps + stats, no raw transcript text), redacts secrets, and PUTs each to
|
|
30
|
+
the agents-traces store.
|
|
31
|
+
|
|
32
|
+
An incremental gate (traces-sync.json ledger) means re-runs only upload
|
|
33
|
+
sessions whose file_mtime_ms changed since the last sync.
|
|
34
|
+
|
|
35
|
+
Sign in first with: agents auth login
|
|
36
|
+
`.trimStart();
|
|
37
|
+
const STATUS_EXAMPLES = `
|
|
38
|
+
$ agents traces status
|
|
39
|
+
Show what's been synced — session count and last sync time for this device.
|
|
40
|
+
`.trimStart();
|
|
41
|
+
const OPEN_EXAMPLES = `
|
|
42
|
+
$ agents traces open
|
|
43
|
+
Open the Phoenix Evals console for your account.
|
|
44
|
+
`.trimStart();
|
|
45
|
+
const SETUP_EXAMPLES = `
|
|
46
|
+
$ agents traces setup
|
|
47
|
+
Provision the private agents-traces Worker, R2 bucket, and managed domain.
|
|
48
|
+
|
|
49
|
+
$ agents traces setup --account <id> --token <token>
|
|
50
|
+
Provision non-interactively with explicit Cloudflare credentials.
|
|
51
|
+
`.trimStart();
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Command handlers
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
async function handleSync(opts) {
|
|
56
|
+
const limit = opts.limit !== undefined ? parseInt(opts.limit, 10) : undefined;
|
|
57
|
+
const dryRun = opts.dryRun === true;
|
|
58
|
+
if (dryRun && !opts.out) {
|
|
59
|
+
console.error(chalk.red('traces sync --dry-run requires --out <dir>'));
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// A dry-run computes locally and needs no Phoenix backend; a real sync does.
|
|
64
|
+
if (!dryRun) {
|
|
65
|
+
try {
|
|
66
|
+
resolveTracesBackend();
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
console.error(chalk.red(err.message));
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
console.log(chalk.dim(dryRun ? `Computing traces locally → ${opts.out}` : 'Syncing traces…'));
|
|
75
|
+
let result;
|
|
76
|
+
try {
|
|
77
|
+
result = await syncTraces({ limit, dryRun, outDir: opts.out });
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
console.error(chalk.red(`Sync failed: ${err.message}`));
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const parts = [];
|
|
85
|
+
if (result.uploaded > 0) {
|
|
86
|
+
parts.push(chalk.green(`${result.uploaded} ${dryRun ? 'written' : 'uploaded'}`));
|
|
87
|
+
}
|
|
88
|
+
if (result.skipped > 0) {
|
|
89
|
+
parts.push(chalk.dim(`${result.skipped} skipped`));
|
|
90
|
+
}
|
|
91
|
+
if (result.errors > 0) {
|
|
92
|
+
parts.push(chalk.yellow(`${result.errors} errors`));
|
|
93
|
+
}
|
|
94
|
+
if (parts.length === 0) {
|
|
95
|
+
parts.push(chalk.dim('nothing new'));
|
|
96
|
+
}
|
|
97
|
+
console.log(parts.join(chalk.dim(' · ')));
|
|
98
|
+
}
|
|
99
|
+
async function handleStatus() {
|
|
100
|
+
const ledger = readSyncLedger();
|
|
101
|
+
if (!ledger.lastSyncMtime) {
|
|
102
|
+
console.log(chalk.dim('No sync recorded on this device. Run: agents traces sync'));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const when = new Date(ledger.lastSyncMtime).toLocaleString();
|
|
106
|
+
console.log(`Last sync: ${chalk.bold(when)}`);
|
|
107
|
+
console.log(chalk.dim('Run `agents traces sync` to push new sessions.'));
|
|
108
|
+
}
|
|
109
|
+
async function handleOpen() {
|
|
110
|
+
// M2 console — for now open the base URL which will route to the console once deployed.
|
|
111
|
+
const url = managedTracesBaseUrl();
|
|
112
|
+
console.log(chalk.dim(`Opening ${url}`));
|
|
113
|
+
const outcome = await showUrl(url);
|
|
114
|
+
if (outcome.via === 'none') {
|
|
115
|
+
console.log(url);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function handleSetup(opts) {
|
|
119
|
+
const { input } = await import('@inquirer/prompts');
|
|
120
|
+
const { apiToken, accountId: bundledAccountId } = readCloudflareCreds(opts.bundle, {
|
|
121
|
+
apiToken: opts.token,
|
|
122
|
+
accountId: opts.account,
|
|
123
|
+
});
|
|
124
|
+
const accountId = opts.account ?? bundledAccountId ?? await input({ message: 'Cloudflare account id' });
|
|
125
|
+
if (!accountId)
|
|
126
|
+
throw new Error('A Cloudflare account id is required.');
|
|
127
|
+
const result = await provisionTraces({
|
|
128
|
+
apiToken,
|
|
129
|
+
accountId,
|
|
130
|
+
workerName: opts.worker,
|
|
131
|
+
bucketName: opts.bucket,
|
|
132
|
+
domain: opts.domain,
|
|
133
|
+
phoenixIdBase: PHOENIX_ID_BASE,
|
|
134
|
+
});
|
|
135
|
+
console.log(chalk.green(`Traces endpoint ready → ${chalk.bold(result.baseUrl)}`));
|
|
136
|
+
}
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Registration
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
export function registerTracesCommands(program) {
|
|
141
|
+
const tracesCmd = program
|
|
142
|
+
.command('traces')
|
|
143
|
+
.description("Sync this device's derived, redacted trajectories to your Phoenix account");
|
|
144
|
+
const setupCmd = tracesCmd
|
|
145
|
+
.command('setup')
|
|
146
|
+
.description('Provision the private Cloudflare Worker and R2 bucket for traces')
|
|
147
|
+
.option('--bundle <name>', 'secrets bundle holding the Cloudflare API token', DEFAULT_CF_BUNDLE)
|
|
148
|
+
.option('--worker <name>', 'Worker name', DEFAULT_WORKER_NAME)
|
|
149
|
+
.option('--bucket <name>', 'R2 bucket name', DEFAULT_BUCKET_NAME)
|
|
150
|
+
.option('--account <id>', 'Cloudflare account id (else read from the bundle / prompt)')
|
|
151
|
+
.option('--token <token>', 'Cloudflare API token (else read from the --bundle)')
|
|
152
|
+
.option('--domain <host>', 'custom domain to map', DEFAULT_TRACES_DOMAIN)
|
|
153
|
+
.action(async (opts) => {
|
|
154
|
+
try {
|
|
155
|
+
await handleSetup(opts);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
console.error(chalk.red(err.message));
|
|
159
|
+
process.exitCode = 1;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
setHelpSections(setupCmd, { examples: SETUP_EXAMPLES });
|
|
163
|
+
// sync
|
|
164
|
+
const syncCmd = tracesCmd
|
|
165
|
+
.command('sync')
|
|
166
|
+
.description('Push derived, redacted trajectories (incremental)')
|
|
167
|
+
.option('--limit <n>', 'Maximum number of sessions to sync in this run')
|
|
168
|
+
.option('--dry-run', 'Compute the shards from real sessions.db and WRITE them locally (no auth, no upload)')
|
|
169
|
+
.option('--out <dir>', 'Directory to write index.json + sessions/<id>.json (required with --dry-run)')
|
|
170
|
+
.action(handleSync);
|
|
171
|
+
setHelpSections(syncCmd, { examples: SYNC_EXAMPLES, notes: SYNC_NOTES });
|
|
172
|
+
// status
|
|
173
|
+
const statusCmd = tracesCmd
|
|
174
|
+
.command('status')
|
|
175
|
+
.description('Show last sync time for this device')
|
|
176
|
+
.action(handleStatus);
|
|
177
|
+
setHelpSections(statusCmd, { examples: STATUS_EXAMPLES });
|
|
178
|
+
// open
|
|
179
|
+
const openCmd = tracesCmd
|
|
180
|
+
.command('open')
|
|
181
|
+
.description('Open the Phoenix Evals console')
|
|
182
|
+
.action(handleOpen);
|
|
183
|
+
setHelpSections(openCmd, { examples: OPEN_EXAMPLES });
|
|
184
|
+
}
|
package/dist/commands/view.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { ResourceSection, ViewJsonAgent } from '../lib/view-types.js';
|
|
|
15
15
|
export type { ResourceItemJson, ResourceSection, SyncState, VersionResourcesJson, ViewJsonAgent, ViewJsonVersion } from '../lib/view-types.js';
|
|
16
16
|
import { type ProfileSummary } from '../lib/profiles.js';
|
|
17
17
|
import { type ByokUsageResult } from '../lib/byok-usage.js';
|
|
18
|
-
export declare function viewUsageSummaryOptions(agentId: AgentId, signedIn: boolean, usageInfo: UsageInfo | undefined, maxWindows: number | undefined): FormatUsageSummaryOpts;
|
|
18
|
+
export declare function viewUsageSummaryOptions(agentId: AgentId, signedIn: boolean, usageInfo: UsageInfo | undefined, maxWindows: number | undefined, version?: string): FormatUsageSummaryOpts;
|
|
19
19
|
/** Shared account identity formatter, re-exported for the view-specific tests. */
|
|
20
20
|
export declare const accountColumnLabel: typeof accountDisplayLabel;
|
|
21
21
|
/**
|
package/dist/commands/view.js
CHANGED
|
@@ -7,7 +7,7 @@ import * as path from 'path';
|
|
|
7
7
|
import { AGENTS, ALL_AGENT_IDS, accountDisplayLabel, getAllCliStates, getUnmanagedCliState, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
|
|
8
8
|
import { ambientClaudeToken, loginHint } from '../lib/signin-badge.js';
|
|
9
9
|
import { machineId } from '../lib/machine-id.js';
|
|
10
|
-
import { authCacheKey,
|
|
10
|
+
import { authCacheKey, readAuthHealthCache } from '../lib/auth-health.js';
|
|
11
11
|
import { agentReportsUsage, classifyUsageErrorKind, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageBenignState, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, isUsageHeadlessScopeError, } from '../lib/accounting/usage.js';
|
|
12
12
|
import { readManifest } from '../lib/manifest.js';
|
|
13
13
|
import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/installations/versions.js';
|
|
@@ -32,7 +32,7 @@ import { renderHarnessDetail } from './harness.js';
|
|
|
32
32
|
import { confirm } from '@inquirer/prompts';
|
|
33
33
|
import { formatPath, isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
34
34
|
import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
|
|
35
|
-
export function viewUsageSummaryOptions(agentId, signedIn, usageInfo, maxWindows) {
|
|
35
|
+
export function viewUsageSummaryOptions(agentId, signedIn, usageInfo, maxWindows, version) {
|
|
36
36
|
const headless = isUsageHeadlessScopeError(usageInfo?.error);
|
|
37
37
|
const benignState = usageInfo ? getUsageBenignState(usageInfo) : null;
|
|
38
38
|
return {
|
|
@@ -40,9 +40,15 @@ export function viewUsageSummaryOptions(agentId, signedIn, usageInfo, maxWindows
|
|
|
40
40
|
unverified: !headless && !!usageInfo?.snapshot && !!usageInfo.error,
|
|
41
41
|
headless,
|
|
42
42
|
maxWindows,
|
|
43
|
+
expectedWindows: agentId === 'claude'
|
|
44
|
+
? [{ key: 'session', shortLabel: 'S' }, { key: 'week', shortLabel: 'W' }]
|
|
45
|
+
: undefined,
|
|
43
46
|
errorKind: classifyUsageErrorKind(usageInfo?.error),
|
|
44
47
|
errorDetail: usageInfo?.error ?? null,
|
|
45
48
|
benignState,
|
|
49
|
+
noRecentUsageLabel: agentId === 'grok'
|
|
50
|
+
? `run grok${version ? `@${version}` : ''} once to refresh usage`
|
|
51
|
+
: null,
|
|
46
52
|
};
|
|
47
53
|
}
|
|
48
54
|
/** Shared account identity formatter, re-exported for the view-specific tests. */
|
|
@@ -92,8 +98,6 @@ export function compareAccountOrderedVersions(a, b, globalDefault) {
|
|
|
92
98
|
* views leave the cap unset and show every blocking window.
|
|
93
99
|
*/
|
|
94
100
|
const OVERVIEW_MAX_USAGE_WINDOWS = 2;
|
|
95
|
-
/** Fixed width for the last-active column ("just now", "8h ago", "2d ago"). */
|
|
96
|
-
const LAST_ACTIVE_COL_WIDTH = 10;
|
|
97
101
|
/**
|
|
98
102
|
* Join fixed view columns with a consistent two-space gutter. Empty trailing
|
|
99
103
|
* columns are dropped so a row without an auth chip does not grow a dangling
|
|
@@ -312,22 +316,6 @@ function renderHostClisSection(cwd) {
|
|
|
312
316
|
console.log(` ${chalk.red('error')} ${chalk.gray(err.file)}: ${chalk.gray(err.reason)}`);
|
|
313
317
|
}
|
|
314
318
|
}
|
|
315
|
-
/**
|
|
316
|
-
* A compact live-auth chip for a version row, read from the fleet auth-health
|
|
317
|
-
* cache (written by `agents fleet ping` / the daemon). Empty when the install
|
|
318
|
-
* hasn't been probed — this is the local "signed in" flag's ground-truth
|
|
319
|
-
* companion: ● live, ○ revoked (re-login), ◐ unverified/limited, plus its age.
|
|
320
|
-
*/
|
|
321
|
-
function liveAuthChip(cache, host, agentId, version) {
|
|
322
|
-
const h = cache[authCacheKey(host, agentId, version)];
|
|
323
|
-
if (!h)
|
|
324
|
-
return '';
|
|
325
|
-
const glyph = h.verdict === 'live' ? chalk.green('●')
|
|
326
|
-
: h.verdict === 'revoked' ? chalk.red('○')
|
|
327
|
-
: h.verdict === 'expired' ? chalk.yellow('○')
|
|
328
|
-
: chalk.yellow('◐');
|
|
329
|
-
return `${glyph} ${chalk.gray(formatCheckedAge(h.checkedAt))}`;
|
|
330
|
-
}
|
|
331
319
|
async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
332
320
|
const spinnerText = filterAgentId
|
|
333
321
|
? `Checking ${agentLabel(filterAgentId)} agents...`
|
|
@@ -378,9 +366,6 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
378
366
|
}
|
|
379
367
|
}
|
|
380
368
|
// Shim healing is silent — users don't need to know about internal repairs
|
|
381
|
-
const selfHost = machineId();
|
|
382
|
-
// Read the auth-health cache once (not per version row — see the batching note above).
|
|
383
|
-
const authCache = readAuthHealthCache();
|
|
384
369
|
// Pre-fetch account info for all versions in parallel. Spinner stays up through
|
|
385
370
|
// account + usage so a multi-account cold path doesn't leave a blank terminal
|
|
386
371
|
// after "Checking…" vanishes (the hang the screenshots caught).
|
|
@@ -548,7 +533,7 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
548
533
|
const info = rawInfo ? mergeCanonical(rawInfo) : undefined;
|
|
549
534
|
const usageKey = getUsageLookupKey(info);
|
|
550
535
|
const usageInfo = usageKey ? usageByKey.get(usageKey) : undefined;
|
|
551
|
-
const usageStr = formatUsageSummary(info?.plan || null, usageInfo?.snapshot || null, maxPlanWidth, viewUsageSummaryOptions(agentId, !!info?.signedIn, usageInfo, usageWindowCap));
|
|
536
|
+
const usageStr = formatUsageSummary(info?.plan || null, usageInfo?.snapshot || null, maxPlanWidth, viewUsageSummaryOptions(agentId, !!info?.signedIn, usageInfo, usageWindowCap, v));
|
|
552
537
|
maxUsageWidth = Math.max(maxUsageWidth, stringWidth(usageStr));
|
|
553
538
|
const statusStr = formatUsageStatusBadge(info?.usageStatus);
|
|
554
539
|
maxStatusWidth = Math.max(maxStatusWidth, stringWidth(statusStr));
|
|
@@ -608,11 +593,12 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
608
593
|
}
|
|
609
594
|
const hasEmail = !!vInfo?.email;
|
|
610
595
|
const signedIn = !!vInfo?.signedIn;
|
|
611
|
-
const usageStr = formatUsageSummary(vInfo?.plan || null, usageInfo?.snapshot || null, maxPlanWidth, viewUsageSummaryOptions(agentId, signedIn, usageInfo, usageWindowCap));
|
|
596
|
+
const usageStr = formatUsageSummary(vInfo?.plan || null, usageInfo?.snapshot || null, maxPlanWidth, viewUsageSummaryOptions(agentId, signedIn, usageInfo, usageWindowCap, version));
|
|
612
597
|
const hasUsage = usageStr.length > 0;
|
|
613
598
|
// Only show lastActive for versions with an actual logged-in account.
|
|
614
599
|
// Otherwise it reflects install time (misleading "just now" for fresh installs).
|
|
615
|
-
const
|
|
600
|
+
const lastActive = vInfo && hasEmail ? formatLastActive(vInfo.lastActive) : '';
|
|
601
|
+
const activeStr = lastActive;
|
|
616
602
|
const hasActive = activeStr.length > 0;
|
|
617
603
|
// The model now has its own column above; keep only the run mode here.
|
|
618
604
|
const runDefaults = resolveRunDefaults(agentId, version);
|
|
@@ -649,16 +635,12 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
649
635
|
const statusStr = formatUsageStatusBadge(vInfo?.usageStatus);
|
|
650
636
|
parts.push(padToWidth(statusStr, maxStatusWidth));
|
|
651
637
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
parts.push(hasActive ? padToWidth(activeStr, LAST_ACTIVE_COL_WIDTH) : ' '.repeat(LAST_ACTIVE_COL_WIDTH));
|
|
638
|
+
if (hasActive)
|
|
639
|
+
parts.push(activeStr);
|
|
655
640
|
}
|
|
656
641
|
if (runDefaultBits.length > 0) {
|
|
657
642
|
parts.push(chalk.gray(`run ${runDefaultBits.join(' ')}`));
|
|
658
643
|
}
|
|
659
|
-
const authChip = liveAuthChip(authCache, selfHost, agentId, version);
|
|
660
|
-
if (authChip)
|
|
661
|
-
parts.push(authChip);
|
|
662
644
|
console.log(joinViewColumns(parts));
|
|
663
645
|
if (showPaths) {
|
|
664
646
|
const versionDir = getVersionDir(agentId, version);
|
|
@@ -747,7 +729,8 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
747
729
|
const gUsageKey = getUsageLookupKey(gInfo);
|
|
748
730
|
const gUsage = gUsageKey ? usageByKey.get(gUsageKey) : undefined;
|
|
749
731
|
const gUsageStr = formatUsageSummary(gInfo?.plan || null, gUsage?.snapshot || null, 3, viewUsageSummaryOptions(agentId, !!gInfo?.signedIn, gUsage, usageWindowCap));
|
|
750
|
-
const
|
|
732
|
+
const gLastActive = gInfo ? formatLastActive(gInfo.lastActive) : '';
|
|
733
|
+
const gActiveStr = gLastActive;
|
|
751
734
|
if (gInfo?.email || gUsageStr || gActiveStr || gInfo?.signedIn) {
|
|
752
735
|
const gDisplay = accountColumnLabel(gInfo);
|
|
753
736
|
parts.push(gDisplay ? chalk.cyan(padToWidth(gDisplay, gMaxEmail)) : ' '.repeat(gMaxEmail));
|
|
@@ -758,7 +741,7 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
758
741
|
parts.push(padToWidth(formatUsageStatusBadge(gInfo?.usageStatus), gMaxStatusWidth));
|
|
759
742
|
}
|
|
760
743
|
if (gActiveStr)
|
|
761
|
-
parts.push(
|
|
744
|
+
parts.push(gActiveStr);
|
|
762
745
|
console.log(joinViewColumns(parts));
|
|
763
746
|
if (showPaths && cliState?.path) {
|
|
764
747
|
console.log(chalk.gray(` ${cliState.path}`));
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* - `__vault-age-helper`
|
|
15
15
|
* - `__secrets-get` / `__secrets-ping` / `__secrets-lock` (SYNC_* tokens)
|
|
16
16
|
* - `__shim`
|
|
17
|
+
* - `__claude-statusline`
|
|
17
18
|
* - `__daemon-run`
|
|
18
19
|
*
|
|
19
20
|
* The tokens are imported from the leaf module sync-commands.ts — the SAME
|
|
@@ -70,6 +71,10 @@ if (process.argv[2] === '__shim') {
|
|
|
70
71
|
const code = await execShimPassthrough(agent, rawArgs, process.cwd(), pinned || undefined);
|
|
71
72
|
process.exit(code);
|
|
72
73
|
}
|
|
74
|
+
if (process.argv[2] === '__claude-statusline') {
|
|
75
|
+
const { runClaudeStatusLine } = await import('./lib/claude-statusline.js');
|
|
76
|
+
process.exit(await runClaudeStatusLine());
|
|
77
|
+
}
|
|
73
78
|
if (process.argv[2] === '__daemon-run') {
|
|
74
79
|
const { runDaemon, log: daemonLog } = await import('./lib/daemon/daemon.js');
|
|
75
80
|
// RUSH-2418: the daemon is the one always-on process here, and it ran with no
|
|
@@ -125,6 +125,8 @@ export interface UsageSnapshot {
|
|
|
125
125
|
capturedAt: Date | null;
|
|
126
126
|
windows: UsageWindow[];
|
|
127
127
|
plan?: string | null;
|
|
128
|
+
/** Action that makes an event-fed source emit a current reading. */
|
|
129
|
+
refreshHint?: string | null;
|
|
128
130
|
/**
|
|
129
131
|
* A refusal observed from a real harness run, independent of API windows.
|
|
130
132
|
* `session_limit` recovers on a clock (`resetsAt`). `out_of_credits` is a
|
|
@@ -306,6 +308,11 @@ export interface FormatUsageSummaryOpts {
|
|
|
306
308
|
* column width; single-agent and detail views leave this unset.
|
|
307
309
|
*/
|
|
308
310
|
maxWindows?: number;
|
|
311
|
+
/** Windows that must keep a visible slot even when the provider omits one. */
|
|
312
|
+
expectedWindows?: Array<{
|
|
313
|
+
key: string;
|
|
314
|
+
shortLabel: string;
|
|
315
|
+
}>;
|
|
309
316
|
/**
|
|
310
317
|
* The classified cause of `usageInfo.error` (RUSH-3040), from
|
|
311
318
|
* {@link classifyUsageErrorKind}. Lets the no-bars branch below name the
|
|
@@ -326,6 +333,8 @@ export interface FormatUsageSummaryOpts {
|
|
|
326
333
|
errorDetail?: string | null;
|
|
327
334
|
/** Benign state from {@link getUsageBenignState}; never sourced from `UsageInfo.error`. */
|
|
328
335
|
benignState?: UsageBenignState | null;
|
|
336
|
+
/** Provider-specific replacement for the generic no-local-event marker. */
|
|
337
|
+
noRecentUsageLabel?: string | null;
|
|
329
338
|
}
|
|
330
339
|
/** Format a one-line usage summary with compact bars for inline display. */
|
|
331
340
|
export declare function formatUsageSummary(plan: string | null, snapshot: UsageSnapshot | null, planWidth?: number, opts?: FormatUsageSummaryOpts): string;
|
|
@@ -552,7 +561,7 @@ export declare function normalizeDroidWindows(data: DroidBillingLimitsResponse):
|
|
|
552
561
|
* a file-based setup-token and, when none is provisioned, gets `null` — it never
|
|
553
562
|
* reads Claude Code's interactive login (transmitting that ACL-bound OAuth token
|
|
554
563
|
* to Anthropic's API is what gets it revoked; see the branch body and
|
|
555
|
-
* docs/
|
|
564
|
+
* docs/secrets.md). It is OFF by default so full-credential
|
|
556
565
|
* callers that refresh (`isClaudeAuthValid` -> `getClaudeAccessToken`) still
|
|
557
566
|
* read the interactive login. Rush Cloud dispatch does not call this helper
|
|
558
567
|
* at all (SING-1b: the account manifest is email-only; RUSH-2359 removed the
|
|
@@ -588,6 +597,8 @@ export declare function readClaudeUsageCache(usageKey: string, cachePath?: strin
|
|
|
588
597
|
export declare function pruneExpiredClaudeUsageCacheEntry(usageKey: string, cachePath?: string, now?: Date): void;
|
|
589
598
|
/** Write a usage snapshot to the on-disk cache. */
|
|
590
599
|
export declare function writeClaudeUsageCache(usageKey: string, snapshot: UsageSnapshot, cachePath?: string): void;
|
|
600
|
+
/** Atomically merge partial native Claude windows into the current fresh row. */
|
|
601
|
+
export declare function mergeClaudeUsageCacheWindows(usageKey: string, snapshot: UsageSnapshot, cachePath?: string): void;
|
|
591
602
|
/**
|
|
592
603
|
* Persist a Claude tokens/credits exhaustion (`out of usage credits` / `monthly
|
|
593
604
|
* spend limit`) from a real run. Unlike a rate/session limit this does NOT reset
|
|
@@ -614,8 +625,8 @@ export declare function parseClaudeSessionLimitReset(text: string, nowMs?: numbe
|
|
|
614
625
|
* Returns true only when a valid access token can be obtained.
|
|
615
626
|
*/
|
|
616
627
|
export declare function isClaudeAuthValid(home?: string): Promise<boolean>;
|
|
617
|
-
/** Render a colored
|
|
618
|
-
export declare function renderBar(usedPercent: number, length: number
|
|
628
|
+
/** Render a proportional colored progress bar at one-eighth-cell resolution. */
|
|
629
|
+
export declare function renderBar(usedPercent: number, length: number): string;
|
|
619
630
|
/** Return a chalk color function based on the usage percentage threshold. */
|
|
620
631
|
export declare function getUsageColor(usedPercent: number): (text: string) => string;
|
|
621
632
|
/** Parse the latest billing info from Grok's unified log. */
|
|
@@ -26,6 +26,7 @@ import { getCacheDir } from '../state.js';
|
|
|
26
26
|
import { mapBounded } from '../concurrency.js';
|
|
27
27
|
import { atomicWriteFileSync, ensureLockTarget, withFileLock } from '../fs-atomic.js';
|
|
28
28
|
import { withRefreshLease } from '../refresh-coordinator.js';
|
|
29
|
+
import { padToWidth } from '../session/width.js';
|
|
29
30
|
const execFileAsync = promisify(execFile);
|
|
30
31
|
const CLAUDE_USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
|
|
31
32
|
const CLAUDE_TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
|
|
@@ -216,6 +217,7 @@ const COMPACT_BAR_LEN = 5;
|
|
|
216
217
|
const USAGE_BAR_LEN = 10;
|
|
217
218
|
const FULL = '\u2588';
|
|
218
219
|
const EMPTY = '\u2591';
|
|
220
|
+
const PARTIAL_BLOCKS = ['', '\u258F', '\u258E', '\u258D', '\u258C', '\u258B', '\u258A', '\u2589'];
|
|
219
221
|
/** Construct the benign no-local-log result without overloading `error`. */
|
|
220
222
|
export function usageNoRecentUsageInfo() {
|
|
221
223
|
return { snapshot: null, error: null, [USAGE_BENIGN_STATE]: 'no-recent-usage' };
|
|
@@ -498,11 +500,20 @@ export function formatUsageSummary(plan, snapshot, planWidth = 3, opts) {
|
|
|
498
500
|
// multi-meter agent cannot force the whole table to wrap.
|
|
499
501
|
const selected = pickCompactUsageWindows(snapshot.windows, opts?.maxWindows);
|
|
500
502
|
const hidden = Math.max(0, snapshot.windows.filter((w) => w.key !== 'sonnet_week').length - selected.length);
|
|
501
|
-
const
|
|
503
|
+
const expected = opts?.expectedWindows;
|
|
504
|
+
const windowsToRender = expected
|
|
505
|
+
? expected.map(({ key, shortLabel }) => ({ window: selected.find((item) => item.key === key), shortLabel }))
|
|
506
|
+
: selected.map((window) => ({ window, shortLabel: window.shortLabel }));
|
|
507
|
+
const windowParts = windowsToRender.map(({ window, shortLabel }, index) => {
|
|
508
|
+
if (!window) {
|
|
509
|
+
const missing = chalk.red(`${shortLabel}: ${FULL.repeat(COMPACT_BAR_LEN)} unavailable`);
|
|
510
|
+
return index < windowsToRender.length - 1 ? padToWidth(missing, 20) : missing;
|
|
511
|
+
}
|
|
502
512
|
const bar = renderCompactUsageBar(window.usedPercent);
|
|
503
513
|
const pct = colorUsage(`${Math.round(window.usedPercent)}%`, window.usedPercent);
|
|
504
514
|
const reset = window.resetsAt ? chalk.dim(` (${formatResetHint(window.resetsAt)})`) : '';
|
|
505
|
-
|
|
515
|
+
const rendered = `${chalk.gray(`${shortLabel}:`)} ${bar} ${pct}${reset}`;
|
|
516
|
+
return index < windowsToRender.length - 1 ? padToWidth(rendered, 20) : rendered;
|
|
506
517
|
});
|
|
507
518
|
if (hidden > 0) {
|
|
508
519
|
windowParts.push(chalk.dim(`+${hidden}`));
|
|
@@ -510,6 +521,9 @@ export function formatUsageSummary(plan, snapshot, planWidth = 3, opts) {
|
|
|
510
521
|
if (windowParts.length > 0) {
|
|
511
522
|
parts.push(windowParts.join(' '));
|
|
512
523
|
}
|
|
524
|
+
else if (snapshot.refreshHint) {
|
|
525
|
+
parts.push(chalk.dim(snapshot.refreshHint));
|
|
526
|
+
}
|
|
513
527
|
// The bars came from the cache and the live read that should have confirmed
|
|
514
528
|
// them failed, so they are the last thing we saw — not the current state.
|
|
515
529
|
// Drawing them unmarked is what let a 26h-old "48% used" read as fact.
|
|
@@ -528,7 +542,7 @@ export function formatUsageSummary(plan, snapshot, planWidth = 3, opts) {
|
|
|
528
542
|
parts.push(chalk.dim(USAGE_HEADLESS_SCOPE_MARKER));
|
|
529
543
|
}
|
|
530
544
|
else if (opts?.benignState === 'no-recent-usage') {
|
|
531
|
-
parts.push(chalk.dim(USAGE_NO_RECENT_USAGE_MARKER));
|
|
545
|
+
parts.push(chalk.dim(opts.noRecentUsageLabel || USAGE_NO_RECENT_USAGE_MARKER));
|
|
532
546
|
}
|
|
533
547
|
else if (opts?.unavailable) {
|
|
534
548
|
// Signed-in account we could NOT fetch usage for (no live token in a reachable
|
|
@@ -1410,7 +1424,7 @@ function deleteCachedClaudeOauth(service) {
|
|
|
1410
1424
|
* a file-based setup-token and, when none is provisioned, gets `null` — it never
|
|
1411
1425
|
* reads Claude Code's interactive login (transmitting that ACL-bound OAuth token
|
|
1412
1426
|
* to Anthropic's API is what gets it revoked; see the branch body and
|
|
1413
|
-
* docs/
|
|
1427
|
+
* docs/secrets.md). It is OFF by default so full-credential
|
|
1414
1428
|
* callers that refresh (`isClaudeAuthValid` -> `getClaudeAccessToken`) still
|
|
1415
1429
|
* read the interactive login. Rush Cloud dispatch does not call this helper
|
|
1416
1430
|
* at all (SING-1b: the account manifest is email-only; RUSH-2359 removed the
|
|
@@ -1443,7 +1457,7 @@ export async function loadClaudeOauth(home, opts) {
|
|
|
1443
1457
|
// interactive credential used programmatically, which Anthropic flags and
|
|
1444
1458
|
// revokes (the fleet-wide-logout class, RUSH-1822), and which violates the
|
|
1445
1459
|
// invariant that the interactive/rotating login is untouchable
|
|
1446
|
-
// (docs/
|
|
1460
|
+
// (docs/secrets.md). Report unprovisioned (-> probe
|
|
1447
1461
|
// token 'missing' -> auth-health 'unconfigured', benign for rotation); seed a
|
|
1448
1462
|
// setup-token via the mint-auth path to restore usage/probe for the account.
|
|
1449
1463
|
return null;
|
|
@@ -1604,6 +1618,32 @@ export function writeClaudeUsageCache(usageKey, snapshot, cachePath = getClaudeU
|
|
|
1604
1618
|
/* best-effort cache write — lock busy or disk full */
|
|
1605
1619
|
}
|
|
1606
1620
|
}
|
|
1621
|
+
/** Atomically merge partial native Claude windows into the current fresh row. */
|
|
1622
|
+
export function mergeClaudeUsageCacheWindows(usageKey, snapshot, cachePath = getClaudeUsageCachePath()) {
|
|
1623
|
+
try {
|
|
1624
|
+
ensureLockTarget(cachePath, '{}');
|
|
1625
|
+
withFileLock(cachePath, () => {
|
|
1626
|
+
const cache = readClaudeUsageCacheFile(cachePath);
|
|
1627
|
+
const prior = cache[usageKey];
|
|
1628
|
+
const priorSnapshot = prior
|
|
1629
|
+
? deserializeClaudeUsageSnapshot(prior, snapshot.capturedAt ?? new Date())
|
|
1630
|
+
: null;
|
|
1631
|
+
const windows = new Map(priorSnapshot?.windows.map((window) => [window.key, window]) ?? []);
|
|
1632
|
+
for (const window of snapshot.windows)
|
|
1633
|
+
windows.set(window.key, window);
|
|
1634
|
+
cache[usageKey] = serializeClaudeUsageSnapshot({
|
|
1635
|
+
...snapshot,
|
|
1636
|
+
windows: [...windows.values()],
|
|
1637
|
+
plan: snapshot.plan ?? priorSnapshot?.plan ?? null,
|
|
1638
|
+
unavailable: carryForwardUnavailable(prior?.unavailable, snapshot.unavailable),
|
|
1639
|
+
});
|
|
1640
|
+
atomicWriteFileSync(cachePath, JSON.stringify(cache, null, 2), 'utf-8');
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
catch {
|
|
1644
|
+
/* best-effort cache write — lock busy or disk full */
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1607
1647
|
/** Read the entire usage cache file from disk. */
|
|
1608
1648
|
function readClaudeUsageCacheFile(cachePath) {
|
|
1609
1649
|
if (!fs.existsSync(cachePath)) {
|
|
@@ -1632,6 +1672,7 @@ function serializeClaudeUsageSnapshot(snapshot) {
|
|
|
1632
1672
|
return {
|
|
1633
1673
|
capturedAt: snapshot.capturedAt?.toISOString() || null,
|
|
1634
1674
|
plan: snapshot.plan ?? null,
|
|
1675
|
+
refreshHint: snapshot.refreshHint ?? null,
|
|
1635
1676
|
unavailable: snapshot.unavailable
|
|
1636
1677
|
? {
|
|
1637
1678
|
reason: snapshot.unavailable.reason,
|
|
@@ -1686,7 +1727,7 @@ function deserializeClaudeUsageSnapshot(snapshot, now) {
|
|
|
1686
1727
|
// and `deriveUsageStatusFromSnapshot` still returns null for zero windows, so
|
|
1687
1728
|
// it can never read as a 0% bar or an "available" badge (the RUSH-2858
|
|
1688
1729
|
// property that made expired windows drop in the first place).
|
|
1689
|
-
if (windows.length === 0 && !unavailable && !snapshot.plan) {
|
|
1730
|
+
if (windows.length === 0 && !unavailable && !snapshot.plan && !snapshot.refreshHint) {
|
|
1690
1731
|
return null;
|
|
1691
1732
|
}
|
|
1692
1733
|
return {
|
|
@@ -1695,6 +1736,7 @@ function deserializeClaudeUsageSnapshot(snapshot, now) {
|
|
|
1695
1736
|
capturedAt,
|
|
1696
1737
|
windows,
|
|
1697
1738
|
plan: snapshot.plan ?? null,
|
|
1739
|
+
refreshHint: snapshot.refreshHint ?? null,
|
|
1698
1740
|
unavailable,
|
|
1699
1741
|
};
|
|
1700
1742
|
}
|
|
@@ -1973,14 +2015,17 @@ function renderUsageBar(usedPercent) {
|
|
|
1973
2015
|
}
|
|
1974
2016
|
/** Render a compact usage bar for inline summaries. */
|
|
1975
2017
|
function renderCompactUsageBar(usedPercent) {
|
|
1976
|
-
return renderBar(usedPercent, COMPACT_BAR_LEN
|
|
1977
|
-
}
|
|
1978
|
-
/** Render a colored
|
|
1979
|
-
export function renderBar(usedPercent, length
|
|
1980
|
-
const
|
|
1981
|
-
const
|
|
2018
|
+
return renderBar(usedPercent, COMPACT_BAR_LEN);
|
|
2019
|
+
}
|
|
2020
|
+
/** Render a proportional colored progress bar at one-eighth-cell resolution. */
|
|
2021
|
+
export function renderBar(usedPercent, length) {
|
|
2022
|
+
const clamped = Math.max(0, Math.min(100, usedPercent));
|
|
2023
|
+
const eighths = Math.round((clamped / 100) * length * 8);
|
|
2024
|
+
const filled = Math.floor(eighths / 8);
|
|
2025
|
+
const partial = eighths % 8;
|
|
1982
2026
|
const color = getUsageColor(usedPercent);
|
|
1983
|
-
|
|
2027
|
+
const gauge = FULL.repeat(filled) + PARTIAL_BLOCKS[partial];
|
|
2028
|
+
return color(gauge) + chalk.dim(EMPTY.repeat(length - filled - (partial > 0 ? 1 : 0)));
|
|
1984
2029
|
}
|
|
1985
2030
|
/** Apply the appropriate color to a text string based on usage percentage. */
|
|
1986
2031
|
function colorUsage(text, usedPercent) {
|
|
@@ -2080,10 +2125,10 @@ async function getGrokUsageInfo(options) {
|
|
|
2080
2125
|
// (see readLatestGrokBilling).
|
|
2081
2126
|
const now = new Date();
|
|
2082
2127
|
const windows = match.windows.filter((window) => isCachedUsageWindowFresh(window, match.capturedAt, now));
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2128
|
+
const version = options?.cliVersion;
|
|
2129
|
+
const refreshHint = windows.length === 0
|
|
2130
|
+
? `run grok${version ? `@${version}` : ''} once to refresh usage`
|
|
2131
|
+
: null;
|
|
2087
2132
|
return {
|
|
2088
2133
|
snapshot: {
|
|
2089
2134
|
source: 'last_seen',
|
|
@@ -2091,6 +2136,7 @@ async function getGrokUsageInfo(options) {
|
|
|
2091
2136
|
capturedAt: match.capturedAt,
|
|
2092
2137
|
windows,
|
|
2093
2138
|
plan: match.subscriptionTier,
|
|
2139
|
+
refreshHint,
|
|
2094
2140
|
},
|
|
2095
2141
|
error: null,
|
|
2096
2142
|
};
|
|
@@ -2483,7 +2529,7 @@ export function normalizeCursorUsageSummary(data) {
|
|
|
2483
2529
|
function readCursorCredentials(base) {
|
|
2484
2530
|
try {
|
|
2485
2531
|
const cfgPath = path.join(base, '.cursor', 'cli-config.json');
|
|
2486
|
-
const authPath = path.join(base, '.
|
|
2532
|
+
const authPath = path.join(base, '.cursor', 'auth.json');
|
|
2487
2533
|
if (!fs.existsSync(cfgPath) || !fs.existsSync(authPath))
|
|
2488
2534
|
return null;
|
|
2489
2535
|
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
@@ -1504,12 +1504,9 @@ const CREDENTIAL_FILE_SEGMENTS = {
|
|
|
1504
1504
|
// Muse Code stores OAuth / API credentials at ~/.config/muse/auth.json
|
|
1505
1505
|
// (or META_API_KEY in the environment, which is not a file).
|
|
1506
1506
|
muse: [['.config', 'muse', 'auth.json']],
|
|
1507
|
-
//
|
|
1508
|
-
//
|
|
1509
|
-
|
|
1510
|
-
// version home with no token of its own is genuinely logged out once runs pin
|
|
1511
|
-
// XDG_CONFIG_HOME per home (see buildExecEnv).
|
|
1512
|
-
cursor: [['.config', 'cursor', 'auth.json']],
|
|
1507
|
+
// With AGENT_CLI_CREDENTIAL_STORE=file, Cursor stores both OAuth tokens at
|
|
1508
|
+
// ~/.cursor/auth.json. cli-config.json holds account metadata only.
|
|
1509
|
+
cursor: [['.cursor', 'auth.json']],
|
|
1513
1510
|
};
|
|
1514
1511
|
/** Whether an agent's credential file exists under a given home. */
|
|
1515
1512
|
function credentialFileExistsUnder(agentId, home) {
|
|
@@ -2112,7 +2109,7 @@ export async function getAccountInfo(agentId, home) {
|
|
|
2112
2109
|
case 'cursor': {
|
|
2113
2110
|
// Cursor CLI keeps account metadata in ~/.cursor/cli-config.json
|
|
2114
2111
|
// (authInfo: { email, userId, authId }) and its OAuth tokens SEPARATELY
|
|
2115
|
-
// in ~/.
|
|
2112
|
+
// in ~/.cursor/auth.json ({ accessToken, refreshToken }). Presence
|
|
2116
2113
|
// of an access token is the signed-in signal; email/ids come from
|
|
2117
2114
|
// cli-config. authId is the OAuth subject (e.g. "google-oauth2|<n>") — the
|
|
2118
2115
|
// same value the usage endpoint keys on (see getCursorUsageInfo).
|
|
@@ -2126,7 +2123,7 @@ export async function getAccountInfo(agentId, home) {
|
|
|
2126
2123
|
const accountId = normalizeIdentityPart(authInfo?.authId ?? authInfo?.userId);
|
|
2127
2124
|
if (!email && !accountId)
|
|
2128
2125
|
return { ...empty, lastActive };
|
|
2129
|
-
const authPath = resolveAccountCredentialPath(base, '.
|
|
2126
|
+
const authPath = resolveAccountCredentialPath(base, '.cursor', 'auth.json');
|
|
2130
2127
|
let hasToken = false;
|
|
2131
2128
|
if (authPath) {
|
|
2132
2129
|
try {
|
|
@@ -2135,8 +2132,13 @@ export async function getAccountInfo(agentId, home) {
|
|
|
2135
2132
|
}
|
|
2136
2133
|
catch { /* unreadable token file */ }
|
|
2137
2134
|
}
|
|
2135
|
+
// cli-config survives logout and may describe a different keychain
|
|
2136
|
+
// account. Without this version's file token, its identity is stale
|
|
2137
|
+
// metadata rather than a usable managed login.
|
|
2138
|
+
if (!hasToken)
|
|
2139
|
+
return { ...empty, lastActive };
|
|
2138
2140
|
const accountKey = buildIdentityKey(agentId, [['user', accountId]]);
|
|
2139
|
-
return { ...empty, email, accountId, accountKey, signedIn:
|
|
2141
|
+
return { ...empty, email, accountId, accountKey, signedIn: true, lastActive };
|
|
2140
2142
|
}
|
|
2141
2143
|
catch { }
|
|
2142
2144
|
return { ...empty, lastActive };
|