@phnx-labs/agents-cli 1.22.52 → 1.22.54
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 +336 -0
- package/README.md +42 -9
- package/dist/bootstrap.js +55 -154
- package/dist/cli/command-registry.d.ts +5 -0
- package/dist/cli/command-registry.js +8 -1
- package/dist/commands/accounts.js +220 -174
- package/dist/commands/apply.js +6 -3
- package/dist/commands/auth-mint.d.ts +8 -0
- package/dist/commands/auth-mint.js +96 -0
- package/dist/commands/auth.js +5 -1
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cost.js +8 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/doctor.js +6 -1
- package/dist/commands/exec.js +26 -17
- package/dist/commands/fleet-capture.js +7 -0
- package/dist/commands/focus.d.ts +1 -0
- package/dist/commands/focus.js +4 -2
- package/dist/commands/go.d.ts +5 -4
- package/dist/commands/go.js +8 -7
- package/dist/commands/insights.js +9 -0
- package/dist/commands/monitors.js +85 -30
- package/dist/commands/output.js +8 -2
- package/dist/commands/repo.js +18 -0
- package/dist/commands/secrets.js +33 -14
- package/dist/commands/sessions-inject.js +8 -3
- package/dist/commands/sessions-picker.js +2 -1
- package/dist/commands/sessions.d.ts +20 -12
- package/dist/commands/sessions.js +94 -40
- package/dist/commands/setup-accounts.d.ts +8 -0
- package/dist/commands/setup-accounts.js +47 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +11 -2
- package/dist/commands/share.d.ts +52 -3
- package/dist/commands/share.js +262 -18
- package/dist/commands/ssh.d.ts +7 -0
- package/dist/commands/ssh.js +53 -14
- package/dist/commands/status.js +14 -0
- package/dist/commands/sync.js +44 -0
- package/dist/commands/view.d.ts +3 -1
- package/dist/commands/view.js +5 -4
- package/dist/lib/account-registry.d.ts +15 -5
- package/dist/lib/account-registry.js +165 -53
- package/dist/lib/accounting/rotate.d.ts +20 -6
- package/dist/lib/accounting/rotate.js +38 -7
- package/dist/lib/accounting/usage.d.ts +37 -1
- package/dist/lib/accounting/usage.js +71 -6
- package/dist/lib/agent-spec/agents.d.ts +5 -2
- package/dist/lib/agent-spec/agents.js +25 -7
- package/dist/lib/analytics/mix-commands.js +12 -6
- package/dist/lib/answer-router.js +2 -1
- package/dist/lib/auth-mint.d.ts +150 -0
- package/dist/lib/auth-mint.js +434 -0
- package/dist/lib/browser/profiles.d.ts +18 -0
- package/dist/lib/browser/profiles.js +26 -1
- package/dist/lib/browser/registry.d.ts +44 -14
- package/dist/lib/browser/registry.js +141 -45
- package/dist/lib/browser/remote-control.d.ts +9 -7
- package/dist/lib/browser/remote-control.js +9 -7
- package/dist/lib/claude-account-token.d.ts +10 -0
- package/dist/lib/claude-account-token.js +14 -4
- package/dist/lib/config-drift.d.ts +37 -0
- package/dist/lib/config-drift.js +72 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +19 -0
- package/dist/lib/daemon/auth-sync-service.js +34 -0
- package/dist/lib/daemon/daemon.js +30 -4
- package/dist/lib/daemon/runner.js +10 -2
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.d.ts +3 -3
- package/dist/lib/device-config.js +8 -7
- package/dist/lib/devices/config-migration.js +147 -1
- package/dist/lib/devices/connect.d.ts +26 -0
- package/dist/lib/devices/connect.js +48 -1
- package/dist/lib/devices/device-docs.d.ts +35 -0
- package/dist/lib/devices/device-docs.js +163 -0
- package/dist/lib/devices/discovery-policy.d.ts +14 -2
- package/dist/lib/devices/discovery-policy.js +31 -21
- package/dist/lib/devices/doctor-findings.d.ts +5 -1
- package/dist/lib/devices/doctor-findings.js +19 -1
- package/dist/lib/devices/registry.d.ts +11 -5
- package/dist/lib/devices/registry.js +46 -18
- package/dist/lib/exec.d.ts +88 -30
- package/dist/lib/exec.js +138 -34
- package/dist/lib/feed/feed.d.ts +10 -2
- package/dist/lib/feed/feed.js +35 -2
- package/dist/lib/feed-broadcast.js +1 -1
- package/dist/lib/fleet/apply.d.ts +11 -0
- package/dist/lib/fleet/apply.js +23 -3
- package/dist/lib/fleet/auth-sync.js +5 -3
- package/dist/lib/help.d.ts +9 -0
- package/dist/lib/help.js +29 -1
- package/dist/lib/hosts/dispatch.d.ts +4 -3
- package/dist/lib/hosts/dispatch.js +12 -8
- package/dist/lib/hosts/passthrough.d.ts +1 -10
- package/dist/lib/hosts/passthrough.js +1 -13
- package/dist/lib/hosts/providers/local.d.ts +9 -3
- package/dist/lib/hosts/providers/local.js +23 -12
- package/dist/lib/hosts/reconnect.d.ts +7 -4
- package/dist/lib/hosts/reconnect.js +29 -25
- package/dist/lib/hosts/registry.js +4 -1
- package/dist/lib/hosts/remote-os.js +3 -1
- package/dist/lib/installations/versions.js +9 -1
- package/dist/lib/linux-userns.d.ts +58 -0
- package/dist/lib/linux-userns.js +116 -0
- package/dist/lib/memory.d.ts +26 -0
- package/dist/lib/memory.js +80 -1
- package/dist/lib/monitors/config.d.ts +11 -0
- package/dist/lib/monitors/config.js +8 -0
- package/dist/lib/monitors/engine.js +8 -1
- package/dist/lib/monitors/state.d.ts +37 -1
- package/dist/lib/monitors/state.js +79 -4
- package/dist/lib/permissions-registry.d.ts +2 -0
- package/dist/lib/permissions-registry.js +116 -14
- package/dist/lib/permissions.d.ts +5 -3
- package/dist/lib/permissions.js +25 -27
- package/dist/lib/profiles.d.ts +8 -7
- package/dist/lib/profiles.js +12 -0
- package/dist/lib/project-key.d.ts +9 -0
- package/dist/lib/project-key.js +11 -0
- package/dist/lib/secrets/bundles.d.ts +35 -0
- package/dist/lib/secrets/bundles.js +78 -1
- package/dist/lib/secrets/push.d.ts +3 -8
- package/dist/lib/secrets/push.js +18 -14
- package/dist/lib/secrets/remote.d.ts +9 -18
- package/dist/lib/secrets/remote.js +11 -26
- package/dist/lib/secrets/reserved-sync.d.ts +65 -0
- package/dist/lib/secrets/reserved-sync.js +129 -0
- package/dist/lib/self-heal/checks/hook-manifest.d.ts +2 -0
- package/dist/lib/self-heal/checks/hook-manifest.js +56 -0
- package/dist/lib/self-heal/registry.js +4 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/session/active.d.ts +10 -1
- package/dist/lib/session/active.js +8 -5
- package/dist/lib/session/actor-sidecar.d.ts +7 -0
- package/dist/lib/session/actor-sidecar.js +2 -0
- package/dist/lib/session/db.d.ts +39 -4
- package/dist/lib/session/db.js +168 -31
- package/dist/lib/session/discover.d.ts +32 -4
- package/dist/lib/session/discover.js +126 -37
- package/dist/lib/session/insights.d.ts +14 -0
- package/dist/lib/session/insights.js +25 -2
- package/dist/lib/session/linear.js +1 -1
- package/dist/lib/session/live-metadata.js +1 -0
- package/dist/lib/session/pid-registry.d.ts +7 -0
- package/dist/lib/session/prompt.d.ts +15 -0
- package/dist/lib/session/prompt.js +21 -0
- package/dist/lib/session/shell-programs.d.ts +17 -0
- package/dist/lib/session/shell-programs.js +21 -0
- package/dist/lib/session/state.js +2 -1
- package/dist/lib/session/stream-render.js +2 -1
- package/dist/lib/session/tool-calls.js +2 -5
- package/dist/lib/session/trajectory-html.js +2 -1
- package/dist/lib/session/trajectory.js +3 -12
- package/dist/lib/session/types.d.ts +25 -0
- package/dist/lib/session/types.js +10 -0
- package/dist/lib/share/publish.d.ts +53 -5
- package/dist/lib/share/publish.js +99 -17
- package/dist/lib/share/worker-template.js +594 -64
- package/dist/lib/startup/root-command.js +2 -1
- package/dist/lib/state.d.ts +24 -0
- package/dist/lib/state.js +318 -54
- package/dist/lib/sync-status.d.ts +4 -0
- package/dist/lib/sync-status.js +3 -0
- package/dist/lib/terminal/resolve.d.ts +7 -0
- package/dist/lib/terminal/resolve.js +41 -2
- package/dist/lib/traces/classify.js +24 -19
- package/dist/lib/traces/insights.d.ts +67 -0
- package/dist/lib/traces/insights.js +178 -0
- package/dist/lib/traces/phenotype.d.ts +67 -0
- package/dist/lib/traces/phenotype.js +437 -0
- package/dist/lib/traces/segments.d.ts +133 -0
- package/dist/lib/traces/segments.js +301 -0
- package/dist/lib/traces/sync.d.ts +33 -0
- package/dist/lib/traces/sync.js +11 -2
- package/dist/lib/types.d.ts +47 -1
- package/dist/lib/usage-refresh.js +2 -1
- package/dist/lib/view-types.d.ts +2 -0
- package/dist/lib/watchdog/runner.js +18 -4
- package/package.json +2 -1
|
@@ -22,6 +22,12 @@ import type { AgentId } from '../types.js';
|
|
|
22
22
|
*/
|
|
23
23
|
export declare function usageNoCredentialError(agent: string): string;
|
|
24
24
|
export declare function usageExpiredCredentialError(agent: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Kimi-specific expired-credential wording. A normal Kimi launch refreshes its
|
|
27
|
+
* own OAuth access token, so the recovery action for an expired Kimi credential
|
|
28
|
+
* is to run Kimi once — not to re-auth through agents-cli (RUSH-3198).
|
|
29
|
+
*/
|
|
30
|
+
export declare function usageExpiredKimiCredentialError(): string;
|
|
25
31
|
export declare function usageRejectedError(agent: string, status: number): string;
|
|
26
32
|
/**
|
|
27
33
|
* Canonical phrase for the Anthropic setup-token scope gap (RUSH-2392).
|
|
@@ -39,6 +45,26 @@ export declare const USAGE_HEADLESS_SCOPE_MARKER = "usage unavailable (headless)
|
|
|
39
45
|
export declare function usageHeadlessScopeError(agent?: string): string;
|
|
40
46
|
/** True when an error string is the setup-token scope gap (RUSH-2392). */
|
|
41
47
|
export declare function isUsageHeadlessScopeError(error: string | null | undefined): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Canonical phrase for a Claude account the usage reader holds no usable
|
|
50
|
+
* credential for. Distinct from {@link USAGE_HEADLESS_SCOPE_MARKER}, which
|
|
51
|
+
* means a setup-token WAS read and the endpoint refused its scope.
|
|
52
|
+
*/
|
|
53
|
+
export declare const USAGE_NO_USAGE_CREDENTIAL_MARKER = "usage unavailable (no usage credential)";
|
|
54
|
+
/**
|
|
55
|
+
* Claude's own no-credential message. The shared
|
|
56
|
+
* {@link usageNoCredentialError} offers "sign in" as the remedy, which holds
|
|
57
|
+
* for Kimi/Droid/Cursor — their CLIs rotate a readable token on the next launch
|
|
58
|
+
* — and is false for Claude: the usage read deliberately never touches the
|
|
59
|
+
* interactive login (RUSH-1822), so an account that IS signed in reads as
|
|
60
|
+
* unreadable here and signing in again changes nothing. Naming only the second
|
|
61
|
+
* remedy would send the operator to `claude setup-token`, whose token then hits
|
|
62
|
+
* the `user:profile` scope gap (RUSH-2392) — the loop reported in #2987 — so
|
|
63
|
+
* this message states both constraints and that the account still runs.
|
|
64
|
+
*/
|
|
65
|
+
export declare function usageNoClaudeUsageCredentialError(): string;
|
|
66
|
+
/** True when an error string is the Claude no-usage-credential state (#2987). */
|
|
67
|
+
export declare function isUsageNoUsageCredentialError(error: string | null | undefined): boolean;
|
|
42
68
|
/**
|
|
43
69
|
* Detect Anthropic's usage-endpoint scope denial: HTTP 403 whose body names
|
|
44
70
|
* `user:profile` (or "scope requirement"). A bare 403 without that body stays
|
|
@@ -72,6 +98,16 @@ export declare function usageUnreachableError(agent: string, cause?: unknown): s
|
|
|
72
98
|
export declare const USAGE_NO_RECENT_USAGE_MARKER = "no usage recorded yet";
|
|
73
99
|
export declare const USAGE_BENIGN_STATE: unique symbol;
|
|
74
100
|
export type UsageBenignState = 'no-recent-usage';
|
|
101
|
+
/**
|
|
102
|
+
* Sentinel `UsageInfo.error` for a read-only lookup whose cache held nothing
|
|
103
|
+
* (`getUsageInfoForIdentity`). No request was made and nothing failed — the
|
|
104
|
+
* daemon simply has not collected this account yet. It was an unclassified
|
|
105
|
+
* literal, so `classifyUsageErrorKind` fell through to `'rejected'` and
|
|
106
|
+
* `agents view` printed the generic "usage unavailable" for a cold cache,
|
|
107
|
+
* which reads as a failure the operator should chase (#2987). The string value
|
|
108
|
+
* is unchanged; callers that already compare against `'stale'` keep working.
|
|
109
|
+
*/
|
|
110
|
+
export declare const USAGE_NOT_COLLECTED_MARKER = "stale";
|
|
75
111
|
/**
|
|
76
112
|
* Shared error-classification + 429 backoff for a networked usage fetch whose
|
|
77
113
|
* only signal is an HTTP status (or none at all, on a network failure) —
|
|
@@ -91,7 +127,7 @@ export declare function classifyUsageFetchFailure(agent: string, agentId: 'antig
|
|
|
91
127
|
* distinct causes (RUSH-3040). Matched against the canonical strings this file
|
|
92
128
|
* constructs — never re-derive these prefixes at a call site.
|
|
93
129
|
*/
|
|
94
|
-
export type UsageErrorKind = 'no-credential' | 'expired-credential' | 'rate-limited' | 'rejected' | 'headless-scope' | 'unreachable';
|
|
130
|
+
export type UsageErrorKind = 'no-credential' | 'no-usage-credential' | 'expired-credential' | 'rate-limited' | 'rejected' | 'headless-scope' | 'unreachable' | 'not-collected';
|
|
95
131
|
/** Classify a `UsageInfo.error` string into its {@link UsageErrorKind}, or null when there is no error. */
|
|
96
132
|
export declare function classifyUsageErrorKind(error: string | null | undefined): UsageErrorKind | null;
|
|
97
133
|
/**
|
|
@@ -59,6 +59,14 @@ export function usageNoCredentialError(agent) {
|
|
|
59
59
|
export function usageExpiredCredentialError(agent) {
|
|
60
60
|
return `${agent} credential expired — re-auth this account (a usage read never refreshes it).`;
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Kimi-specific expired-credential wording. A normal Kimi launch refreshes its
|
|
64
|
+
* own OAuth access token, so the recovery action for an expired Kimi credential
|
|
65
|
+
* is to run Kimi once — not to re-auth through agents-cli (RUSH-3198).
|
|
66
|
+
*/
|
|
67
|
+
export function usageExpiredKimiCredentialError() {
|
|
68
|
+
return `Kimi credential expired — run Kimi once to refresh it (a usage read never refreshes it).`;
|
|
69
|
+
}
|
|
62
70
|
export function usageRejectedError(agent, status) {
|
|
63
71
|
return status === 429
|
|
64
72
|
? `${agent} is rate-limiting the usage endpoint for this machine (HTTP 429).`
|
|
@@ -84,6 +92,31 @@ export function usageHeadlessScopeError(agent = 'Claude') {
|
|
|
84
92
|
export function isUsageHeadlessScopeError(error) {
|
|
85
93
|
return typeof error === 'string' && error.includes(USAGE_HEADLESS_SCOPE_MARKER);
|
|
86
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Canonical phrase for a Claude account the usage reader holds no usable
|
|
97
|
+
* credential for. Distinct from {@link USAGE_HEADLESS_SCOPE_MARKER}, which
|
|
98
|
+
* means a setup-token WAS read and the endpoint refused its scope.
|
|
99
|
+
*/
|
|
100
|
+
export const USAGE_NO_USAGE_CREDENTIAL_MARKER = 'usage unavailable (no usage credential)';
|
|
101
|
+
/**
|
|
102
|
+
* Claude's own no-credential message. The shared
|
|
103
|
+
* {@link usageNoCredentialError} offers "sign in" as the remedy, which holds
|
|
104
|
+
* for Kimi/Droid/Cursor — their CLIs rotate a readable token on the next launch
|
|
105
|
+
* — and is false for Claude: the usage read deliberately never touches the
|
|
106
|
+
* interactive login (RUSH-1822), so an account that IS signed in reads as
|
|
107
|
+
* unreadable here and signing in again changes nothing. Naming only the second
|
|
108
|
+
* remedy would send the operator to `claude setup-token`, whose token then hits
|
|
109
|
+
* the `user:profile` scope gap (RUSH-2392) — the loop reported in #2987 — so
|
|
110
|
+
* this message states both constraints and that the account still runs.
|
|
111
|
+
*/
|
|
112
|
+
export function usageNoClaudeUsageCredentialError() {
|
|
113
|
+
return (`Claude ${USAGE_NO_USAGE_CREDENTIAL_MARKER} — a usage read never uses your login ` +
|
|
114
|
+
'(RUSH-1822); a setup-token cannot read usage (RUSH-2392). The account still runs.');
|
|
115
|
+
}
|
|
116
|
+
/** True when an error string is the Claude no-usage-credential state (#2987). */
|
|
117
|
+
export function isUsageNoUsageCredentialError(error) {
|
|
118
|
+
return typeof error === 'string' && error.includes(USAGE_NO_USAGE_CREDENTIAL_MARKER);
|
|
119
|
+
}
|
|
87
120
|
/**
|
|
88
121
|
* Detect Anthropic's usage-endpoint scope denial: HTTP 403 whose body names
|
|
89
122
|
* `user:profile` (or "scope requirement"). A bare 403 without that body stays
|
|
@@ -130,6 +163,16 @@ export function usageUnreachableError(agent, cause) {
|
|
|
130
163
|
*/
|
|
131
164
|
export const USAGE_NO_RECENT_USAGE_MARKER = 'no usage recorded yet';
|
|
132
165
|
export const USAGE_BENIGN_STATE = Symbol('usageBenignState');
|
|
166
|
+
/**
|
|
167
|
+
* Sentinel `UsageInfo.error` for a read-only lookup whose cache held nothing
|
|
168
|
+
* (`getUsageInfoForIdentity`). No request was made and nothing failed — the
|
|
169
|
+
* daemon simply has not collected this account yet. It was an unclassified
|
|
170
|
+
* literal, so `classifyUsageErrorKind` fell through to `'rejected'` and
|
|
171
|
+
* `agents view` printed the generic "usage unavailable" for a cold cache,
|
|
172
|
+
* which reads as a failure the operator should chase (#2987). The string value
|
|
173
|
+
* is unchanged; callers that already compare against `'stale'` keep working.
|
|
174
|
+
*/
|
|
175
|
+
export const USAGE_NOT_COLLECTED_MARKER = 'stale';
|
|
133
176
|
/**
|
|
134
177
|
* Shared error-classification + 429 backoff for a networked usage fetch whose
|
|
135
178
|
* only signal is an HTTP status (or none at all, on a network failure) —
|
|
@@ -155,8 +198,12 @@ export function classifyUsageFetchFailure(agent, agentId, status, retryAfterHead
|
|
|
155
198
|
export function classifyUsageErrorKind(error) {
|
|
156
199
|
if (!error)
|
|
157
200
|
return null;
|
|
201
|
+
if (error === USAGE_NOT_COLLECTED_MARKER)
|
|
202
|
+
return 'not-collected';
|
|
158
203
|
if (isUsageHeadlessScopeError(error))
|
|
159
204
|
return 'headless-scope';
|
|
205
|
+
if (isUsageNoUsageCredentialError(error))
|
|
206
|
+
return 'no-usage-credential';
|
|
160
207
|
if (error.startsWith('No readable '))
|
|
161
208
|
return 'no-credential';
|
|
162
209
|
if (error.includes('credential expired'))
|
|
@@ -342,7 +389,7 @@ export async function getUsageInfoForIdentity(input, opts) {
|
|
|
342
389
|
// cache file holds every account without collision.
|
|
343
390
|
if (!usageKey) {
|
|
344
391
|
if (readOnly)
|
|
345
|
-
return { snapshot: null, error:
|
|
392
|
+
return { snapshot: null, error: USAGE_NOT_COLLECTED_MARKER };
|
|
346
393
|
return getUsageInfo(input.agentId, {
|
|
347
394
|
home: input.home,
|
|
348
395
|
cliVersion: input.cliVersion,
|
|
@@ -361,11 +408,12 @@ export async function getUsageInfoForIdentity(input, opts) {
|
|
|
361
408
|
// it. A stale-or-absent snapshot is handled downstream by the router's own
|
|
362
409
|
// freshness guard (`isUsageVerified` in rotate.ts), which routes around a
|
|
363
410
|
// number it can't confirm rather than trusting an old one — so returning a
|
|
364
|
-
// stale snapshot here is safe, and an absent one reports
|
|
411
|
+
// stale snapshot here is safe, and an absent one reports
|
|
412
|
+
// {@link USAGE_NOT_COLLECTED_MARKER}.
|
|
365
413
|
if (readOnly) {
|
|
366
414
|
if (cached)
|
|
367
415
|
return { snapshot: cached, error: null };
|
|
368
|
-
return { snapshot: null, error:
|
|
416
|
+
return { snapshot: null, error: USAGE_NOT_COLLECTED_MARKER };
|
|
369
417
|
}
|
|
370
418
|
// Explicit refresh: block on the shared device collector.
|
|
371
419
|
return fetchLiveUsageDeduped(input, usageKey, cached, opts?.fileOnly === true);
|
|
@@ -460,15 +508,28 @@ function formatUsageErrorKindLabel(kind, detail) {
|
|
|
460
508
|
switch (kind) {
|
|
461
509
|
case 'no-credential':
|
|
462
510
|
return 'sign in / provision token';
|
|
511
|
+
// Both of these are permanent for the account as configured, and both used
|
|
512
|
+
// to render as the generic bucket — which reads as a transient failure and
|
|
513
|
+
// sends operators back to `claude setup-token` for a remedy that cannot
|
|
514
|
+
// work (#2987). Name the state instead.
|
|
515
|
+
case 'no-usage-credential':
|
|
516
|
+
return USAGE_NO_USAGE_CREDENTIAL_MARKER;
|
|
517
|
+
case 'headless-scope':
|
|
518
|
+
return USAGE_HEADLESS_SCOPE_MARKER;
|
|
463
519
|
case 'expired-credential':
|
|
520
|
+
// Kimi refreshes its own credential on a normal launch; the recovery hint
|
|
521
|
+
// is embedded in the error string so the label matches the exact action.
|
|
522
|
+
if (detail?.includes('run Kimi once'))
|
|
523
|
+
return 'run Kimi once';
|
|
464
524
|
return 're-auth for usage';
|
|
525
|
+
case 'not-collected':
|
|
526
|
+
return 'usage pending';
|
|
465
527
|
case 'rate-limited': {
|
|
466
528
|
const retryHint = detail?.match(/not retrying for (.+)\.$/)?.[1] ?? null;
|
|
467
529
|
return retryHint ? `rate-limited (retry ~${retryHint})` : 'rate-limited';
|
|
468
530
|
}
|
|
469
531
|
case 'rejected':
|
|
470
532
|
case 'unreachable':
|
|
471
|
-
case 'headless-scope':
|
|
472
533
|
case null:
|
|
473
534
|
case undefined:
|
|
474
535
|
default:
|
|
@@ -766,7 +827,11 @@ async function getClaudeUsageInfo(options) {
|
|
|
766
827
|
fileOnly: options?.fileOnly === true,
|
|
767
828
|
});
|
|
768
829
|
if (!oauth?.accessToken) {
|
|
769
|
-
|
|
830
|
+
// NOT the shared no-credential message: "sign in" is not a remedy here.
|
|
831
|
+
// The account this reads for is usually signed in already — the reader is
|
|
832
|
+
// forbidden from touching that login (RUSH-1822) — so the shared wording
|
|
833
|
+
// asked the operator to redo the one thing they had already done (#2987).
|
|
834
|
+
return { snapshot: null, error: usageNoClaudeUsageCredentialError() };
|
|
770
835
|
}
|
|
771
836
|
const requestedOrgId = normalizeString(options?.organizationId);
|
|
772
837
|
const liveOrgId = normalizeString(oauth.organizationUuid);
|
|
@@ -887,7 +952,7 @@ async function getKimiUsageInfo(options) {
|
|
|
887
952
|
}
|
|
888
953
|
const expiresAt = typeof cred?.expires_at === 'number' ? cred.expires_at : null;
|
|
889
954
|
if (expiresAt !== null && Date.now() / 1000 >= expiresAt) {
|
|
890
|
-
return { snapshot: null, error:
|
|
955
|
+
return { snapshot: null, error: usageExpiredKimiCredentialError() };
|
|
891
956
|
}
|
|
892
957
|
// Honour a live Retry-After rather than re-arming the penalty (see
|
|
893
958
|
// usage-backoff.ts). No request at all while the window is open.
|
|
@@ -376,8 +376,11 @@ export declare function resolveOpenCodeAccountId(base: string): string | undefin
|
|
|
376
376
|
* this file is not authoritative, and probing the Keychain would raise an
|
|
377
377
|
* authorization sheet per installed version on every `agents run` — the reason
|
|
378
378
|
* rotation stopped calling `isClaudeAuthValid` at all. Off macOS the file IS the
|
|
379
|
-
* only store, so a token-less file is proof of signed-out
|
|
380
|
-
*
|
|
379
|
+
* only store, so a missing or token-less file is proof of signed-out — unless a
|
|
380
|
+
* Linux setup-token (`.claude/.oauth_token`) is present, which the shim exports
|
|
381
|
+
* as `CLAUDE_CODE_OAUTH_TOKEN` and can authenticate the run without
|
|
382
|
+
* `.credentials.json`. `platform` is a parameter so both branches are testable
|
|
383
|
+
* on any host.
|
|
381
384
|
*
|
|
382
385
|
* Sync, no Keychain, no network — safe on the `agents run` hot path.
|
|
383
386
|
*/
|
|
@@ -1897,14 +1897,29 @@ function museAuthEmail(value, depth = 0) {
|
|
|
1897
1897
|
* this file is not authoritative, and probing the Keychain would raise an
|
|
1898
1898
|
* authorization sheet per installed version on every `agents run` — the reason
|
|
1899
1899
|
* rotation stopped calling `isClaudeAuthValid` at all. Off macOS the file IS the
|
|
1900
|
-
* only store, so a token-less file is proof of signed-out
|
|
1901
|
-
*
|
|
1900
|
+
* only store, so a missing or token-less file is proof of signed-out — unless a
|
|
1901
|
+
* Linux setup-token (`.claude/.oauth_token`) is present, which the shim exports
|
|
1902
|
+
* as `CLAUDE_CODE_OAUTH_TOKEN` and can authenticate the run without
|
|
1903
|
+
* `.credentials.json`. `platform` is a parameter so both branches are testable
|
|
1904
|
+
* on any host.
|
|
1902
1905
|
*
|
|
1903
1906
|
* Sync, no Keychain, no network — safe on the `agents run` hot path.
|
|
1904
1907
|
*/
|
|
1905
1908
|
export function isClaudeCredentialFileBlank(base, platform = process.platform) {
|
|
1906
1909
|
if (platform === 'darwin')
|
|
1907
1910
|
return false;
|
|
1911
|
+
// A per-version setup-token is a real credential on Linux even when
|
|
1912
|
+
// `.credentials.json` was never written (the shim's `$CLAUDE_CONFIG_DIR/.oauth_token`
|
|
1913
|
+
// fallback). Treat it as signed-in so rotation does not skip a worker that
|
|
1914
|
+
// authenticates from an attached setup-token.
|
|
1915
|
+
try {
|
|
1916
|
+
const token = fs.readFileSync(path.join(base, '.claude', '.oauth_token'), 'utf-8').trim();
|
|
1917
|
+
if (token.length > 0)
|
|
1918
|
+
return false;
|
|
1919
|
+
}
|
|
1920
|
+
catch {
|
|
1921
|
+
/* absent — fall through to the credentials.json floor */
|
|
1922
|
+
}
|
|
1908
1923
|
try {
|
|
1909
1924
|
const raw = fs.readFileSync(path.join(base, '.claude', '.credentials.json'), 'utf-8');
|
|
1910
1925
|
const oauth = JSON.parse(raw).claudeAiOauth;
|
|
@@ -1913,11 +1928,14 @@ export function isClaudeCredentialFileBlank(base, platform = process.platform) {
|
|
|
1913
1928
|
const nonEmpty = (v) => typeof v === 'string' && v.trim().length > 0;
|
|
1914
1929
|
return !nonEmpty(oauth.accessToken) && !nonEmpty(oauth.refreshToken);
|
|
1915
1930
|
}
|
|
1916
|
-
catch {
|
|
1917
|
-
//
|
|
1918
|
-
//
|
|
1919
|
-
//
|
|
1920
|
-
//
|
|
1931
|
+
catch (err) {
|
|
1932
|
+
// Off macOS the file IS the store. Missing it means this home cannot
|
|
1933
|
+
// authenticate (a newly installed default with leftover `.claude.json`
|
|
1934
|
+
// oauthAccount is the PHNX-2685 false-healthy case). A corrupt file is
|
|
1935
|
+
// not positive evidence of a blank credential — leave the existing
|
|
1936
|
+
// signal alone rather than declaring a working install signed out.
|
|
1937
|
+
if (err.code === 'ENOENT')
|
|
1938
|
+
return true;
|
|
1921
1939
|
return false;
|
|
1922
1940
|
}
|
|
1923
1941
|
}
|
|
@@ -73,7 +73,10 @@ export function registerMixCommands(parent) {
|
|
|
73
73
|
.option('--days <n>', 'Days of history to include', '7')
|
|
74
74
|
.option('--json', 'Emit JSON instead of tables')
|
|
75
75
|
.action(function summary() {
|
|
76
|
-
|
|
76
|
+
// optsWithGlobals(): --json collides by name with the `insights` parent, so
|
|
77
|
+
// commander binds it to the parent and this.opts() never sees it. Merging
|
|
78
|
+
// ancestor opts is what the per-recipe leaves below already do.
|
|
79
|
+
const o = this.optsWithGlobals();
|
|
77
80
|
renderMixDashboard(parseMixDays(o.days), Boolean(o.json), banner);
|
|
78
81
|
});
|
|
79
82
|
setHelpSections(mix, {
|
|
@@ -104,7 +107,8 @@ export function registerMixCommands(parent) {
|
|
|
104
107
|
parent.command('recipes')
|
|
105
108
|
.description('List baked mix-recipe ids')
|
|
106
109
|
.option('--json', 'Emit JSON')
|
|
107
|
-
.action((
|
|
110
|
+
.action(function recipes() {
|
|
111
|
+
const o = this.optsWithGlobals();
|
|
108
112
|
const list = listRecipes();
|
|
109
113
|
if (o.json) {
|
|
110
114
|
console.log(JSON.stringify(list, null, 2));
|
|
@@ -122,7 +126,8 @@ export function registerMixCommands(parent) {
|
|
|
122
126
|
.option('--days <n>', 'Days of history', '7')
|
|
123
127
|
.option('--limit <n>', 'Max rows', '40')
|
|
124
128
|
.option('--json', 'Emit JSON')
|
|
125
|
-
.action((
|
|
129
|
+
.action(function query() {
|
|
130
|
+
const o = this.optsWithGlobals();
|
|
126
131
|
const win = analyticsWindow(parseMixDays(o.days));
|
|
127
132
|
const kind = o.kind && USAGE_KINDS.includes(o.kind)
|
|
128
133
|
? o.kind
|
|
@@ -165,8 +170,9 @@ export function registerMixCommands(parent) {
|
|
|
165
170
|
.option('--days <n>', 'Days of history', '7')
|
|
166
171
|
.option('--json', 'Emit JSON')
|
|
167
172
|
.action(function recipeAction() {
|
|
168
|
-
|
|
169
|
-
|
|
173
|
+
// optsWithGlobals() merges the `insights` parent opts, so the name-colliding
|
|
174
|
+
// --json/--since reach this leaf (see the sibling commands above).
|
|
175
|
+
const o = this.optsWithGlobals();
|
|
170
176
|
const win = analyticsWindow(parseMixDays(o.days));
|
|
171
177
|
const section = runRecipe(id, win);
|
|
172
178
|
if (o.json) {
|
|
@@ -187,7 +193,7 @@ export function registerMixCommands(parent) {
|
|
|
187
193
|
.option('--days <n>', 'Days of history to include', '7')
|
|
188
194
|
.option('--json', 'Emit JSON instead of tables')
|
|
189
195
|
.action(function summary() {
|
|
190
|
-
const o = this.
|
|
196
|
+
const o = this.optsWithGlobals();
|
|
191
197
|
renderMixDashboard(parseMixDays(o.days), Boolean(o.json), banner);
|
|
192
198
|
});
|
|
193
199
|
setHelpSections(trends, {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { addressabilityRecoveryHint } from './terminal/resolve.js';
|
|
1
2
|
import { injectTargetFromReplyRail } from './session/inject.js';
|
|
2
3
|
/**
|
|
3
4
|
* Match free-text answer against question options.
|
|
@@ -123,7 +124,7 @@ export function resolveAnswerRoute(input) {
|
|
|
123
124
|
return {
|
|
124
125
|
kind: 'refuse',
|
|
125
126
|
reason: 'Agent is parked on a question but has no addressable terminal (no tmux/iterm/pty rail). ' +
|
|
126
|
-
|
|
127
|
+
addressabilityRecoveryHint(session),
|
|
127
128
|
};
|
|
128
129
|
}
|
|
129
130
|
// Open block but agent is still looping between tool calls — mailbox is correct.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-class setup-token mint + seed (PHNX-2364).
|
|
3
|
+
*
|
|
4
|
+
* Closes the mint-auth manual recipe: drive `claude setup-token` through the
|
|
5
|
+
* same injectable PTY driver `agents fleet login` uses, capture a well-formed
|
|
6
|
+
* `sk-ant-oat01-…` token (the #1767 ANSI-banner guard), and seed BOTH:
|
|
7
|
+
*
|
|
8
|
+
* 1. a named provider account (`agents accounts add` shape, policy never)
|
|
9
|
+
* 2. the reserved FILE-BASED `auth` bundle keyed per-account email, which
|
|
10
|
+
* usage/probe reads (`resolveClaudeSetupToken`)
|
|
11
|
+
*
|
|
12
|
+
* Native rotating OAuth is never copied. Only this non-rotating class is
|
|
13
|
+
* stored and optionally synced. Interactive mint is Claude-only; every other
|
|
14
|
+
* harness fails loud with the command that actually provisions it.
|
|
15
|
+
*/
|
|
16
|
+
import type { AgentId } from './types.js';
|
|
17
|
+
import { type CredentialAccount } from './account-registry.js';
|
|
18
|
+
import { type DriveOptions, type PtyDriver } from './fleet/remote-login.js';
|
|
19
|
+
/** Well-formed Claude setup-token as it appears inside a TTY blob. */
|
|
20
|
+
export declare const CLAUDE_SETUP_TOKEN_CAPTURE_RE: RegExp;
|
|
21
|
+
export interface MintFlow {
|
|
22
|
+
harness: AgentId;
|
|
23
|
+
provider: string;
|
|
24
|
+
auth: 'setup-token';
|
|
25
|
+
/** Interactive mint argv after HOME=… <bin>. Null when stdin-seed only. */
|
|
26
|
+
mintArgs: string[] | null;
|
|
27
|
+
verificationUrlRegex: RegExp;
|
|
28
|
+
tokenCapture: RegExp;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Harnesses that expose an interactive setup-token mint. Native device-code
|
|
32
|
+
* login (codex/droid/kimi/grok) stays on `agents fleet login`; API keys stay
|
|
33
|
+
* on `agents accounts add`. Adding a harness here without a real mint command
|
|
34
|
+
* is a lying table — do not.
|
|
35
|
+
*/
|
|
36
|
+
export declare const MINT_FLOWS: Record<string, MintFlow>;
|
|
37
|
+
export declare function listMintableHarnesses(): AgentId[];
|
|
38
|
+
export declare function getMintFlow(harnessRaw: string): MintFlow;
|
|
39
|
+
export declare function unmintableMessage(harness: string): string;
|
|
40
|
+
/** Strip CSI / Fe ANSI so a #1767 TTY blob can be scanned for a real token. */
|
|
41
|
+
export declare function stripAnsi(text: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Pull a single well-formed Claude setup-token out of a (possibly ANSI-wrapped)
|
|
44
|
+
* screen. Returns null when none is present. Two distinct tokens fail loud —
|
|
45
|
+
* guessing which one to seed is how a banner fragment becomes an auth header.
|
|
46
|
+
*/
|
|
47
|
+
export declare function extractClaudeSetupToken(screen: string): string | null;
|
|
48
|
+
/** First https URL on the screen, trailing punctuation stripped. */
|
|
49
|
+
export declare function extractMintUrl(screen: string, flow: MintFlow): string | undefined;
|
|
50
|
+
export declare function isEmail(value: string): boolean;
|
|
51
|
+
/** Account-name slug of an email (`ada@example.com` → `ada-at-example.com`). */
|
|
52
|
+
export declare function accountNameFromEmail(email: string): string;
|
|
53
|
+
export declare function assertValidSetupToken(token: string): string;
|
|
54
|
+
export interface ResolveMintIdentityInput {
|
|
55
|
+
account?: string;
|
|
56
|
+
email?: string;
|
|
57
|
+
home?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface ResolvedMintIdentity {
|
|
60
|
+
accountName: string;
|
|
61
|
+
email: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the named account + the email that keys the reserved `auth` bundle.
|
|
65
|
+
* `--account` that looks like an email is the email; a name needs `--email` or
|
|
66
|
+
* a locally signed-in `.claude.json`. Missing email fails loud — we must not
|
|
67
|
+
* fall back to a bare shared key (that is the multi-account mix-up).
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveMintIdentity(input: ResolveMintIdentityInput): ResolvedMintIdentity;
|
|
70
|
+
/**
|
|
71
|
+
* Write (or rotate) the reserved FILE-BASED `auth` bundle's per-account key.
|
|
72
|
+
* Usage/probe ignores a keychain- or vault-backed bundle of this name, so a
|
|
73
|
+
* wrong backend fails loud instead of looking like a successful mint.
|
|
74
|
+
*/
|
|
75
|
+
export declare function seedReservedAuthToken(email: string, token: string): {
|
|
76
|
+
key: string;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Create or rotate the named provider account that `agents run --account` and
|
|
80
|
+
* `agents accounts sync` consume. Existing account of a different kind fails
|
|
81
|
+
* loud rather than silently overwriting an API key with a setup-token.
|
|
82
|
+
*/
|
|
83
|
+
export declare function seedNamedAccount(name: string, token: string, flow: MintFlow): CredentialAccount;
|
|
84
|
+
export interface MintDriveHooks {
|
|
85
|
+
driver?: PtyDriver;
|
|
86
|
+
openUrl?: (url: string) => Promise<void>;
|
|
87
|
+
/** Asked once the authorize URL is on screen, when `--code` was not given. */
|
|
88
|
+
readCode?: () => Promise<string | undefined>;
|
|
89
|
+
drive?: DriveOptions;
|
|
90
|
+
}
|
|
91
|
+
export interface DriveMintResult {
|
|
92
|
+
token: string;
|
|
93
|
+
url?: string;
|
|
94
|
+
sessionId: string;
|
|
95
|
+
}
|
|
96
|
+
export interface DriveSetupTokenMintOpts extends MintDriveHooks {
|
|
97
|
+
code?: string;
|
|
98
|
+
/** Suppress stdout progress so `--json` callers get a parseable blob. */
|
|
99
|
+
json?: boolean;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Drive `claude setup-token` in a PTY: scrape the authorize URL, open it,
|
|
103
|
+
* optionally paste `--code`, then capture the token with the #1767 guard.
|
|
104
|
+
* Tears the session down on the way out (success, timeout, or throw).
|
|
105
|
+
*/
|
|
106
|
+
export declare function driveSetupTokenMint(command: string, flow: MintFlow, opts?: DriveSetupTokenMintOpts): Promise<DriveMintResult>;
|
|
107
|
+
export declare function buildMintCommand(flow: MintFlow, bin: string, home: string): string;
|
|
108
|
+
export declare function resolveMintInstallation(harness: AgentId): {
|
|
109
|
+
version: string;
|
|
110
|
+
bin: string;
|
|
111
|
+
home: string;
|
|
112
|
+
};
|
|
113
|
+
export interface MintAndSeedInput {
|
|
114
|
+
harness: string;
|
|
115
|
+
account?: string;
|
|
116
|
+
email?: string;
|
|
117
|
+
token?: string;
|
|
118
|
+
code?: string;
|
|
119
|
+
open?: boolean;
|
|
120
|
+
fleet?: boolean;
|
|
121
|
+
devices?: string[];
|
|
122
|
+
/** Suppress progress prints so `--json` stdout stays machine-parseable. */
|
|
123
|
+
json?: boolean;
|
|
124
|
+
hooks?: MintDriveHooks;
|
|
125
|
+
}
|
|
126
|
+
export interface FleetSyncRow {
|
|
127
|
+
device: string;
|
|
128
|
+
ok: boolean;
|
|
129
|
+
message: string;
|
|
130
|
+
}
|
|
131
|
+
export interface MintAndSeedResult {
|
|
132
|
+
harness: AgentId;
|
|
133
|
+
account: string;
|
|
134
|
+
email: string;
|
|
135
|
+
authBundleKey: string;
|
|
136
|
+
rotated: boolean;
|
|
137
|
+
fleet: FleetSyncRow[];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* End-to-end mint: resolve identity, obtain a token (stdin or PTY drive),
|
|
141
|
+
* seed the named account + reserved auth bundle, optionally sync the fleet.
|
|
142
|
+
* Never returns or logs the token.
|
|
143
|
+
*/
|
|
144
|
+
export declare function mintAndSeed(input: MintAndSeedInput): Promise<MintAndSeedResult>;
|
|
145
|
+
export declare function resolveSyncTargets(fleet: boolean, devices: string[]): Promise<string[]>;
|
|
146
|
+
/** True when a Claude setup-token is already seeded on this box (setup status). */
|
|
147
|
+
export declare function hasMintedSetupToken(): {
|
|
148
|
+
ready: boolean;
|
|
149
|
+
detail: string;
|
|
150
|
+
};
|