@phnx-labs/agents-cli 1.22.45 → 1.22.47

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.
Files changed (177) hide show
  1. package/CHANGELOG.md +303 -0
  2. package/LICENSE +102 -182
  3. package/README.md +54 -17
  4. package/dist/cli/command-registry.d.ts +2 -1
  5. package/dist/cli/command-registry.js +6 -2
  6. package/dist/commands/accounts.js +56 -12
  7. package/dist/commands/auth.d.ts +2 -0
  8. package/dist/commands/auth.js +229 -0
  9. package/dist/commands/browser-sessions-picker.js +9 -7
  10. package/dist/commands/browser.js +266 -15
  11. package/dist/commands/config.js +26 -4
  12. package/dist/commands/cost.d.ts +1 -1
  13. package/dist/commands/detach.d.ts +13 -0
  14. package/dist/commands/detach.js +11 -8
  15. package/dist/commands/exec.js +55 -38
  16. package/dist/commands/feed-watch.d.ts +2 -0
  17. package/dist/commands/feed-watch.js +28 -0
  18. package/dist/commands/feed.js +27 -0
  19. package/dist/commands/feedback.js +3 -19
  20. package/dist/commands/insights.d.ts +1 -1
  21. package/dist/commands/insights.js +2 -2
  22. package/dist/commands/lease.js +3 -3
  23. package/dist/commands/routines.js +1 -1
  24. package/dist/commands/sessions-picker-factory.d.ts +2 -1
  25. package/dist/commands/sessions-picker-factory.js +1 -1
  26. package/dist/commands/sessions-stop.d.ts +24 -0
  27. package/dist/commands/sessions-stop.js +66 -0
  28. package/dist/commands/sessions-trace.d.ts +82 -0
  29. package/dist/commands/sessions-trace.js +387 -0
  30. package/dist/commands/sessions.js +4 -0
  31. package/dist/commands/ssh.d.ts +15 -0
  32. package/dist/commands/ssh.js +194 -16
  33. package/dist/commands/teams.js +34 -2
  34. package/dist/commands/view.d.ts +2 -0
  35. package/dist/commands/view.js +19 -32
  36. package/dist/commands/watchdog.js +9 -14
  37. package/dist/lib/account-capabilities.d.ts +4 -0
  38. package/dist/lib/account-capabilities.js +13 -9
  39. package/dist/lib/account-registry.d.ts +2 -0
  40. package/dist/lib/account-registry.js +30 -2
  41. package/dist/lib/accounting/rotate.d.ts +9 -0
  42. package/dist/lib/accounting/rotate.js +12 -2
  43. package/dist/lib/accounting/usage.d.ts +92 -6
  44. package/dist/lib/accounting/usage.js +329 -75
  45. package/dist/lib/agent-modes.js +29 -1
  46. package/dist/lib/agent-spec/agents.d.ts +4 -3
  47. package/dist/lib/agent-spec/agents.js +20 -7
  48. package/dist/lib/analytics/mix-commands.d.ts +1 -1
  49. package/dist/lib/analytics/mix-commands.js +2 -2
  50. package/dist/lib/auth-health.d.ts +13 -3
  51. package/dist/lib/auth-health.js +50 -8
  52. package/dist/lib/browser/devices.d.ts +19 -0
  53. package/dist/lib/browser/devices.js +23 -0
  54. package/dist/lib/browser/ipc.d.ts +1 -1
  55. package/dist/lib/browser/ipc.js +33 -7
  56. package/dist/lib/browser/profiles.d.ts +106 -1
  57. package/dist/lib/browser/profiles.js +300 -17
  58. package/dist/lib/browser/remote-control.d.ts +26 -2
  59. package/dist/lib/browser/remote-control.js +33 -3
  60. package/dist/lib/browser/runtime-state.d.ts +9 -0
  61. package/dist/lib/browser/runtime-state.js +24 -8
  62. package/dist/lib/browser/service.d.ts +37 -0
  63. package/dist/lib/browser/service.js +78 -20
  64. package/dist/lib/browser/sessions-list.d.ts +1 -3
  65. package/dist/lib/browser/sessions-list.js +3 -16
  66. package/dist/lib/browser/types.d.ts +12 -1
  67. package/dist/lib/cli-resources.js +17 -15
  68. package/dist/lib/codex-policy.d.ts +11 -1
  69. package/dist/lib/codex-policy.js +20 -5
  70. package/dist/lib/config-keys.d.ts +4 -3
  71. package/dist/lib/config-keys.js +17 -7
  72. package/dist/lib/daemon/runner.js +15 -2
  73. package/dist/lib/daemon-ticks.js +2 -2
  74. package/dist/lib/device-config.d.ts +25 -8
  75. package/dist/lib/device-config.js +90 -17
  76. package/dist/lib/devices/config-migration.d.ts +7 -0
  77. package/dist/lib/devices/config-migration.js +67 -3
  78. package/dist/lib/devices/harness-inventory.js +20 -3
  79. package/dist/lib/devices/health.d.ts +27 -2
  80. package/dist/lib/devices/health.js +39 -9
  81. package/dist/lib/devices/interactive-host.d.ts +15 -0
  82. package/dist/lib/devices/interactive-host.js +53 -0
  83. package/dist/lib/devices/registry.d.ts +61 -4
  84. package/dist/lib/devices/registry.js +94 -38
  85. package/dist/lib/devices/stats-cache.d.ts +6 -0
  86. package/dist/lib/devices/stats-cache.js +10 -2
  87. package/dist/lib/exec.d.ts +25 -3
  88. package/dist/lib/exec.js +75 -22
  89. package/dist/lib/feed/answer.d.ts +29 -0
  90. package/dist/lib/feed/answer.js +145 -0
  91. package/dist/lib/feed/attention.d.ts +83 -0
  92. package/dist/lib/feed/attention.js +0 -0
  93. package/dist/lib/feed/feed.d.ts +109 -0
  94. package/dist/lib/feed/feed.js +154 -3
  95. package/dist/lib/feed/pr-status.d.ts +14 -0
  96. package/dist/lib/feed/pr-status.js +47 -0
  97. package/dist/lib/feed/watch.d.ts +59 -0
  98. package/dist/lib/feed/watch.js +180 -0
  99. package/dist/lib/fleet/capture.js +6 -0
  100. package/dist/lib/fleet/remote-login.js +8 -3
  101. package/dist/lib/fleet/types.d.ts +17 -0
  102. package/dist/lib/harness/adapter.d.ts +11 -0
  103. package/dist/lib/harness/adapters/claude.js +38 -24
  104. package/dist/lib/harness/adapters/codex.js +2 -4
  105. package/dist/lib/hooks/cache.js +8 -0
  106. package/dist/lib/hooks/match.d.ts +3 -0
  107. package/dist/lib/hooks/match.js +25 -0
  108. package/dist/lib/hosts/passthrough.js +16 -0
  109. package/dist/lib/hosts/registry.js +7 -0
  110. package/dist/lib/identity/client.d.ts +53 -0
  111. package/dist/lib/identity/client.js +106 -0
  112. package/dist/lib/identity/index.d.ts +115 -0
  113. package/dist/lib/identity/index.js +82 -0
  114. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  115. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  116. package/dist/lib/menubar/install-menubar.d.ts +13 -0
  117. package/dist/lib/menubar/install-menubar.js +26 -2
  118. package/dist/lib/open-url.d.ts +60 -2
  119. package/dist/lib/open-url.js +167 -12
  120. package/dist/lib/probe.d.ts +8 -0
  121. package/dist/lib/probe.js +105 -0
  122. package/dist/lib/refresh-coordinator.d.ts +1 -1
  123. package/dist/lib/refresh-coordinator.js +1 -1
  124. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  125. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
  126. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  127. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  128. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
  129. package/dist/lib/session/active.d.ts +67 -0
  130. package/dist/lib/session/active.js +69 -1
  131. package/dist/lib/session/db.js +4 -2
  132. package/dist/lib/session/discover.d.ts +2 -0
  133. package/dist/lib/session/discover.js +12 -6
  134. package/dist/lib/session/prompt.d.ts +35 -0
  135. package/dist/lib/session/prompt.js +56 -0
  136. package/dist/lib/session/remote/watch.d.ts +1 -1
  137. package/dist/lib/session/remote/watch.js +6 -1
  138. package/dist/lib/session/render.js +7 -3
  139. package/dist/lib/session/session-cache.js +5 -1
  140. package/dist/lib/session/trajectory-compare.d.ts +47 -0
  141. package/dist/lib/session/trajectory-compare.js +116 -0
  142. package/dist/lib/session/trajectory-html.d.ts +15 -0
  143. package/dist/lib/session/trajectory-html.js +750 -0
  144. package/dist/lib/session/trajectory-lineage.d.ts +92 -0
  145. package/dist/lib/session/trajectory-lineage.js +195 -0
  146. package/dist/lib/session/trajectory-text.d.ts +37 -0
  147. package/dist/lib/session/trajectory-text.js +277 -0
  148. package/dist/lib/session/trajectory.d.ts +102 -0
  149. package/dist/lib/session/trajectory.js +335 -0
  150. package/dist/lib/startup/command-registry.d.ts +5 -2
  151. package/dist/lib/startup/command-registry.js +8 -5
  152. package/dist/lib/state.d.ts +1 -1
  153. package/dist/lib/state.js +4 -2
  154. package/dist/lib/teams/agents.js +9 -0
  155. package/dist/lib/terminal/inject.d.ts +19 -0
  156. package/dist/lib/terminal/inject.js +19 -9
  157. package/dist/lib/tmux/session.d.ts +46 -13
  158. package/dist/lib/tmux/session.js +173 -27
  159. package/dist/lib/types.d.ts +16 -0
  160. package/dist/lib/usage-backoff.d.ts +2 -1
  161. package/dist/lib/usage-backoff.js +60 -19
  162. package/dist/lib/usage-refresh.d.ts +17 -3
  163. package/dist/lib/usage-refresh.js +57 -11
  164. package/dist/lib/view-types.d.ts +2 -2
  165. package/dist/lib/watchdog/log.d.ts +1 -1
  166. package/dist/lib/watchdog/log.js +1 -1
  167. package/dist/lib/watchdog/runner.d.ts +36 -45
  168. package/dist/lib/watchdog/runner.js +150 -119
  169. package/dist/lib/watchdog/service.d.ts +0 -1
  170. package/dist/lib/watchdog/service.js +0 -1
  171. package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
  172. package/dist/lib/watchdog/watchdog-agent.js +59 -0
  173. package/dist/lib/watchdog/watchdog.d.ts +11 -2
  174. package/dist/lib/watchdog/watchdog.js +37 -104
  175. package/package.json +2 -2
  176. package/dist/commands/usage.d.ts +0 -27
  177. package/dist/commands/usage.js +0 -96
@@ -1,10 +1,21 @@
1
+ /** Native-login selector coverage required by RUSH-3053. */
2
+ export const NATIVE_ACCOUNT_SELECTOR_AGENTS = ['claude', 'codex', 'cursor', 'grok', 'kimi'];
3
+ /** Explicit dispositions for config-isolated harnesses outside that selector. */
4
+ export const NATIVE_ACCOUNT_SELECTOR_EXCLUSIONS = {
5
+ copilot: 'no inspectable native identity',
6
+ opencode: 'provider-set identity is not safely attributable to one native login',
7
+ muse: 'email-only conditional identity is outside the RUSH-3053 harness contract',
8
+ };
1
9
  /** Canonical truth for native-account naming and attachment semantics. */
2
10
  export const NATIVE_ACCOUNT_CAPABILITIES = {
3
- // Version-scoped with a strong account key the only harnesses whose native
4
- // identity is safe to pin per installed version.
11
+ // Config-isolated harnesses with a stable native identity. A labeled launch
12
+ // may use the auth/config home from one installed version with another binary.
5
13
  claude: { inspection: 'strong', scope: 'version', status: 'supported' },
6
14
  codex: { inspection: 'strong', scope: 'version', status: 'supported' },
7
15
  grok: { inspection: 'strong', scope: 'version', status: 'supported' },
16
+ cursor: { inspection: 'strong', scope: 'version', status: 'supported' },
17
+ // Kimi exposes a stable opaque id but no email, so it requires a manual label.
18
+ kimi: { inspection: 'opaque', scope: 'version', status: 'supported' },
8
19
  // Version-scoped but only an email identity — nameable only when that email is
9
20
  // present (the resolver rejects an emailless Muse login).
10
21
  muse: { inspection: 'email', scope: 'version', status: 'conditional' },
@@ -17,12 +28,8 @@ export const NATIVE_ACCOUNT_CAPABILITIES = {
17
28
  // "unsupported" is correct until a device-scoped identity key (with a stable
18
29
  // device id) exists to validate against.
19
30
  antigravity: { inspection: 'opaque', scope: 'device', status: 'unsupported' },
20
- kimi: { inspection: 'opaque', scope: 'device', status: 'unsupported' },
21
31
  droid: { inspection: 'opaque', scope: 'device', status: 'unsupported' },
22
32
  opencode: { inspection: 'opaque', scope: 'device', status: 'unsupported' },
23
- // Cursor multi-account isolation is unresolved (RUSH-2400) — blocked from
24
- // native naming/attachment; its API-key path is a provider account instead.
25
- cursor: { inspection: 'none', scope: 'unsupported', status: 'unsupported' },
26
33
  // Discoverable in the list, but not nameable.
27
34
  gemini: { inspection: 'email', scope: 'unsupported', status: 'discovery-only' },
28
35
  copilot: { inspection: 'none', scope: 'unsupported', status: 'unsupported' },
@@ -62,9 +69,6 @@ export function nativeAccountNamingRefusal(agent) {
62
69
  if (cap.scope === 'device') {
63
70
  return `${agent} accounts can't be isolated by agents-cli yet (device-scoped login). ${suffix}`;
64
71
  }
65
- if (agent === 'cursor') {
66
- return `${agent} accounts can't be isolated by agents-cli yet (multi-account isolation unresolved). ${suffix}`;
67
- }
68
72
  if (cap.status === 'discovery-only') {
69
73
  return `${agent} native accounts are discovery-only; agents-cli cannot name or attach this login. ${suffix}`;
70
74
  }
@@ -48,6 +48,8 @@ export declare function listNativeAccounts(meta: Pick<Meta, 'accounts'>): Native
48
48
  */
49
49
  export declare function findUnifiedAccount(nameOrId: string, meta: Pick<Meta, 'accounts'>, doc?: AccountRegistryDocument): UnifiedAccount | null;
50
50
  export declare function addNativeAccount(name: string, agent: AgentId, identityKey: string, identityLabel: string | undefined, scope: 'version' | 'device'): NativeAccount;
51
+ /** Create or replace the version-independent label for one native identity. */
52
+ export declare function labelNativeAccount(agent: AgentId, identityKey: string, identityLabel: string | undefined, label: string | undefined, scope: 'version' | 'device'): NativeAccount;
51
53
  export declare function bindAccount(nameOrId: string, target: string): UnifiedAccount;
52
54
  export declare function unbindAccount(nameOrId: string, target: string): void;
53
55
  export declare function accountBindings(accountId: string, meta: Pick<Meta, 'accounts'>): string[];
@@ -25,12 +25,17 @@ import { bundleExists, deleteBundle, listBundles, readBundle, renameBundle, writ
25
25
  import { getAccountProvider } from './account-provider-registry.js';
26
26
  import { accountSecretItem, buildAccountBundle, parseAccountBundle } from './account-schema.js';
27
27
  const NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
28
+ const NATIVE_LABEL = /^[a-zA-Z0-9][a-zA-Z0-9@._+-]*$/;
28
29
  const AUTH_KINDS = ['api-key', 'setup-token', 'bearer-token'];
29
30
  export function accountRegistryPath(base = getUserAgentsDir()) { return path.join(base, 'accounts.yaml'); }
30
31
  function assertName(name) {
31
32
  if (!NAME.test(name))
32
33
  throw new Error('Account name must start with a letter or number and contain only letters, numbers, dot, underscore, or dash.');
33
34
  }
35
+ function assertNativeLabel(label) {
36
+ if (!NATIVE_LABEL.test(label))
37
+ throw new Error('Account label must start with a letter or number and contain only letters, numbers, @, dot, underscore, plus, or dash.');
38
+ }
34
39
  function isAccountAuthKind(value) {
35
40
  return typeof value === 'string' && AUTH_KINDS.includes(value);
36
41
  }
@@ -143,7 +148,8 @@ export function listNativeAccounts(meta) {
143
148
  * able to omit it.
144
149
  */
145
150
  export function findUnifiedAccount(nameOrId, meta, doc) {
146
- const native = listNativeAccounts(meta).find(account => account.id === nameOrId || account.name === nameOrId);
151
+ const needle = nameOrId.toLowerCase();
152
+ const native = listNativeAccounts(meta).find(account => account.id === nameOrId || account.name.toLowerCase() === needle || account.identityLabel?.toLowerCase() === needle);
147
153
  if (native)
148
154
  return native;
149
155
  const provider = findAccount(nameOrId, doc ?? readAccountRegistry());
@@ -154,7 +160,7 @@ function assertUniqueUnifiedName(name, meta, doc) {
154
160
  throw new Error(`Account '${name}' already exists.`);
155
161
  }
156
162
  export function addNativeAccount(name, agent, identityKey, identityLabel, scope) {
157
- assertName(name);
163
+ assertNativeLabel(name);
158
164
  const meta = readMeta();
159
165
  assertUniqueUnifiedName(name, meta);
160
166
  const duplicate = listNativeAccounts(meta).find(account => account.agent === agent && account.identityKey === identityKey);
@@ -170,6 +176,28 @@ export function addNativeAccount(name, agent, identityKey, identityLabel, scope)
170
176
  }));
171
177
  return account;
172
178
  }
179
+ /** Create or replace the version-independent label for one native identity. */
180
+ export function labelNativeAccount(agent, identityKey, identityLabel, label, scope) {
181
+ const resolvedLabel = label ?? identityLabel;
182
+ if (!resolvedLabel)
183
+ throw new Error(`${agent} does not expose an email; pass a manual label.`);
184
+ assertNativeLabel(resolvedLabel);
185
+ const meta = readMeta();
186
+ const existing = listNativeAccounts(meta).find(account => account.agent === agent && account.identityKey === identityKey);
187
+ const collision = findUnifiedAccount(resolvedLabel, meta);
188
+ if (collision && collision.id !== existing?.id)
189
+ throw new Error(`Account '${resolvedLabel}' already exists.`);
190
+ if (!existing)
191
+ return addNativeAccount(resolvedLabel, agent, identityKey, identityLabel, scope);
192
+ updateMeta(current => ({
193
+ ...current,
194
+ accounts: {
195
+ ...current.accounts,
196
+ native: { ...current.accounts?.native, [existing.id]: { ...current.accounts?.native?.[existing.id], name: resolvedLabel, identityLabel } },
197
+ },
198
+ }));
199
+ return { ...existing, name: resolvedLabel, identityLabel };
200
+ }
173
201
  export function bindAccount(nameOrId, target) {
174
202
  const meta = readMeta();
175
203
  const account = findUnifiedAccount(nameOrId, meta);
@@ -118,6 +118,15 @@ export declare const USAGE_DECISION_MAX_AGE_MS: number;
118
118
  /**
119
119
  * Whether this candidate's usage number is recent enough to route on. A missing
120
120
  * snapshot is unverified by definition — there is no number to trust.
121
+ *
122
+ * A snapshot with NO windows is unverified for the same reason, however fresh
123
+ * it is: it carries a subscription plan and no utilization, so there is still
124
+ * no number. Freshness alone would make a meterless harness (Grok reports a
125
+ * tier and no meters) verify against nothing — and since `preferVerified`
126
+ * narrows the pool to verified candidates, the one account whose billing log
127
+ * was touched most recently would win every draw, then win again because
128
+ * running it refreshes that log. That self-reinforcing pin is exactly what the
129
+ * narrowing rule below exists to prevent.
121
130
  */
122
131
  export declare function isUsageVerified(candidate: RotateCandidate, nowMs?: number): boolean;
123
132
  /**
@@ -113,10 +113,20 @@ export const USAGE_DECISION_MAX_AGE_MS = 5 * 60 * 1000;
113
113
  /**
114
114
  * Whether this candidate's usage number is recent enough to route on. A missing
115
115
  * snapshot is unverified by definition — there is no number to trust.
116
+ *
117
+ * A snapshot with NO windows is unverified for the same reason, however fresh
118
+ * it is: it carries a subscription plan and no utilization, so there is still
119
+ * no number. Freshness alone would make a meterless harness (Grok reports a
120
+ * tier and no meters) verify against nothing — and since `preferVerified`
121
+ * narrows the pool to verified candidates, the one account whose billing log
122
+ * was touched most recently would win every draw, then win again because
123
+ * running it refreshes that log. That self-reinforcing pin is exactly what the
124
+ * narrowing rule below exists to prevent.
116
125
  */
117
126
  export function isUsageVerified(candidate, nowMs = Date.now()) {
118
- const capturedAt = candidate.usageSnapshot?.capturedAt;
119
- if (!capturedAt)
127
+ const snapshot = candidate.usageSnapshot;
128
+ const capturedAt = snapshot?.capturedAt;
129
+ if (!capturedAt || !snapshot?.windows.length)
120
130
  return false;
121
131
  return nowMs - capturedAt.getTime() <= USAGE_DECISION_MAX_AGE_MS;
122
132
  }
@@ -60,6 +60,40 @@ export declare function isClaudeUsageScopeDenied(status: number, bodyText: strin
60
60
  */
61
61
  export declare function usageThrottledError(agent: string, untilMs: number): string;
62
62
  export declare function usageUnreachableError(agent: string, cause?: unknown): string;
63
+ /**
64
+ * Marker for a log-based (`network: false`) provider — Codex, Grok — that has
65
+ * simply never recorded a rate-limit event on this machine yet: no session
66
+ * log exists, or no session in it carries usage data. Distinct on purpose from
67
+ * `usageUnreachableError`: that one means the local log COULDN'T be read (a
68
+ * real failure worth surfacing distinctly); this one means there is nothing to
69
+ * read because the account has not run here, which is expected for a fresh
70
+ * install and should render as a benign state, not an error (RUSH-3040).
71
+ */
72
+ export declare const USAGE_NO_RECENT_USAGE_MARKER = "no usage recorded yet";
73
+ export declare const USAGE_BENIGN_STATE: unique symbol;
74
+ export type UsageBenignState = 'no-recent-usage';
75
+ /**
76
+ * Shared error-classification + 429 backoff for a networked usage fetch whose
77
+ * only signal is an HTTP status (or none at all, on a network failure) —
78
+ * Antigravity's :retrieveUserQuota and Muse's Meta Model API probe are both
79
+ * this shape. They were added to `USAGE_SOURCES` after the four original
80
+ * `usageXError` constructors and did not get their own scheme (usage.ts's
81
+ * error handling was written for "four networked providers"; RUSH-3040).
82
+ * Route every no-snapshot outcome for either through this one function so a
83
+ * future entry cannot be added second-class again — it owns noting the 429
84
+ * backoff, so callers must NOT also call {@link noteUsageRateLimited} for the
85
+ * same response.
86
+ */
87
+ export declare function classifyUsageFetchFailure(agent: string, agentId: 'antigravity' | 'muse', status: number | null, retryAfterHeader: string | null | undefined, usageScope?: string | null): string;
88
+ /**
89
+ * The specific cause behind a `UsageInfo.error`, so a renderer can name the
90
+ * exact state instead of a generic "usage unavailable" for one of several
91
+ * distinct causes (RUSH-3040). Matched against the canonical strings this file
92
+ * constructs — never re-derive these prefixes at a call site.
93
+ */
94
+ export type UsageErrorKind = 'no-credential' | 'expired-credential' | 'rate-limited' | 'rejected' | 'headless-scope' | 'unreachable';
95
+ /** Classify a `UsageInfo.error` string into its {@link UsageErrorKind}, or null when there is no error. */
96
+ export declare function classifyUsageErrorKind(error: string | null | undefined): UsageErrorKind | null;
63
97
  /**
64
98
  * True when a Claude OAuth access token is within the refresh leeway of expiry
65
99
  * (or already expired) — i.e. it "would need a refresh" before the next use.
@@ -91,17 +125,29 @@ export interface UsageSnapshot {
91
125
  capturedAt: Date | null;
92
126
  windows: UsageWindow[];
93
127
  plan?: string | null;
94
- /** A refusal observed from a real harness run, independent of API windows. */
128
+ /**
129
+ * A refusal observed from a real harness run, independent of API windows.
130
+ * `session_limit` recovers on a clock (`resetsAt`). `out_of_credits` is a
131
+ * tokens/balance exhaustion that does NOT reset on a clock — it has no
132
+ * `resetsAt` and is cleared only by a later successful run on the account
133
+ * (clearClaudeAccountRefusal). Both exclude the account from rotation while set.
134
+ */
95
135
  unavailable?: {
96
- reason: 'session_limit';
97
- resetsAt: Date;
136
+ reason: 'session_limit' | 'out_of_credits';
137
+ resetsAt?: Date;
98
138
  };
99
139
  }
100
140
  /** Usage data plus any error encountered while fetching. */
101
141
  export interface UsageInfo {
102
142
  snapshot: UsageSnapshot | null;
103
143
  error: string | null;
144
+ /** Benign local state, symbol-backed so `--json` keeps its existing shape. */
145
+ [USAGE_BENIGN_STATE]?: UsageBenignState;
104
146
  }
147
+ /** Construct the benign no-local-log result without overloading `error`. */
148
+ export declare function usageNoRecentUsageInfo(): UsageInfo;
149
+ /** Read a benign state for the human renderer; symbols are omitted by JSON serialization. */
150
+ export declare function getUsageBenignState(info: UsageInfo): UsageBenignState | null;
105
151
  /** Input needed to identify an account for usage lookup. */
106
152
  export interface UsageIdentityInput {
107
153
  agentId: AgentId;
@@ -114,6 +160,13 @@ interface UsageOptions {
114
160
  home?: string;
115
161
  cliVersion?: string | null;
116
162
  organizationId?: string | null;
163
+ /**
164
+ * The account's usage key (`claude:org=…`, `kimi:user=…`, …) when the caller
165
+ * knows which account this fetch is for. Scopes the 429 backoff to that
166
+ * account (RUSH-3036) so one throttled account cannot park its siblings;
167
+ * absent, the backoff stays provider-wide.
168
+ */
169
+ usageScope?: string | null;
117
170
  /**
118
171
  * When true, never open the ACL-bound OS keychain item (macOS Touch ID).
119
172
  * Daemon usage refresh sets this so a background tick cannot pop biometrics.
@@ -253,6 +306,26 @@ export interface FormatUsageSummaryOpts {
253
306
  * column width; single-agent and detail views leave this unset.
254
307
  */
255
308
  maxWindows?: number;
309
+ /**
310
+ * The classified cause of `usageInfo.error` (RUSH-3040), from
311
+ * {@link classifyUsageErrorKind}. Lets the no-bars branch below name the
312
+ * SPECIFIC reason ('re-auth for usage', 'sign in / provision a long-lived
313
+ * token', 'rate-limited (retry ~12m)') instead of
314
+ * the generic 'usage unavailable' that used to cover ~6 distinct causes.
315
+ * Only consulted when `unavailable` is set — a snapshot WITH bars still
316
+ * renders 'unverified'/`headless` as before. `--json` output is unaffected:
317
+ * `UsageInfo.error` keeps carrying the full message; this only changes the
318
+ * short human string rendered here.
319
+ */
320
+ errorKind?: UsageErrorKind | null;
321
+ /**
322
+ * The raw `UsageInfo.error` string, read only to pull the retry-time hint
323
+ * out of a `rate-limited` classification (the exact duration lives in the
324
+ * message text, not the kind).
325
+ */
326
+ errorDetail?: string | null;
327
+ /** Benign state from {@link getUsageBenignState}; never sourced from `UsageInfo.error`. */
328
+ benignState?: UsageBenignState | null;
256
329
  }
257
330
  /** Format a one-line usage summary with compact bars for inline display. */
258
331
  export declare function formatUsageSummary(plan: string | null, snapshot: UsageSnapshot | null, planWidth?: number, opts?: FormatUsageSummaryOpts): string;
@@ -446,11 +519,11 @@ export interface ProviderProbe {
446
519
  reason?: 'usage_scope';
447
520
  }
448
521
  /** Probe Claude's OAuth token against the usage endpoint. Never refreshes — reports `expired` for a near-expiry token; see the comment below (RUSH-1822). */
449
- export declare function probeClaudeStatus(home?: string, cliVersion?: string | null): Promise<ProviderProbe>;
522
+ export declare function probeClaudeStatus(home?: string, cliVersion?: string | null, usageScope?: string | null): Promise<ProviderProbe>;
450
523
  /** Probe Kimi's OAuth token against the /usages endpoint. Never refreshes (single-use rotation — see getKimiUsageInfo). */
451
- export declare function probeKimiStatus(home?: string): Promise<ProviderProbe>;
524
+ export declare function probeKimiStatus(home?: string, usageScope?: string | null): Promise<ProviderProbe>;
452
525
  /** Probe Droid's WorkOS token against the billing-limits endpoint. Never refreshes (single-use rotation — see getDroidUsageInfo). */
453
- export declare function probeDroidStatus(home?: string): Promise<ProviderProbe>;
526
+ export declare function probeDroidStatus(home?: string, usageScope?: string | null): Promise<ProviderProbe>;
454
527
  /**
455
528
  * Normalize the Factory billing-limits payload into the common UsageWindow
456
529
  * shape. Orgs on the legacy (non token-rate-limit) billing model have no
@@ -515,6 +588,19 @@ export declare function readClaudeUsageCache(usageKey: string, cachePath?: strin
515
588
  export declare function pruneExpiredClaudeUsageCacheEntry(usageKey: string, cachePath?: string, now?: Date): void;
516
589
  /** Write a usage snapshot to the on-disk cache. */
517
590
  export declare function writeClaudeUsageCache(usageKey: string, snapshot: UsageSnapshot, cachePath?: string): void;
591
+ /**
592
+ * Persist a Claude tokens/credits exhaustion (`out of usage credits` / `monthly
593
+ * spend limit`) from a real run. Unlike a rate/session limit this does NOT reset
594
+ * on a clock, so no reset time is stored — rotation excludes the account until a
595
+ * later successful run clears it via {@link clearClaudeAccountRefusal}.
596
+ */
597
+ export declare function noteClaudeOutOfCredits(usageKey: string, cachePath?: string): void;
598
+ /**
599
+ * Clear any persisted refusal marker for an account after a run SUCCEEDS on it.
600
+ * This is the recovery path for `out_of_credits` (which has no clock) and also
601
+ * proactively clears a stale `session_limit` the moment the account serves again.
602
+ */
603
+ export declare function clearClaudeAccountRefusal(usageKey: string, cachePath?: string): void;
518
604
  /**
519
605
  * Persist a Claude session-limit refusal from a real run until its stated reset.
520
606
  * This quota is not part of Anthropic's five-hour/weekly usage response.