@cortexkit/opencode-anthropic-auth 1.19.1 → 1.21.0

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 (38) hide show
  1. package/README.md +119 -2
  2. package/dist/cache-diagnostics.d.ts +79 -0
  3. package/dist/cli.js +2 -2
  4. package/dist/index-1db17bc2.js +2 -0
  5. package/dist/{index-dex8fs0m.js → index-4nz423bt.js} +1 -1
  6. package/dist/{index-dzs7w297.js → index-4x20896x.js} +1 -1
  7. package/dist/{index-2k47hc8b.js → index-w9d41yy9.js} +1 -1
  8. package/dist/index-wezdyxst.js +2 -0
  9. package/dist/{index-075t8qec.js → index-y0khnbt6.js} +1 -1
  10. package/dist/index-yskwj5e6.js +428 -0
  11. package/dist/index.d.ts +0 -37
  12. package/dist/index.js +23 -19
  13. package/dist/lane-start.d.ts +17 -0
  14. package/dist/{logger-n49md99d.js → logger-ftk4bg3p.js} +1 -1
  15. package/dist/prime-manager-registry.d.ts +7 -0
  16. package/dist/request-policy.d.ts +25 -0
  17. package/dist/rpc/port-file.js +1 -1
  18. package/dist/rpc/protocol.d.ts +2 -1
  19. package/dist/rpc/protocol.js +1 -0
  20. package/dist/rpc/rpc-client.js +1 -1
  21. package/dist/rpc/rpc-dir.js +1 -1
  22. package/dist/server-fallback.d.ts +2 -0
  23. package/dist/sidebar-state.d.ts +26 -1
  24. package/dist/sidebar-state.js +1 -1
  25. package/dist/transform.d.ts +13 -0
  26. package/dist/tui/command-dialogs.d.ts +16 -1
  27. package/dist/tui-preferences.js +1 -1
  28. package/package.json +4 -4
  29. package/src/rpc/protocol.ts +15 -10
  30. package/src/sidebar-state.ts +154 -17
  31. package/src/tui/command-dialogs.tsx +112 -0
  32. package/src/tui-compiled/rpc/protocol.ts +15 -10
  33. package/src/tui-compiled/sidebar-state.ts +154 -17
  34. package/src/tui-compiled/tui/command-dialogs.tsx +134 -18
  35. package/src/tui-compiled/tui.tsx +43 -1
  36. package/src/tui.tsx +28 -0
  37. package/dist/index-a13w4gm9.js +0 -2
  38. package/dist/index-vwgzgh5x.js +0 -422
@@ -0,0 +1,17 @@
1
+ export declare const LANE_START_TEXT = "[lane start] \u2014 automated cache warm; no response needed.";
2
+ export declare const LANE_START_REQUEST_HEADER = "x-cortexkit-lane-start";
3
+ export declare function fireLaneStart(client: unknown, sessionId: string): Promise<void>;
4
+ export declare class LaneStartTracker {
5
+ #private;
6
+ observeSyntheticMessage(input: {
7
+ sessionId: string;
8
+ messageId: string;
9
+ parts: unknown[];
10
+ }): boolean;
11
+ markHeaders(input: {
12
+ sessionId: string;
13
+ messageId: string;
14
+ headers: Record<string, string>;
15
+ }): boolean;
16
+ clearSession(sessionId: string): void;
17
+ }
@@ -1 +1 @@
1
- import{cc as a,dc as b,ec as c,fc as d,gc as e,hc as f,ic as g,jc as h,kc as i,lc as j,mc as k,nc as l,oc as m}from"./index-dex8fs0m.js";import"./index-dzs7w297.js";export{d as shouldEmit,b as setLogLevel,m as relayLog,h as redactPayload,a as parseLogLevel,k as logger,l as log,g as isSecretKey,c as getLogLevel,e as getLogFilePath,i as formatLogLine,j as __setLogTestSink,f as SECRET_KEY_EXACT};
1
+ import{Fc as a,Gc as b,Hc as c,Ic as d,Jc as e,Kc as f,Lc as g,Mc as h,Nc as i,Oc as j,Pc as k,Qc as l,Rc as m}from"./index-4nz423bt.js";import"./index-4x20896x.js";export{d as shouldEmit,b as setLogLevel,m as relayLog,h as redactPayload,a as parseLogLevel,k as logger,l as log,g as isSecretKey,c as getLogLevel,e as getLogFilePath,i as formatLogLine,j as __setLogTestSink,f as SECRET_KEY_EXACT};
@@ -0,0 +1,7 @@
1
+ import { type PrimeManager } from '@cortexkit/anthropic-auth-core';
2
+ type PrimeManagerAdoption = {
3
+ slot: string;
4
+ rebind: (manager: PrimeManager) => void;
5
+ };
6
+ export declare function adoptPrimeManager(storagePath: string, create: () => PrimeManager, adoption: PrimeManagerAdoption): PrimeManager;
7
+ export {};
@@ -0,0 +1,25 @@
1
+ import { type AccountStorage, type OAuthQuotaSnapshot } from '@cortexkit/anthropic-auth-core';
2
+ /**
3
+ * Format the user-facing 429 message for a killswitch block. When the block
4
+ * is scoped-driven, name the model so the operator can distinguish a
5
+ * per-model weekly block from a whole-account kill.
6
+ */
7
+ export declare function formatKillswitchBlockMessage(input: {
8
+ retryAfterSeconds: number;
9
+ modelName?: string;
10
+ }): string;
11
+ /**
12
+ * Decide whether a killswitch block is scoped-driven versus a whole-account
13
+ * 5h/7d block. Account-level limits take precedence over scoped limits.
14
+ */
15
+ export declare function resolveScopedDrivenBlock(input: {
16
+ mainQuota: OAuthQuotaSnapshot | undefined;
17
+ requestModelId: string | undefined;
18
+ storage: AccountStorage | null;
19
+ }): {
20
+ isScopedDriven: true;
21
+ modelName: string;
22
+ modelId: string;
23
+ } | {
24
+ isScopedDriven: false;
25
+ };
@@ -1 +1 @@
1
- import{pc as a,qc as b}from"../index-2k47hc8b.js";import"../index-dzs7w297.js";export{a as writePortFile,b as discoverPortFile};
1
+ import{Sc as a,Tc as b}from"../index-w9d41yy9.js";import"../index-4x20896x.js";export{a as writePortFile,b as discoverPortFile};
@@ -1,4 +1,5 @@
1
- export type CommandModalName = 'claude-account' | 'claude-cache' | 'claude-cachekeep' | 'claude-quota' | 'claude-dump' | 'claude-fast' | 'claude-routing' | 'claude-killswitch' | 'claude-logging';
1
+ export declare const COMMAND_MODAL_NAMES: readonly ['claude-account', 'claude-cache', 'claude-cachekeep', 'claude-prime', 'claude-start', 'claude-quota', 'claude-dump', 'claude-fast', 'claude-routing', 'claude-killswitch', 'claude-logging'];
2
+ export type CommandModalName = (typeof COMMAND_MODAL_NAMES)[number];
2
3
  export interface OpenDialogPayload {
3
4
  command: CommandModalName;
4
5
  text: string;
@@ -0,0 +1 @@
1
+ import{Uc as a}from"../index-1db17bc2.js";import"../index-4x20896x.js";export{a as COMMAND_MODAL_NAMES};
@@ -1 +1 @@
1
- import{qc as l}from"../index-2k47hc8b.js";import"../index-dzs7w297.js";async function a(n,e,t,o){let r=await l(n,o);if(!r)return null;let i=new AbortController,p=setTimeout(()=>i.abort(),2000);try{let s=await fetch(`http://127.0.0.1:${r.port}/rpc/${e}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${r.token}`},body:JSON.stringify(t),signal:i.signal});if(!s.ok)return null;return await s.json()}catch{return null}finally{clearTimeout(p)}}function u(n,e){return{async pending(t,o){return(await a(n,"pending-notifications",{lastReceivedId:t,sessionId:o},e))?.messages??[]},async apply(t){return await a(n,"apply",{...t},e)??{text:"apply failed",knobs:{}}}}}export{u as createRpcClient};
1
+ import{Tc as l}from"../index-w9d41yy9.js";import"../index-4x20896x.js";async function a(n,e,t,o){let r=await l(n,o);if(!r)return null;let i=new AbortController,p=setTimeout(()=>i.abort(),2000);try{let s=await fetch(`http://127.0.0.1:${r.port}/rpc/${e}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${r.token}`},body:JSON.stringify(t),signal:i.signal});if(!s.ok)return null;return await s.json()}catch{return null}finally{clearTimeout(p)}}function u(n,e){return{async pending(t,o){return(await a(n,"pending-notifications",{lastReceivedId:t,sessionId:o},e))?.messages??[]},async apply(t){return await a(n,"apply",{...t},e)??{text:"apply failed",knobs:{}}}}}export{u as createRpcClient};
@@ -1 +1 @@
1
- import{rc as a,sc as b}from"../index-075t8qec.js";import"../index-dzs7w297.js";export{a as tmpdir,b as getRpcDir};
1
+ import{Vc as a,Wc as b}from"../index-y0khnbt6.js";import"../index-4x20896x.js";export{a as tmpdir,b as getRpcDir};
@@ -18,9 +18,11 @@ export declare function applyServerSideFallbackToBody(body: Record<string, unkno
18
18
  };
19
19
  export declare function createServerSideFallbackStreamRewriter(options: {
20
20
  requestedModel: string;
21
+ maxPendingBytes?: number;
21
22
  onOutcome?: (outcome: ServerSideFallbackOutcome) => void;
22
23
  onRefusalAfterToolUse?: () => boolean | undefined;
23
24
  }): {
25
+ pendingLength(): number;
24
26
  push(text: string): string;
25
27
  flush(): string;
26
28
  };
@@ -53,6 +53,15 @@ export interface FableRecoverySidebarState {
53
53
  /** Anthropic-selected target while server-side safety fallback is active. */
54
54
  targetModelId?: string;
55
55
  }
56
+ export interface PrimeSidebarAccountState {
57
+ id: string;
58
+ label: string;
59
+ nextDueAt?: number | null;
60
+ lastPrimedAt?: number | null;
61
+ lastResult?: 'ok' | 'error';
62
+ usage?: PrimeUsageCounters;
63
+ estimatedCostUsd?: number;
64
+ }
56
65
  export interface SidebarState {
57
66
  main: {
58
67
  quota: AccountQuota | null;
@@ -75,9 +84,19 @@ export interface SidebarState {
75
84
  window?: string;
76
85
  trackedSessions?: number;
77
86
  };
87
+ /**
88
+ * Prime opt-in flag plus per-account status. Absent on the wire when
89
+ * the feature is disabled — `normalizeSidebarState` validates every
90
+ * account independently and drops any entry it cannot prove.
91
+ */
92
+ prime?: {
93
+ enabled: boolean;
94
+ accounts: PrimeSidebarAccountState[];
95
+ };
78
96
  fableRecoveries?: FableRecoverySidebarState[];
79
97
  lastUpdated: number;
80
98
  }
99
+ import type { PrimeUsageCounters } from '@cortexkit/anthropic-auth-core';
81
100
  export declare function getSidebarStateFile(): string;
82
101
  export declare const DEFAULT_SIDEBAR_STATE: SidebarState;
83
102
  export declare function normalizeSidebarState(raw: unknown): SidebarState;
@@ -93,7 +112,7 @@ interface SidebarStateWriteTestHooks {
93
112
  lockRetryMinMs?: number;
94
113
  lockRetryMaxMs?: number;
95
114
  }
96
- export declare function __setSidebarStateWriteTestHooks(hooks: SidebarStateWriteTestHooks | null): void;
115
+ export declare function __setSidebarStateWriteTestHooks(hooks: SidebarStateWriteTestHooks | null, stateFile?: string): void;
97
116
  export interface InitialSidebarRoutingTestHooks {
98
117
  beforeSidebarRead?: () => void | Promise<void>;
99
118
  beforeStorageLoad?: () => void | Promise<void>;
@@ -126,6 +145,12 @@ export declare function formatScopedQuotaLabel(title: string): string;
126
145
  * blank on a future model that has not yet been cataloged here.
127
146
  */
128
147
  export declare function formatFallbackModelLabel(modelId: string | undefined): string;
148
+ export declare function formatPrimeTime(epochMs: number): string;
149
+ export declare function formatPrimeCost(value: number): string;
150
+ export declare function formatPrimeAccountValue(account: PrimeSidebarAccountState): {
151
+ text: string;
152
+ hasError: boolean;
153
+ };
129
154
  export declare function getFableRecoverySummary(state: SidebarState, sessionId: string): string | undefined;
130
155
  export declare function getCollapsedQuotaSummary(quota: AccountQuota | null): {
131
156
  fiveHourUsedPercent: number | null;
@@ -1 +1 @@
1
- import{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r}from"./index-a13w4gm9.js";import"./index-vwgzgh5x.js";import"./index-dex8fs0m.js";import"./index-dzs7w297.js";export{i as setSidebarState,k as resolveActiveAccount,c as normalizeSidebarState,a as getSidebarStateFile,h as getSidebarState,f as getInitialSidebarRoutingTestHooks,n as getFableRecoverySummary,o as getCollapsedQuotaSummary,l as formatScopedQuotaLabel,m as formatFallbackModelLabel,j as drainSidebarWrites,r as computeQuotaPacing,d as __setSidebarStateWriteTestHooks,e as __setInitialSidebarRoutingTestHooks,g as __acquireSidebarStateLockForTest,q as SEVEN_DAY_MS,p as FIVE_HOUR_MS,b as DEFAULT_SIDEBAR_STATE};
1
+ import{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u}from"./index-wezdyxst.js";import"./index-yskwj5e6.js";import"./index-4nz423bt.js";import"./index-4x20896x.js";export{i as setSidebarState,k as resolveActiveAccount,c as normalizeSidebarState,a as getSidebarStateFile,h as getSidebarState,f as getInitialSidebarRoutingTestHooks,q as getFableRecoverySummary,r as getCollapsedQuotaSummary,l as formatScopedQuotaLabel,n as formatPrimeTime,o as formatPrimeCost,p as formatPrimeAccountValue,m as formatFallbackModelLabel,j as drainSidebarWrites,u as computeQuotaPacing,d as __setSidebarStateWriteTestHooks,e as __setInitialSidebarRoutingTestHooks,g as __acquireSidebarStateLockForTest,t as SEVEN_DAY_MS,s as FIVE_HOUR_MS,b as DEFAULT_SIDEBAR_STATE};
@@ -1,5 +1,6 @@
1
1
  import { type Cache1hMode, type ClaudeCodeIdentity } from '@cortexkit/anthropic-auth-core';
2
2
  import { type ServerSideFallbackOutcome } from './server-fallback';
3
+ export declare const NON_STREAMING_DIAGNOSTICS_MAX_BYTES: number;
3
4
  export type FetchInput = string | URL | Request;
4
5
  /**
5
6
  * Merge headers from a Request object and/or a RequestInit headers value
@@ -86,6 +87,8 @@ export declare function rewriteRequestBody(body: string, options?: {
86
87
  perf?: RewritePerfCallback;
87
88
  hybridStandbyAnchor?: HybridMessageCacheAnchor;
88
89
  serverSideFallbackEnabled?: boolean;
90
+ laneStart?: boolean;
91
+ cacheDiagnosticsPreviousMessageId?: string | null;
89
92
  }): Promise<string>;
90
93
  /**
91
94
  * Create a streaming response that strips the tool prefix from tool names.
@@ -99,5 +102,15 @@ export declare function createStrippedStream(response: Response, options?: {
99
102
  contentFilterModel?: unknown;
100
103
  serverSideFallbackModel?: string;
101
104
  onServerSideFallbackOutcome?: (outcome: ServerSideFallbackOutcome) => void;
105
+ onMessageStart?: (message: Record<string, unknown>) => void;
106
+ onMessageDelta?: (delta: {
107
+ usage?: Record<string, unknown>;
108
+ stopReason?: string;
109
+ }) => void;
110
+ onMessageResponse?: (message: Record<string, unknown>) => void;
111
+ onStreamEnd?: () => void | Promise<void>;
112
+ responseMode?: 'json';
113
+ laneStart?: boolean;
114
+ laneStartOAuthServed?: boolean;
102
115
  }): Response;
103
116
  export {};
@@ -1,4 +1,5 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
+ import type { PrimeAccountStatus } from '@cortexkit/anthropic-auth-core';
2
3
  import type { TuiPluginApi } from '@opencode-ai/plugin/tui';
3
4
  import type { OpenDialogPayload } from '../rpc/protocol.js';
4
5
  type ApplyFn = (command: OpenDialogPayload['command'], args: string) => Promise<{
@@ -10,6 +11,13 @@ type KillswitchDialogConfig = {
10
11
  main?: Record<string, number>;
11
12
  accounts?: Record<string, Record<string, number>>;
12
13
  };
14
+ export declare const PRIME_DIALOG_OPTIONS: {
15
+ title: string;
16
+ value: string;
17
+ }[];
18
+ export declare function handlePrimeStatusOption(option: {
19
+ value: string;
20
+ }, renderMain: () => void): void;
13
21
  export declare function buildKillswitchThresholdSeed(config: KillswitchDialogConfig, accountIds: string[]): string;
14
22
  export declare function buildAccountDialogOption(account: {
15
23
  id: string;
@@ -19,9 +27,16 @@ export declare function buildAccountDialogOption(account: {
19
27
  quotaPercent: number | null;
20
28
  tierLabel?: string;
21
29
  }): {
22
- description?: string | undefined;
23
30
  title: string;
24
31
  value: string;
32
+ description?: string | undefined;
25
33
  };
34
+ /**
35
+ * Format the per-account status lines shown in the Claude prime Status
36
+ * view. Pure: consumed by the dialog's Status pane; the sidebar uses a
37
+ * different formatter (`formatPrimeSidebarValue`) for its one-line
38
+ * expanded row.
39
+ */
40
+ export declare function buildPrimeStatusRows(accounts: PrimeAccountStatus[]): string[];
26
41
  export declare function openCommandDialog(api: TuiPluginApi, payload: OpenDialogPayload, apply: ApplyFn): void;
27
42
  export {};
@@ -1,4 +1,4 @@
1
- import{tc as c,uc as C,vc as v}from"./index-dzs7w297.js";var m=C((h,k)=>{(function(Q){if(typeof k==="object"&&typeof k.exports==="object"){var J=Q(v,h);if(J!==void 0)k.exports=J}else if(typeof define==="function"&&define.amd)define(["require","exports","./impl/format","./impl/edit","./impl/scanner","./impl/parser"],Q)})(function(Q,J){Object.defineProperty(J,"__esModule",{value:!0}),J.applyEdits=J.modify=J.format=J.printParseErrorCode=J.ParseErrorCode=J.stripComments=J.visit=J.getNodeValue=J.getNodePath=J.findNodeAtOffset=J.findNodeAtLocation=J.parseTree=J.parse=J.getLocation=J.SyntaxKind=J.ScanError=J.createScanner=void 0;let X=Q("./impl/format"),G=Q("./impl/edit"),Z=Q("./impl/scanner"),$=Q("./impl/parser");J.createScanner=Z.createScanner;var V;(function(z){z[z.None=0]="None",z[z.UnexpectedEndOfComment=1]="UnexpectedEndOfComment",z[z.UnexpectedEndOfString=2]="UnexpectedEndOfString",z[z.UnexpectedEndOfNumber=3]="UnexpectedEndOfNumber",z[z.InvalidUnicode=4]="InvalidUnicode",z[z.InvalidEscapeCharacter=5]="InvalidEscapeCharacter",z[z.InvalidCharacter=6]="InvalidCharacter"})(V||(J.ScanError=V={}));var Y;(function(z){z[z.OpenBraceToken=1]="OpenBraceToken",z[z.CloseBraceToken=2]="CloseBraceToken",z[z.OpenBracketToken=3]="OpenBracketToken",z[z.CloseBracketToken=4]="CloseBracketToken",z[z.CommaToken=5]="CommaToken",z[z.ColonToken=6]="ColonToken",z[z.NullKeyword=7]="NullKeyword",z[z.TrueKeyword=8]="TrueKeyword",z[z.FalseKeyword=9]="FalseKeyword",z[z.StringLiteral=10]="StringLiteral",z[z.NumericLiteral=11]="NumericLiteral",z[z.LineCommentTrivia=12]="LineCommentTrivia",z[z.BlockCommentTrivia=13]="BlockCommentTrivia",z[z.LineBreakTrivia=14]="LineBreakTrivia",z[z.Trivia=15]="Trivia",z[z.Unknown=16]="Unknown",z[z.EOF=17]="EOF"})(Y||(J.SyntaxKind=Y={})),J.getLocation=$.getLocation,J.parse=$.parse,J.parseTree=$.parseTree,J.findNodeAtLocation=$.findNodeAtLocation,J.findNodeAtOffset=$.findNodeAtOffset,J.getNodePath=$.getNodePath,J.getNodeValue=$.getNodeValue,J.visit=$.visit,J.stripComments=$.stripComments;var A;(function(z){z[z.InvalidSymbol=1]="InvalidSymbol",z[z.InvalidNumberFormat=2]="InvalidNumberFormat",z[z.PropertyNameExpected=3]="PropertyNameExpected",z[z.ValueExpected=4]="ValueExpected",z[z.ColonExpected=5]="ColonExpected",z[z.CommaExpected=6]="CommaExpected",z[z.CloseBraceExpected=7]="CloseBraceExpected",z[z.CloseBracketExpected=8]="CloseBracketExpected",z[z.EndOfFileExpected=9]="EndOfFileExpected",z[z.InvalidCommentToken=10]="InvalidCommentToken",z[z.UnexpectedEndOfComment=11]="UnexpectedEndOfComment",z[z.UnexpectedEndOfString=12]="UnexpectedEndOfString",z[z.UnexpectedEndOfNumber=13]="UnexpectedEndOfNumber",z[z.InvalidUnicode=14]="InvalidUnicode",z[z.InvalidEscapeCharacter=15]="InvalidEscapeCharacter",z[z.InvalidCharacter=16]="InvalidCharacter"})(A||(J.ParseErrorCode=A={}));function L(z){switch(z){case 1:return"InvalidSymbol";case 2:return"InvalidNumberFormat";case 3:return"PropertyNameExpected";case 4:return"ValueExpected";case 5:return"ColonExpected";case 6:return"CommaExpected";case 7:return"CloseBraceExpected";case 8:return"CloseBracketExpected";case 9:return"EndOfFileExpected";case 10:return"InvalidCommentToken";case 11:return"UnexpectedEndOfComment";case 12:return"UnexpectedEndOfString";case 13:return"UnexpectedEndOfNumber";case 14:return"InvalidUnicode";case 15:return"InvalidEscapeCharacter";case 16:return"InvalidCharacter"}return"<unknown ParseErrorCode>"}J.printParseErrorCode=L;function W(z,B,j){return X.format(z,B,j)}J.format=W;function q(z,B,j,R){return G.setProperty(z,B,j,R)}J.modify=q;function O(z,B){let j=B.slice(0).sort((M,N)=>{let T=M.offset-N.offset;if(T===0)return M.length-N.length;return T}),R=z.length;for(let M=j.length-1;M>=0;M--){let N=j[M];if(N.offset+N.length<=R)z=G.applyEdit(z,N);else throw Error("Overlapping edit");R=N.offset}return z}J.applyEdits=O})});var I=c(m(),1);import{readFileSync as y,watch as S}from"node:fs";import{mkdir as E,readFile as g,rename as p,writeFile as l}from"node:fs/promises";import{homedir as x}from"node:os";import{basename as i,dirname as P,join as w}from"node:path";import{clearTimeout as _,setTimeout as f}from"node:timers";var s="OPENCODE_TUI_PREFERENCES_FILE",d="tui-preferences.jsonc";function b(){let Q=process.env[s];if(Q)return Q;let J=process.env.OPENCODE_CONFIG_DIR||w(process.env.XDG_CONFIG_HOME||w(x(),".config"),"opencode");return w(J,d)}function U(Q){return typeof Q==="object"&&Q!==null&&!Array.isArray(Q)}async function Yz(){try{let Q=await g(b(),"utf8"),J=[],X=I.parse(Q,J,{allowTrailingComma:!0});if(J.length>0)return{};return U(X)?X:{}}catch{return{}}}var n="anthropic-auth",t=160,u={forceToTop:!1,order:t,startCollapsed:!1,rememberCollapsed:!0,collapsed:null,pollMs:1500,refreshDebounceMs:200,header:{label:"CLAUDE",showVersion:!0},sections:{quota:!0,fallbackAccounts:!0,routing:!0,cache:!0,health:!0,pacing:!0},appearance:{barWidth:10,barFilledChar:"█",barEmptyChar:"░",warnThreshold:50,errorThreshold:80}};function H(Q,J){return typeof Q==="boolean"?Q:J}function D(Q,J,X,G){if(typeof Q!=="number"||!Number.isFinite(Q))return J;return Math.min(Math.max(Math.round(Q),X),G)}function a(Q,J,X){if(typeof Q!=="string"||Q.length===0)return J;return Q.slice(0,X)}function K(Q,J){if(typeof Q!=="string")return J;return[...Q][0]??J}function Bz(Q){let J=Q[n];if(!U(J))return structuredClone(u);let X=u,G=U(J.header)?J.header:{},Z=U(J.sections)?J.sections:{},$=U(J.appearance)?J.appearance:{},V=D($.warnThreshold,X.appearance.warnThreshold,0,99),Y=Math.max(D($.errorThreshold,X.appearance.errorThreshold,0,100),Math.min(V+1,100));return{forceToTop:H(J.forceToTop,X.forceToTop),order:D(J.order,X.order,-1e4,1e4),startCollapsed:H(J.startCollapsed,X.startCollapsed),rememberCollapsed:H(J.rememberCollapsed,X.rememberCollapsed),collapsed:typeof J.collapsed==="boolean"?J.collapsed:null,pollMs:D(J.pollMs,X.pollMs,500,30000),refreshDebounceMs:D(J.refreshDebounceMs,X.refreshDebounceMs,50,5000),header:{label:a(G.label,X.header.label,20),showVersion:H(G.showVersion,X.header.showVersion)},sections:{quota:H(Z.quota,X.sections.quota),fallbackAccounts:H(Z.fallbackAccounts,X.sections.fallbackAccounts),routing:H(Z.routing,X.sections.routing),cache:H(Z.cache,X.sections.cache),health:H(Z.health,X.sections.health),pacing:H(Z.pacing,X.sections.pacing)},appearance:{barWidth:D($.barWidth,X.appearance.barWidth,4,40),barFilledChar:K($.barFilledChar,X.appearance.barFilledChar),barEmptyChar:K($.barEmptyChar,X.appearance.barEmptyChar),warnThreshold:V,errorThreshold:Y}}}var o=-1e5;function Hz(Q,J,X){let G=Q[J];if(!U(G))return X;if(G.forceToTop===!0)return o+Object.keys(Q).indexOf(J);return D(G.order,X,-1e4,1e4)}var e=`// Shared preferences for opencode TUI plugins.
1
+ import{Xc as c,Yc as C,Zc as v}from"./index-4x20896x.js";var m=C((h,k)=>{(function(Q){if(typeof k==="object"&&typeof k.exports==="object"){var J=Q(v,h);if(J!==void 0)k.exports=J}else if(typeof define==="function"&&define.amd)define(["require","exports","./impl/format","./impl/edit","./impl/scanner","./impl/parser"],Q)})(function(Q,J){Object.defineProperty(J,"__esModule",{value:!0}),J.applyEdits=J.modify=J.format=J.printParseErrorCode=J.ParseErrorCode=J.stripComments=J.visit=J.getNodeValue=J.getNodePath=J.findNodeAtOffset=J.findNodeAtLocation=J.parseTree=J.parse=J.getLocation=J.SyntaxKind=J.ScanError=J.createScanner=void 0;let X=Q("./impl/format"),G=Q("./impl/edit"),Z=Q("./impl/scanner"),$=Q("./impl/parser");J.createScanner=Z.createScanner;var V;(function(z){z[z.None=0]="None",z[z.UnexpectedEndOfComment=1]="UnexpectedEndOfComment",z[z.UnexpectedEndOfString=2]="UnexpectedEndOfString",z[z.UnexpectedEndOfNumber=3]="UnexpectedEndOfNumber",z[z.InvalidUnicode=4]="InvalidUnicode",z[z.InvalidEscapeCharacter=5]="InvalidEscapeCharacter",z[z.InvalidCharacter=6]="InvalidCharacter"})(V||(J.ScanError=V={}));var Y;(function(z){z[z.OpenBraceToken=1]="OpenBraceToken",z[z.CloseBraceToken=2]="CloseBraceToken",z[z.OpenBracketToken=3]="OpenBracketToken",z[z.CloseBracketToken=4]="CloseBracketToken",z[z.CommaToken=5]="CommaToken",z[z.ColonToken=6]="ColonToken",z[z.NullKeyword=7]="NullKeyword",z[z.TrueKeyword=8]="TrueKeyword",z[z.FalseKeyword=9]="FalseKeyword",z[z.StringLiteral=10]="StringLiteral",z[z.NumericLiteral=11]="NumericLiteral",z[z.LineCommentTrivia=12]="LineCommentTrivia",z[z.BlockCommentTrivia=13]="BlockCommentTrivia",z[z.LineBreakTrivia=14]="LineBreakTrivia",z[z.Trivia=15]="Trivia",z[z.Unknown=16]="Unknown",z[z.EOF=17]="EOF"})(Y||(J.SyntaxKind=Y={})),J.getLocation=$.getLocation,J.parse=$.parse,J.parseTree=$.parseTree,J.findNodeAtLocation=$.findNodeAtLocation,J.findNodeAtOffset=$.findNodeAtOffset,J.getNodePath=$.getNodePath,J.getNodeValue=$.getNodeValue,J.visit=$.visit,J.stripComments=$.stripComments;var A;(function(z){z[z.InvalidSymbol=1]="InvalidSymbol",z[z.InvalidNumberFormat=2]="InvalidNumberFormat",z[z.PropertyNameExpected=3]="PropertyNameExpected",z[z.ValueExpected=4]="ValueExpected",z[z.ColonExpected=5]="ColonExpected",z[z.CommaExpected=6]="CommaExpected",z[z.CloseBraceExpected=7]="CloseBraceExpected",z[z.CloseBracketExpected=8]="CloseBracketExpected",z[z.EndOfFileExpected=9]="EndOfFileExpected",z[z.InvalidCommentToken=10]="InvalidCommentToken",z[z.UnexpectedEndOfComment=11]="UnexpectedEndOfComment",z[z.UnexpectedEndOfString=12]="UnexpectedEndOfString",z[z.UnexpectedEndOfNumber=13]="UnexpectedEndOfNumber",z[z.InvalidUnicode=14]="InvalidUnicode",z[z.InvalidEscapeCharacter=15]="InvalidEscapeCharacter",z[z.InvalidCharacter=16]="InvalidCharacter"})(A||(J.ParseErrorCode=A={}));function L(z){switch(z){case 1:return"InvalidSymbol";case 2:return"InvalidNumberFormat";case 3:return"PropertyNameExpected";case 4:return"ValueExpected";case 5:return"ColonExpected";case 6:return"CommaExpected";case 7:return"CloseBraceExpected";case 8:return"CloseBracketExpected";case 9:return"EndOfFileExpected";case 10:return"InvalidCommentToken";case 11:return"UnexpectedEndOfComment";case 12:return"UnexpectedEndOfString";case 13:return"UnexpectedEndOfNumber";case 14:return"InvalidUnicode";case 15:return"InvalidEscapeCharacter";case 16:return"InvalidCharacter"}return"<unknown ParseErrorCode>"}J.printParseErrorCode=L;function W(z,B,j){return X.format(z,B,j)}J.format=W;function q(z,B,j,R){return G.setProperty(z,B,j,R)}J.modify=q;function O(z,B){let j=B.slice(0).sort((M,N)=>{let T=M.offset-N.offset;if(T===0)return M.length-N.length;return T}),R=z.length;for(let M=j.length-1;M>=0;M--){let N=j[M];if(N.offset+N.length<=R)z=G.applyEdit(z,N);else throw Error("Overlapping edit");R=N.offset}return z}J.applyEdits=O})});var I=c(m(),1);import{readFileSync as y,watch as S}from"node:fs";import{mkdir as E,readFile as g,rename as p,writeFile as l}from"node:fs/promises";import{homedir as x}from"node:os";import{basename as i,dirname as P,join as w}from"node:path";import{clearTimeout as _,setTimeout as f}from"node:timers";var s="OPENCODE_TUI_PREFERENCES_FILE",d="tui-preferences.jsonc";function b(){let Q=process.env[s];if(Q)return Q;let J=process.env.OPENCODE_CONFIG_DIR||w(process.env.XDG_CONFIG_HOME||w(x(),".config"),"opencode");return w(J,d)}function U(Q){return typeof Q==="object"&&Q!==null&&!Array.isArray(Q)}async function Yz(){try{let Q=await g(b(),"utf8"),J=[],X=I.parse(Q,J,{allowTrailingComma:!0});if(J.length>0)return{};return U(X)?X:{}}catch{return{}}}var n="anthropic-auth",t=160,u={forceToTop:!1,order:t,startCollapsed:!1,rememberCollapsed:!0,collapsed:null,pollMs:1500,refreshDebounceMs:200,header:{label:"CLAUDE",showVersion:!0},sections:{quota:!0,fallbackAccounts:!0,routing:!0,cache:!0,health:!0,pacing:!0},appearance:{barWidth:10,barFilledChar:"█",barEmptyChar:"░",warnThreshold:50,errorThreshold:80}};function H(Q,J){return typeof Q==="boolean"?Q:J}function D(Q,J,X,G){if(typeof Q!=="number"||!Number.isFinite(Q))return J;return Math.min(Math.max(Math.round(Q),X),G)}function a(Q,J,X){if(typeof Q!=="string"||Q.length===0)return J;return Q.slice(0,X)}function K(Q,J){if(typeof Q!=="string")return J;return[...Q][0]??J}function Bz(Q){let J=Q[n];if(!U(J))return structuredClone(u);let X=u,G=U(J.header)?J.header:{},Z=U(J.sections)?J.sections:{},$=U(J.appearance)?J.appearance:{},V=D($.warnThreshold,X.appearance.warnThreshold,0,99),Y=Math.max(D($.errorThreshold,X.appearance.errorThreshold,0,100),Math.min(V+1,100));return{forceToTop:H(J.forceToTop,X.forceToTop),order:D(J.order,X.order,-1e4,1e4),startCollapsed:H(J.startCollapsed,X.startCollapsed),rememberCollapsed:H(J.rememberCollapsed,X.rememberCollapsed),collapsed:typeof J.collapsed==="boolean"?J.collapsed:null,pollMs:D(J.pollMs,X.pollMs,500,30000),refreshDebounceMs:D(J.refreshDebounceMs,X.refreshDebounceMs,50,5000),header:{label:a(G.label,X.header.label,20),showVersion:H(G.showVersion,X.header.showVersion)},sections:{quota:H(Z.quota,X.sections.quota),fallbackAccounts:H(Z.fallbackAccounts,X.sections.fallbackAccounts),routing:H(Z.routing,X.sections.routing),cache:H(Z.cache,X.sections.cache),health:H(Z.health,X.sections.health),pacing:H(Z.pacing,X.sections.pacing)},appearance:{barWidth:D($.barWidth,X.appearance.barWidth,4,40),barFilledChar:K($.barFilledChar,X.appearance.barFilledChar),barEmptyChar:K($.barEmptyChar,X.appearance.barEmptyChar),warnThreshold:V,errorThreshold:Y}}}var o=-1e5;function Hz(Q,J,X){let G=Q[J];if(!U(G))return X;if(G.forceToTop===!0)return o+Object.keys(Q).indexOf(J);return D(G.order,X,-1e4,1e4)}var e=`// Shared preferences for opencode TUI plugins.
2
2
  // One top-level key per plugin (short name). See each plugin's README for
3
3
  // its supported settings. This file is safe to hand-edit; plugins update
4
4
  // individual keys in place and preserve comments.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-anthropic-auth",
3
- "version": "1.19.1",
3
+ "version": "1.21.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -61,9 +61,9 @@
61
61
  "@opencode-ai/plugin": "*"
62
62
  },
63
63
  "dependencies": {
64
- "@cortexkit/anthropic-auth-core": "1.19.1",
65
- "@opentui/core": "0.4.3",
66
- "@opentui/solid": "0.4.3",
64
+ "@cortexkit/anthropic-auth-core": "1.21.0",
65
+ "@opentui/core": "0.5.7",
66
+ "@opentui/solid": "0.5.7",
67
67
  "jsonc-parser": "^3.3.1",
68
68
  "solid-js": "1.9.12"
69
69
  }
@@ -1,13 +1,18 @@
1
- export type CommandModalName =
2
- | 'claude-account'
3
- | 'claude-cache'
4
- | 'claude-cachekeep'
5
- | 'claude-quota'
6
- | 'claude-dump'
7
- | 'claude-fast'
8
- | 'claude-routing'
9
- | 'claude-killswitch'
10
- | 'claude-logging'
1
+ export const COMMAND_MODAL_NAMES = [
2
+ 'claude-account',
3
+ 'claude-cache',
4
+ 'claude-cachekeep',
5
+ 'claude-prime',
6
+ 'claude-start',
7
+ 'claude-quota',
8
+ 'claude-dump',
9
+ 'claude-fast',
10
+ 'claude-routing',
11
+ 'claude-killswitch',
12
+ 'claude-logging',
13
+ ] as const
14
+
15
+ export type CommandModalName = (typeof COMMAND_MODAL_NAMES)[number]
11
16
 
12
17
  export interface OpenDialogPayload {
13
18
  command: CommandModalName
@@ -52,6 +52,16 @@ export interface FableRecoverySidebarState {
52
52
  targetModelId?: string
53
53
  }
54
54
 
55
+ export interface PrimeSidebarAccountState {
56
+ id: string
57
+ label: string
58
+ nextDueAt?: number | null
59
+ lastPrimedAt?: number | null
60
+ lastResult?: 'ok' | 'error'
61
+ usage?: PrimeUsageCounters
62
+ estimatedCostUsd?: number
63
+ }
64
+
55
65
  export interface SidebarState {
56
66
  main: {
57
67
  quota: AccountQuota | null
@@ -71,6 +81,15 @@ export interface SidebarState {
71
81
  window?: string
72
82
  trackedSessions?: number
73
83
  }
84
+ /**
85
+ * Prime opt-in flag plus per-account status. Absent on the wire when
86
+ * the feature is disabled — `normalizeSidebarState` validates every
87
+ * account independently and drops any entry it cannot prove.
88
+ */
89
+ prime?: {
90
+ enabled: boolean
91
+ accounts: PrimeSidebarAccountState[]
92
+ }
74
93
  fableRecoveries?: FableRecoverySidebarState[]
75
94
  lastUpdated: number
76
95
  }
@@ -88,6 +107,7 @@ import {
88
107
  } from 'node:fs/promises'
89
108
  import { tmpdir } from 'node:os'
90
109
  import { dirname, join } from 'node:path'
110
+ import type { PrimeUsageCounters } from '@cortexkit/anthropic-auth-core'
91
111
  import { logger } from '@cortexkit/anthropic-auth-core'
92
112
 
93
113
  const STATE_FILE_ENV = 'OPENCODE_ANTHROPIC_AUTH_SIDEBAR_STATE_FILE'
@@ -111,6 +131,77 @@ function isRecord(v: unknown): v is Record<string, unknown> {
111
131
  return v !== null && typeof v === 'object' && !Array.isArray(v)
112
132
  }
113
133
 
134
+ function isFiniteNumber(value: unknown): value is number {
135
+ return typeof value === 'number' && Number.isFinite(value)
136
+ }
137
+
138
+ function normalizePrimeUsage(value: unknown): PrimeUsageCounters | undefined {
139
+ if (!isRecord(value)) return undefined
140
+ const count = Number(value.count)
141
+ const inputTokens = Number(value.inputTokens)
142
+ const outputTokens = Number(value.outputTokens)
143
+ const since = Number(value.since)
144
+ if (
145
+ ![count, inputTokens, outputTokens, since].every(Number.isFinite) ||
146
+ count < 0 ||
147
+ inputTokens < 0 ||
148
+ outputTokens < 0 ||
149
+ since < 0
150
+ ) {
151
+ return undefined
152
+ }
153
+ return {
154
+ count: Math.floor(count),
155
+ inputTokens: Math.floor(inputTokens),
156
+ outputTokens: Math.floor(outputTokens),
157
+ since: Math.floor(since),
158
+ }
159
+ }
160
+
161
+ function normalizePrimeAccount(
162
+ value: unknown,
163
+ ): PrimeSidebarAccountState | undefined {
164
+ if (!isRecord(value)) return undefined
165
+ if (typeof value.id !== 'string' || !value.id.trim()) return undefined
166
+ if (typeof value.label !== 'string' || !value.label.trim()) return undefined
167
+ const account: PrimeSidebarAccountState = {
168
+ id: value.id.trim(),
169
+ label: value.label.trim(),
170
+ }
171
+ if (value.nextDueAt === null) {
172
+ account.nextDueAt = null
173
+ } else if (isFiniteNumber(value.nextDueAt)) {
174
+ account.nextDueAt = value.nextDueAt
175
+ }
176
+ if (value.lastPrimedAt === null) {
177
+ account.lastPrimedAt = null
178
+ } else if (isFiniteNumber(value.lastPrimedAt)) {
179
+ account.lastPrimedAt = value.lastPrimedAt
180
+ }
181
+ if (value.lastResult === 'ok' || value.lastResult === 'error') {
182
+ account.lastResult = value.lastResult
183
+ }
184
+ const usage = normalizePrimeUsage(value.usage)
185
+ if (usage) account.usage = usage
186
+ if (isFiniteNumber(value.estimatedCostUsd)) {
187
+ account.estimatedCostUsd = value.estimatedCostUsd
188
+ }
189
+ return account
190
+ }
191
+
192
+ function normalizePrimeSection(
193
+ value: unknown,
194
+ ): SidebarState['prime'] | undefined {
195
+ if (!isRecord(value)) return undefined
196
+ if (typeof value.enabled !== 'boolean') return undefined
197
+ const accounts = Array.isArray(value.accounts)
198
+ ? value.accounts
199
+ .map(normalizePrimeAccount)
200
+ .filter((a): a is PrimeSidebarAccountState => a != null)
201
+ : []
202
+ return { enabled: value.enabled, accounts }
203
+ }
204
+
114
205
  function normalizeQuotaWindow(value: unknown): QuotaWindow | undefined {
115
206
  if (!isRecord(value)) return undefined
116
207
  const usedPercent = Number(value.usedPercent)
@@ -322,6 +413,7 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
322
413
  ? raw.fastMode
323
414
  : DEFAULT_SIDEBAR_STATE.fastMode,
324
415
  cacheKeep,
416
+ prime: normalizePrimeSection(raw.prime),
325
417
  fableRecoveries: fableRecoveries.length > 0 ? fableRecoveries : undefined,
326
418
  lastUpdated: typeof raw.lastUpdated === 'number' ? raw.lastUpdated : 0,
327
419
  }
@@ -348,12 +440,20 @@ interface SidebarStateWriteTestHooks {
348
440
  lockRetryMaxMs?: number
349
441
  }
350
442
 
351
- let sidebarStateWriteTestHooks: SidebarStateWriteTestHooks | null = null
443
+ const sidebarStateWriteTestHooks = new Map<string, SidebarStateWriteTestHooks>()
352
444
 
353
445
  export function __setSidebarStateWriteTestHooks(
354
446
  hooks: SidebarStateWriteTestHooks | null,
447
+ stateFile = getSidebarStateFile(),
355
448
  ): void {
356
- sidebarStateWriteTestHooks = hooks
449
+ if (hooks) sidebarStateWriteTestHooks.set(stateFile, hooks)
450
+ else sidebarStateWriteTestHooks.delete(stateFile)
451
+ }
452
+
453
+ function sidebarStateWriteHooksFor(
454
+ stateFile: string,
455
+ ): SidebarStateWriteTestHooks | undefined {
456
+ return sidebarStateWriteTestHooks.get(stateFile)
357
457
  }
358
458
 
359
459
  // Boot-routing test hooks live here rather than in index.ts: the plugin entry
@@ -388,12 +488,10 @@ async function acquireSidebarStateLock(stateFile: string): Promise<{
388
488
  ownsLock: () => Promise<boolean>
389
489
  } | null> {
390
490
  const lockDir = `${stateFile}.lock`
391
- const budgetMs =
392
- sidebarStateWriteTestHooks?.lockBudgetMs ?? SIDEBAR_LOCK_BUDGET_MS
393
- const retryMinMs =
394
- sidebarStateWriteTestHooks?.lockRetryMinMs ?? SIDEBAR_LOCK_RETRY_MIN_MS
395
- const retryMaxMs =
396
- sidebarStateWriteTestHooks?.lockRetryMaxMs ?? SIDEBAR_LOCK_RETRY_MAX_MS
491
+ const testHooks = sidebarStateWriteHooksFor(stateFile)
492
+ const budgetMs = testHooks?.lockBudgetMs ?? SIDEBAR_LOCK_BUDGET_MS
493
+ const retryMinMs = testHooks?.lockRetryMinMs ?? SIDEBAR_LOCK_RETRY_MIN_MS
494
+ const retryMaxMs = testHooks?.lockRetryMaxMs ?? SIDEBAR_LOCK_RETRY_MAX_MS
397
495
  const deadline = Date.now() + budgetMs
398
496
 
399
497
  while (true) {
@@ -406,7 +504,7 @@ async function acquireSidebarStateLock(stateFile: string): Promise<{
406
504
  mode: 0o600,
407
505
  flag: 'wx',
408
506
  })
409
- await sidebarStateWriteTestHooks?.onLockAcquired?.(lockDir)
507
+ await testHooks?.onLockAcquired?.(lockDir)
410
508
  return {
411
509
  ownsLock: async () => {
412
510
  try {
@@ -424,9 +522,7 @@ async function acquireSidebarStateLock(stateFile: string): Promise<{
424
522
  } catch {
425
523
  return
426
524
  }
427
- await sidebarStateWriteTestHooks?.beforeReleaseDirectoryRemoval?.(
428
- lockDir,
429
- )
525
+ await testHooks?.beforeReleaseDirectoryRemoval?.(lockDir)
430
526
  await rmdir(lockDir).catch(() => {})
431
527
  },
432
528
  }
@@ -443,7 +539,7 @@ async function acquireSidebarStateLock(stateFile: string): Promise<{
443
539
  try {
444
540
  const lockStat = await stat(lockDir)
445
541
  if (Date.now() - lockStat.mtimeMs > SIDEBAR_LOCK_STALE_MS) {
446
- await sidebarStateWriteTestHooks?.afterStaleLockStat?.(lockDir)
542
+ await testHooks?.afterStaleLockStat?.(lockDir)
447
543
  const evictedLockDir = `${lockDir}.evict-${process.pid}-${randomUUID()}`
448
544
  try {
449
545
  await rename(lockDir, evictedLockDir)
@@ -451,7 +547,7 @@ async function acquireSidebarStateLock(stateFile: string): Promise<{
451
547
  if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue
452
548
  throw error
453
549
  }
454
- await sidebarStateWriteTestHooks?.onStaleLockClaimed?.(lockDir)
550
+ await testHooks?.onStaleLockClaimed?.(lockDir)
455
551
  await rm(evictedLockDir, { recursive: true, force: true }).catch(
456
552
  () => {},
457
553
  )
@@ -491,6 +587,7 @@ async function writeSidebarStateAtomic(
491
587
  state: SidebarState,
492
588
  ownsLock: () => Promise<boolean>,
493
589
  ): Promise<'written' | 'lock-lost-before-rename' | 'lock-lost-after-rename'> {
590
+ const testHooks = sidebarStateWriteHooksFor(stateFile)
494
591
  const tempFile = `${stateFile}.${process.pid}.${randomUUID()}.tmp`
495
592
  await writeFile(tempFile, JSON.stringify(state), {
496
593
  encoding: 'utf8',
@@ -504,7 +601,7 @@ async function writeSidebarStateAtomic(
504
601
  await rm(tempFile, { force: true }).catch(() => {})
505
602
  return 'lock-lost-before-rename'
506
603
  }
507
- await sidebarStateWriteTestHooks?.beforeRename?.(stateFile, tempFile)
604
+ await testHooks?.beforeRename?.(stateFile, tempFile)
508
605
  // No production await separates this ownership fence from rename. A process
509
606
  // freeze between the adjacent syscalls remains possible, so rename is also
510
607
  // fenced from the other side below.
@@ -516,7 +613,7 @@ async function writeSidebarStateAtomic(
516
613
  return 'lock-lost-before-rename'
517
614
  }
518
615
  await rename(tempFile, stateFile)
519
- await sidebarStateWriteTestHooks?.afterRename?.(stateFile)
616
+ await testHooks?.afterRename?.(stateFile)
520
617
  if (!(await ownsLock())) return 'lock-lost-after-rename'
521
618
  return 'written'
522
619
  } catch (error) {
@@ -601,7 +698,9 @@ export async function setSidebarState(
601
698
  ),
602
699
  }
603
700
  }
604
- await sidebarStateWriteTestHooks?.afterMergeRead?.(stateFile)
701
+ await sidebarStateWriteHooksFor(stateFile)?.afterMergeRead?.(
702
+ stateFile,
703
+ )
605
704
  } else if (repairingPostRenameLoss) {
606
705
  const current = await getSidebarState(stateFile)
607
706
  // A successor owns its fresh account/quota and recovery snapshots;
@@ -710,6 +809,44 @@ export function formatFallbackModelLabel(modelId: string | undefined): string {
710
809
  return modelId ?? 'Fable 5'
711
810
  }
712
811
 
812
+ export function formatPrimeTime(epochMs: number): string {
813
+ return new Date(epochMs).toLocaleTimeString([], {
814
+ hour: '2-digit',
815
+ minute: '2-digit',
816
+ })
817
+ }
818
+
819
+ export function formatPrimeCost(value: number): string {
820
+ if (value === 0) return '0'
821
+ if (value < 0.0001) return value.toExponential(2)
822
+ return value.toFixed(Math.min(6, Math.max(0, 4)))
823
+ }
824
+
825
+ export function formatPrimeAccountValue(account: PrimeSidebarAccountState): {
826
+ text: string
827
+ hasError: boolean
828
+ } {
829
+ if (account.lastResult === 'error') {
830
+ return { text: 'err', hasError: true }
831
+ }
832
+ if (account.nextDueAt && account.nextDueAt > Date.now()) {
833
+ return { text: formatPrimeTime(account.nextDueAt), hasError: false }
834
+ }
835
+ if (account.lastPrimedAt) {
836
+ return {
837
+ text: `primed ${formatPrimeTime(account.lastPrimedAt)} \u2713`,
838
+ hasError: false,
839
+ }
840
+ }
841
+ if (account.usage?.count) {
842
+ return {
843
+ text: `\u2713 ${account.usage.count} \u2248 $${formatPrimeCost(account.estimatedCostUsd ?? 0)}`,
844
+ hasError: false,
845
+ }
846
+ }
847
+ return { text: '\u2014', hasError: false }
848
+ }
849
+
713
850
  export function getFableRecoverySummary(
714
851
  state: SidebarState,
715
852
  sessionId: string,