@cortexkit/opencode-openai-auth 0.1.2 → 0.1.4

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 (63) hide show
  1. package/README.md +27 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +14 -0
  4. package/dist/commands.d.ts +49 -0
  5. package/dist/core/accounts.d.ts +227 -0
  6. package/dist/core/atomic-write.d.ts +1 -0
  7. package/dist/core/backoff.d.ts +53 -0
  8. package/dist/core/cachekeep.d.ts +95 -0
  9. package/dist/core/oauth.d.ts +126 -0
  10. package/dist/core/provider.d.ts +59 -0
  11. package/dist/core/quota-manager.d.ts +136 -0
  12. package/dist/core/refresh-all-quota.d.ts +52 -0
  13. package/dist/core/refresh-file-lock.d.ts +15 -0
  14. package/dist/index-0s5tftne.js +3 -0
  15. package/dist/index-6qjg8qfw.js +2 -0
  16. package/dist/index-dz7tcrxb.js +2 -0
  17. package/dist/index-eks9xm6s.js +1 -0
  18. package/dist/index-hrg6bdgb.js +2 -0
  19. package/dist/index-jshhys8t.js +2 -0
  20. package/dist/index-m9dtkj3q.js +92 -0
  21. package/dist/index-sgfft5d3.js +2 -0
  22. package/dist/index.d.ts +3 -19
  23. package/dist/index.js +118 -124
  24. package/dist/logger.d.ts +11 -1
  25. package/dist/quota-normalize-qh5n0p7v.js +1 -0
  26. package/dist/quota-normalize.d.ts +33 -0
  27. package/dist/rpc/notifications.d.ts +5 -0
  28. package/dist/rpc/port-file.d.ts +12 -0
  29. package/dist/rpc/port-file.js +1 -0
  30. package/dist/rpc/protocol.d.ts +20 -0
  31. package/dist/rpc/protocol.js +0 -0
  32. package/dist/rpc/rpc-client.d.ts +7 -0
  33. package/dist/rpc/rpc-client.js +1 -0
  34. package/dist/rpc/rpc-dir.d.ts +3 -0
  35. package/dist/rpc/rpc-dir.js +1 -0
  36. package/dist/rpc/rpc-server.d.ts +13 -0
  37. package/dist/sidebar-state.d.ts +83 -0
  38. package/dist/sidebar-state.js +1 -0
  39. package/dist/token-util-sqwtzxz0.js +1 -0
  40. package/dist/token-zzzp7w2y.js +1 -0
  41. package/dist/tui/command-dialogs.d.ts +14 -0
  42. package/dist/tui-preferences.d.ts +40 -0
  43. package/dist/tui-preferences.js +6 -0
  44. package/dist/tui.d.ts +9 -0
  45. package/dist/util/open-url.d.ts +6 -0
  46. package/dist/version.d.ts +1 -1
  47. package/dist/ws-pool.d.ts +8 -0
  48. package/dist/ws.d.ts +4 -0
  49. package/package.json +25 -4
  50. package/src/rpc/notifications.ts +54 -0
  51. package/src/rpc/port-file.ts +70 -0
  52. package/src/rpc/protocol.ts +31 -0
  53. package/src/rpc/rpc-client.ts +78 -0
  54. package/src/rpc/rpc-dir.ts +22 -0
  55. package/src/rpc/rpc-server.ts +129 -0
  56. package/src/sidebar-state.ts +312 -0
  57. package/src/tui/command-dialogs.tsx +742 -0
  58. package/src/tui-preferences.ts +328 -0
  59. package/src/tui.tsx +837 -0
  60. package/dist/index-6z8w05g9.js +0 -2
  61. package/dist/index-xqe8xm96.js +0 -2
  62. package/dist/token-util-fs3z8kkp.js +0 -1
  63. package/dist/token-yyjm093c.js +0 -1
package/dist/logger.d.ts CHANGED
@@ -1 +1,11 @@
1
- export declare function log(message: string, data?: unknown): void;
1
+ type Level = 'error' | 'warn' | 'info' | 'debug' | 'trace';
2
+ export declare function setLogLevel(l: Level | undefined): void;
3
+ export declare function createLogger(channel: string): {
4
+ error: (m: string, d?: unknown) => void;
5
+ warn: (m: string, d?: unknown) => void;
6
+ info: (m: string, d?: unknown) => void;
7
+ debug: (m: string, d?: unknown) => void;
8
+ trace: (m: string, d?: unknown) => void;
9
+ };
10
+ export declare function flushForTest(): Promise<void>;
11
+ export {};
@@ -0,0 +1 @@
1
+ import{c as a,d as b,e as c,f as d}from"./index-sgfft5d3.js";import"./index-hrg6bdgb.js";export{a as toResetIso,c as normalizeWsFrame,d as normalizeWham,b as normalizeQuotaHeaders};
@@ -0,0 +1,33 @@
1
+ import type { OAuthQuotaSnapshot } from './core/accounts.ts';
2
+ export declare function toResetIso(raw: string | number | undefined): string | undefined;
3
+ export declare function normalizeQuotaHeaders(h: Headers): OAuthQuotaSnapshot;
4
+ interface WsRateLimitWindow {
5
+ used_percent: number;
6
+ window_minutes: number;
7
+ reset_at?: string | number;
8
+ }
9
+ interface WsRateLimits {
10
+ primary?: WsRateLimitWindow;
11
+ secondary?: WsRateLimitWindow;
12
+ }
13
+ interface WsRateLimitsFrame {
14
+ type: string;
15
+ rate_limits: WsRateLimits;
16
+ plan_type?: string;
17
+ }
18
+ export declare function normalizeWsFrame(event: WsRateLimitsFrame): OAuthQuotaSnapshot;
19
+ interface WhamRateLimitWindow {
20
+ used_percent: number;
21
+ limit_window_seconds: number;
22
+ reset_at?: string | number;
23
+ }
24
+ interface WhamRateLimits {
25
+ primary_window?: WhamRateLimitWindow;
26
+ secondary_window?: WhamRateLimitWindow;
27
+ }
28
+ interface WhamUsageResponse {
29
+ plan_type?: string;
30
+ rate_limit: WhamRateLimits;
31
+ }
32
+ export declare function normalizeWham(json: WhamUsageResponse): OAuthQuotaSnapshot;
33
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { OpenDialogPayload, RpcNotification } from './protocol';
2
+ export declare function pushNotification(payload: OpenDialogPayload, sessionId?: string): void;
3
+ export declare function drainNotifications(lastReceivedId?: number, sessionId?: string): RpcNotification[];
4
+ export declare function isTuiConnected(sessionId?: string): boolean;
5
+ export declare function resetNotificationsForTest(): void;
@@ -0,0 +1,12 @@
1
+ export interface PortFileEntry {
2
+ port: number;
3
+ token: string;
4
+ pid: number;
5
+ startedAt: number;
6
+ }
7
+ export declare function writePortFile(dir: string, entry: {
8
+ port: number;
9
+ token: string;
10
+ pid: number;
11
+ }): Promise<string>;
12
+ export declare function discoverPortFile(dir: string, expectedPid?: number): Promise<PortFileEntry | null>;
@@ -0,0 +1 @@
1
+ import{Z as a,_ as b}from"../index-dz7tcrxb.js";import"../index-hrg6bdgb.js";export{a as writePortFile,b as discoverPortFile};
@@ -0,0 +1,20 @@
1
+ export type CommandModalName = 'openai-quota' | 'openai-account' | 'openai-routing' | 'openai-killswitch' | 'openai-dump' | 'openai-logging' | 'openai-cachekeep';
2
+ export interface OpenDialogPayload {
3
+ command: CommandModalName;
4
+ text: string;
5
+ knobs: Record<string, unknown>;
6
+ }
7
+ export interface RpcNotification {
8
+ id: number;
9
+ type: 'open-dialog';
10
+ payload: OpenDialogPayload;
11
+ sessionId?: string;
12
+ }
13
+ export interface ApplyRequest {
14
+ command: CommandModalName;
15
+ arguments: string;
16
+ }
17
+ export interface ApplyResult {
18
+ text: string;
19
+ knobs: Record<string, unknown>;
20
+ }
File without changes
@@ -0,0 +1,7 @@
1
+ import { type PortFileEntry } from './port-file';
2
+ import type { ApplyRequest, ApplyResult, RpcNotification } from './protocol';
3
+ export interface RpcClient {
4
+ pending: (lastReceivedId: number, sessionId?: string) => Promise<RpcNotification[]>;
5
+ apply: (request: ApplyRequest) => Promise<ApplyResult>;
6
+ }
7
+ export declare function createRpcClient(dir: string, expectedPid?: number, onSelected?: (entry: PortFileEntry | null) => void): RpcClient;
@@ -0,0 +1 @@
1
+ import{_ as c}from"../index-dz7tcrxb.js";import"../index-hrg6bdgb.js";async function p(n,r,l,o,i){let t=await c(n,r);if(l?.(t),!t)return null;let e=new AbortController,s=setTimeout(()=>e.abort(),2000);try{let a=await fetch(`http://127.0.0.1:${t.port}/rpc/${o}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${t.token}`},body:JSON.stringify(i),signal:e.signal});if(!a.ok)return null;return await a.json()}catch{return null}finally{clearTimeout(s)}}function y(n,r,l){let o=!1,i=(t)=>{if(o)return;o=!0,l?.(t)};return{async pending(t,e){return(await p(n,r,i,"pending-notifications",{lastReceivedId:t,sessionId:e}))?.messages??[]},async apply(t){return await p(n,r,i,"apply",{...t})??{text:"apply failed",knobs:{}}}}}export{y as createRpcClient};
@@ -0,0 +1,3 @@
1
+ import { tmpdir } from 'node:os';
2
+ export declare function getRpcDir(projectDirectory: string): string;
3
+ export { tmpdir };
@@ -0,0 +1 @@
1
+ import{$ as a,aa as b}from"../index-eks9xm6s.js";import"../index-hrg6bdgb.js";export{a as tmpdir,b as getRpcDir};
@@ -0,0 +1,13 @@
1
+ import type { drainNotifications } from './notifications';
2
+ import type { ApplyRequest, ApplyResult } from './protocol';
3
+ export interface RpcServerHandle {
4
+ port: number;
5
+ token: string;
6
+ stop: () => Promise<void>;
7
+ }
8
+ export interface RpcServerOptions {
9
+ dir: string;
10
+ drain: typeof drainNotifications;
11
+ apply: (request: ApplyRequest) => Promise<ApplyResult>;
12
+ }
13
+ export declare function startRpcServer(options: RpcServerOptions): Promise<RpcServerHandle>;
@@ -0,0 +1,83 @@
1
+ export interface QuotaWindow {
2
+ usedPercent: number;
3
+ remainingPercent: number;
4
+ resetsAt?: string;
5
+ }
6
+ export interface AccountQuota {
7
+ primary?: QuotaWindow;
8
+ secondary?: QuotaWindow;
9
+ }
10
+ export interface SidebarAccountState {
11
+ id: string;
12
+ label: string | undefined;
13
+ quota: AccountQuota | null;
14
+ killed: boolean;
15
+ enabled: boolean;
16
+ }
17
+ export interface SidebarState {
18
+ main: {
19
+ quota: AccountQuota | null;
20
+ killed: boolean;
21
+ quotaBackedOff?: boolean;
22
+ quotaBackoffUntil?: number;
23
+ refreshBackedOff?: boolean;
24
+ refreshBackoffUntil?: number;
25
+ };
26
+ fallbacks: SidebarAccountState[];
27
+ activeId: string | undefined;
28
+ route: string;
29
+ planType?: string;
30
+ credits?: number;
31
+ lastUpdated: number;
32
+ }
33
+ export declare function getSidebarStateFile(): string;
34
+ export declare const DEFAULT_SIDEBAR_STATE: SidebarState;
35
+ /**
36
+ * Normalize an arbitrary parsed value into a well-formed SidebarState.
37
+ *
38
+ * JSON.parse + `as SidebarState` is an unchecked cast — a partial, old, or
39
+ * malformed state file passes through and the TUI's `state().main.quota` /
40
+ * `state().fallbacks.filter(...)` throw at runtime. This helper guarantees
41
+ * every required field is present and correctly typed before the value leaves
42
+ * the I/O boundary, so a bad file can never crash the host TUI.
43
+ */
44
+ export declare function normalizeSidebarState(raw: unknown): SidebarState;
45
+ export declare function getSidebarState(): Promise<SidebarState>;
46
+ /**
47
+ * Write sidebar state to disk, serialized through a promise chain so
48
+ * concurrent callers never interleave or let a stale write land last.
49
+ *
50
+ * @param state The state to persist.
51
+ * @param file Explicit path override — callers that bind the path at init
52
+ * time (e.g. the index.ts loader) pass this so late callbacks
53
+ * always write to the path that was current when the loader ran,
54
+ * even if the env changes underneath them during tests.
55
+ * Defaults to getSidebarStateFile() (per-call resolution).
56
+ */
57
+ export declare function setSidebarState(state: SidebarState, file?: string): Promise<void>;
58
+ /**
59
+ * Await all pending sidebar writes. Tests call this before restoring env
60
+ * vars in teardown so no in-flight write can re-resolve getSidebarStateFile()
61
+ * after the env is changed.
62
+ */
63
+ export declare function drainSidebarWrites(): Promise<void>;
64
+ export declare function resolveActiveAccount(state: SidebarState): {
65
+ id: string;
66
+ name: string;
67
+ quota: AccountQuota | null;
68
+ killed: boolean;
69
+ };
70
+ export declare function getCollapsedQuotaSummary(quota: AccountQuota | null): {
71
+ primaryUsedPercent: number | null;
72
+ secondaryUsedPercent: number | null;
73
+ text: string | null;
74
+ };
75
+ export declare const FIVE_HOUR_MS: number;
76
+ export declare const SEVEN_DAY_MS: number;
77
+ export interface QuotaPacing {
78
+ pacePercent: number;
79
+ deltaPercent: number;
80
+ state: 'deficit' | 'reserve' | 'on-pace';
81
+ runsOutAt: string | null;
82
+ }
83
+ export declare function computeQuotaPacing(window: QuotaWindow, windowMs: number, now: number): QuotaPacing | null;
@@ -0,0 +1 @@
1
+ import{M as a,N as b,O as c,P as d,Q as e,R as f,S as g,T as h,U as i,V as j,W as k}from"./index-jshhys8t.js";import"./index-0s5tftne.js";import"./index-hrg6bdgb.js";export{e as setSidebarState,g as resolveActiveAccount,c as normalizeSidebarState,a as getSidebarStateFile,d as getSidebarState,h as getCollapsedQuotaSummary,f as drainSidebarWrites,k as computeQuotaPacing,j as SEVEN_DAY_MS,i as FIVE_HOUR_MS,b as DEFAULT_SIDEBAR_STATE};
@@ -0,0 +1 @@
1
+ import{b as a}from"./index-6qjg8qfw.js";import"./index-hrg6bdgb.js";export default a();
@@ -0,0 +1 @@
1
+ import{a as U,b as V}from"./index-6qjg8qfw.js";import{ca as R}from"./index-hrg6bdgb.js";var E=R((M,Q)=>{var{defineProperty:K,getOwnPropertyDescriptor:W,getOwnPropertyNames:X}=Object,Y=Object.prototype.hasOwnProperty,Z=(q,v)=>{for(var z in v)K(q,z,{get:v[z],enumerable:!0})},$=(q,v,z,F)=>{if(v&&typeof v==="object"||typeof v==="function"){for(let G of X(v))if(!Y.call(q,G)&&G!==z)K(q,G,{get:()=>v[G],enumerable:!(F=W(v,G))||F.enumerable})}return q},A=(q)=>$(K({},"__esModule",{value:!0}),q),L={};Z(L,{refreshToken:()=>D});Q.exports=A(L);var H=U(),B=V();async function D(){let{projectId:q,teamId:v}=(0,B.findProjectInfo)(),z=(0,B.loadToken)(q);if(!z||(0,B.isExpired)((0,B.getTokenPayload)(z.token))){let F=await(0,B.getVercelCliToken)();if(!F)throw new H.VercelOidcTokenError("Failed to refresh OIDC token: Log in to Vercel CLI and link your project with `vc link`");if(!q)throw new H.VercelOidcTokenError("Failed to refresh OIDC token: Try re-linking your project with `vc link`");if(z=await(0,B.getVercelOidcToken)(F,q,v),!z)throw new H.VercelOidcTokenError("Failed to refresh OIDC token");(0,B.saveToken)(z,q)}process.env.VERCEL_OIDC_TOKEN=z.token;return}});export default E();
@@ -0,0 +1,14 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { TuiPluginApi } from '@opencode-ai/plugin/tui';
3
+ import type { OpenDialogPayload } from '../rpc/protocol.js';
4
+ type ApplyFn = (command: OpenDialogPayload['command'], args: string) => Promise<{
5
+ text: string;
6
+ knobs: Record<string, unknown>;
7
+ }>;
8
+ export declare function buildCachekeepDialogOptions(payload: OpenDialogPayload): {
9
+ title: string;
10
+ value: string;
11
+ description: string;
12
+ }[];
13
+ export declare function openCommandDialog(api: TuiPluginApi, payload: OpenDialogPayload, apply: ApplyFn): void;
14
+ export {};
@@ -0,0 +1,40 @@
1
+ export declare const TUI_PREFS_FILE_ENV = "OPENCODE_TUI_PREFERENCES_FILE";
2
+ export declare function getTuiPreferencesFile(): string;
3
+ export declare function readTuiPreferencesFile(): Promise<Record<string, unknown>>;
4
+ export declare const PLUGIN_KEY = "openai-auth";
5
+ export declare const DEFAULT_SLOT_ORDER = 160;
6
+ export interface OpenaiAuthTuiPrefs {
7
+ forceToTop: boolean;
8
+ order: number;
9
+ startCollapsed: boolean;
10
+ rememberCollapsed: boolean;
11
+ collapsed: boolean | null;
12
+ pollMs: number;
13
+ refreshDebounceMs: number;
14
+ header: {
15
+ label: string;
16
+ showVersion: boolean;
17
+ };
18
+ sections: {
19
+ quota: boolean;
20
+ fallbackAccounts: boolean;
21
+ routing: boolean;
22
+ health: boolean;
23
+ pacing: boolean;
24
+ };
25
+ appearance: {
26
+ barWidth: number;
27
+ barFilledChar: string;
28
+ barEmptyChar: string;
29
+ warnThreshold: number;
30
+ errorThreshold: number;
31
+ };
32
+ }
33
+ export type AppearancePrefs = OpenaiAuthTuiPrefs['appearance'];
34
+ export declare const DEFAULT_PREFS: OpenaiAuthTuiPrefs;
35
+ export declare function resolveOpenaiAuthPrefs(root: Record<string, unknown>): OpenaiAuthTuiPrefs;
36
+ export declare function computeEffectiveOrder(root: Record<string, unknown>, pluginKey: string, defaultOrder: number): number;
37
+ type JsonValue = string | number | boolean | null;
38
+ export declare function queueTuiPreferenceUpdate(pluginKey: string, path: string[], value: JsonValue): Promise<void>;
39
+ export declare function watchTuiPreferences(onChange: () => void): () => void;
40
+ export {};
@@ -0,0 +1,6 @@
1
+ import{ba as P,ca as C,ea as _}from"./index-hrg6bdgb.js";var g=C((T,L)=>{(function(Q){if(typeof L==="object"&&typeof L.exports==="object"){var J=Q(_,T);if(J!==void 0)L.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"),Z=Q("./impl/edit"),G=Q("./impl/scanner"),$=Q("./impl/parser");J.createScanner=G.createScanner;var W;(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"})(W||(J.ScanError=W={}));var H;(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"})(H||(J.SyntaxKind=H={})),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 q;(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"})(q||(J.ParseErrorCode=q={}));function D(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=D;function m(z,U,j){return X.format(z,U,j)}J.format=m;function f(z,U,j,A){return Z.setProperty(z,U,j,A)}J.modify=f;function u(z,U){let j=U.slice(0).sort((I,Y)=>{let F=I.offset-Y.offset;if(F===0)return I.length-Y.length;return F}),A=z.length;for(let I=j.length-1;I>=0;I--){let Y=j[I];if(Y.offset+Y.length<=A)z=Z.applyEdit(z,Y);else throw Error("Overlapping edit");A=Y.offset}return z}J.applyEdits=u})});var M=P(g(),1);import{watch as K}from"node:fs";import{mkdir as c,readFile as O,rename as y,writeFile as S}from"node:fs/promises";import{homedir as E}from"node:os";import{basename as l,dirname as v,join as k}from"node:path";var p="OPENCODE_TUI_PREFERENCES_FILE",x="tui-preferences.jsonc";function w(){let Q=process.env[p];if(Q)return Q;let J=process.env.OPENCODE_CONFIG_DIR||k(process.env.XDG_CONFIG_HOME||k(E(),".config"),"opencode");return k(J,x)}function N(Q){return typeof Q==="object"&&Q!==null&&!Array.isArray(Q)}async function Xz(){try{let Q=await O(w(),"utf8"),J=[],X=M.parse(Q,J,{allowTrailingComma:!0});if(J.length>0)return{};return N(X)?X:{}}catch{return{}}}var i="openai-auth",s=160,b={forceToTop:!1,order:s,startCollapsed:!1,rememberCollapsed:!0,collapsed:null,pollMs:1500,refreshDebounceMs:200,header:{label:"OPENAI",showVersion:!0},sections:{quota:!0,fallbackAccounts:!0,routing:!0,health:!0,pacing:!0},appearance:{barWidth:10,barFilledChar:"█",barEmptyChar:"░",warnThreshold:50,errorThreshold:80}};function V(Q,J){return typeof Q==="boolean"?Q:J}function B(Q,J,X,Z){if(typeof Q!=="number"||!Number.isFinite(Q))return J;return Math.min(Math.max(Math.round(Q),X),Z)}function d(Q,J,X){if(typeof Q!=="string"||Q.length===0)return J;return Q.slice(0,X)}function h(Q,J){if(typeof Q!=="string")return J;return[...Q][0]??J}function Zz(Q){let J=Q[i];if(!N(J))return structuredClone(b);let X=b,Z=N(J.header)?J.header:{},G=N(J.sections)?J.sections:{},$=N(J.appearance)?J.appearance:{},W=B($.warnThreshold,X.appearance.warnThreshold,0,99),H=Math.max(B($.errorThreshold,X.appearance.errorThreshold,0,100),Math.min(W+1,100));return{forceToTop:V(J.forceToTop,X.forceToTop),order:B(J.order,X.order,-1e4,1e4),startCollapsed:V(J.startCollapsed,X.startCollapsed),rememberCollapsed:V(J.rememberCollapsed,X.rememberCollapsed),collapsed:typeof J.collapsed==="boolean"?J.collapsed:null,pollMs:B(J.pollMs,X.pollMs,500,30000),refreshDebounceMs:B(J.refreshDebounceMs,X.refreshDebounceMs,50,5000),header:{label:d(Z.label,X.header.label,20),showVersion:V(Z.showVersion,X.header.showVersion)},sections:{quota:V(G.quota,X.sections.quota),fallbackAccounts:V(G.fallbackAccounts,X.sections.fallbackAccounts),routing:V(G.routing,X.sections.routing),health:V(G.health,X.sections.health),pacing:V(G.pacing,X.sections.pacing)},appearance:{barWidth:B($.barWidth,X.appearance.barWidth,4,40),barFilledChar:h($.barFilledChar,X.appearance.barFilledChar),barEmptyChar:h($.barEmptyChar,X.appearance.barEmptyChar),warnThreshold:W,errorThreshold:H}}}var n=-1e5;function $z(Q,J,X){let Z=Q[J];if(!N(Z))return X;if(Z.forceToTop===!0)return n+Object.keys(Q).indexOf(J);return B(Z.order,X,-1e4,1e4)}var t=`// Shared preferences for opencode TUI plugins.
2
+ // One top-level key per plugin (short name). See each plugin's README for
3
+ // its supported settings. This file is safe to hand-edit; plugins update
4
+ // individual keys in place and preserve comments.
5
+ {}
6
+ `;async function a(Q,J,X){let Z=w();await c(v(Z),{recursive:!0});let G;try{G=await O(Z,"utf8")}catch{G=""}if(G.trim()==="")G=t;let $=M.modify(G,[Q,...J],X,{formattingOptions:{insertSpaces:!0,tabSize:2}}),W=M.applyEdits(G,$),H=`${Z}.${process.pid}.tmp`;await S(H,W,"utf8"),await y(H,Z)}var R=Promise.resolve();function Gz(Q,J,X){return R=R.then(()=>a(Q,J,X)).catch(()=>{}),R}var o=150;function Hz(Q){let J=w(),X=l(J),Z=null,G=null;O(J,"utf8").then(($)=>{if(G===null)G=$}).catch(()=>{});try{let $=K(v(J),(W,H)=>{let q=H===X||H?.startsWith(`${X}.`)&&H.endsWith(".tmp");if(H!=null&&!q)return;if(Z)clearTimeout(Z);Z=setTimeout(()=>{Z=null,O(J,"utf8").catch(()=>null).then((D)=>{if(D===null)return;if(D===G)return;G=D,Q()})},o)});return()=>{if(Z)clearTimeout(Z);$.close()}}catch{return()=>{}}}export{Hz as watchTuiPreferences,Zz as resolveOpenaiAuthPrefs,Xz as readTuiPreferencesFile,Gz as queueTuiPreferenceUpdate,w as getTuiPreferencesFile,$z as computeEffectiveOrder,p as TUI_PREFS_FILE_ENV,i as PLUGIN_KEY,s as DEFAULT_SLOT_ORDER,b as DEFAULT_PREFS};
package/dist/tui.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { TuiPluginModule } from '@opencode-ai/plugin/tui';
3
+ import { type SidebarState } from './sidebar-state.js';
4
+ export declare function formatResetIn(resetsAt: string | undefined): string;
5
+ export declare function readStateFromFile(): Promise<SidebarState>;
6
+ declare const plugin: TuiPluginModule & {
7
+ id: string;
8
+ };
9
+ export default plugin;
@@ -0,0 +1,6 @@
1
+ type BrowserExec = (file: string, args: string[], options: {
2
+ stdio: 'ignore';
3
+ timeout: number;
4
+ }) => unknown;
5
+ export declare function openUrl(url: string, platform?: NodeJS.Platform, execFileSync?: BrowserExec): void;
6
+ export {};
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const PackageVersion = "0.1.2";
1
+ export declare const PackageVersion = "0.1.4";
package/dist/ws-pool.d.ts CHANGED
@@ -10,6 +10,14 @@ export interface CreateWebSocketFetchOptions {
10
10
  firstEventGraceMs?: number;
11
11
  /** Use the hand-rolled raw TCP/TLS WebSocket client instead of native WebSocket. */
12
12
  rawWebSocket?: boolean;
13
+ /**
14
+ * Push per-turn quota from a codex.rate_limits in-band frame.
15
+ *
16
+ * Receives the snapshot plus the per-request account identity (access token
17
+ * and accountId) captured at send time so the frame is attributed to the
18
+ * connection's own account, not a shared mutable global.
19
+ */
20
+ onQuota?: (s: Record<string, unknown>, accessToken: string, accountId: string | undefined) => void;
13
21
  }
14
22
  interface PoolEntry {
15
23
  socket?: WebSocket;
package/dist/ws.d.ts CHANGED
@@ -20,6 +20,10 @@ export interface StreamResponsesWebSocketOptions {
20
20
  onRetryableTerminal?: (event: Record<string, unknown>) => Promise<WebSocket | undefined>;
21
21
  onConnectionInvalid?: (error: ResponseStreamError) => void;
22
22
  onAbort?: (error: Error) => void;
23
+ /** Push per-turn quota from a codex.rate_limits in-band frame. */
24
+ onQuota?: (s: Record<string, unknown>) => void;
25
+ /** Display+classify only: called when response.failed carries a rate_limit_reached_type. */
26
+ onRateLimitReached?: (window: string) => void;
23
27
  }
24
28
  export interface WrappedError {
25
29
  status: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-openai-auth",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -10,25 +10,42 @@
10
10
  },
11
11
  "main": "./dist/index.js",
12
12
  "types": "./dist/index.d.ts",
13
+ "bin": {
14
+ "openai-auth": "./dist/cli.js"
15
+ },
13
16
  "exports": {
14
17
  ".": {
15
18
  "import": "./dist/index.js",
16
19
  "types": "./dist/index.d.ts"
20
+ },
21
+ "./tui": {
22
+ "types": "./dist/tui.d.ts",
23
+ "import": "./src/tui.tsx"
24
+ },
25
+ "./tui-prefs": {
26
+ "types": "./dist/tui-preferences.d.ts",
27
+ "import": "./dist/tui-preferences.js"
17
28
  }
18
29
  },
19
30
  "oc-plugin": [
20
- "server"
31
+ "server",
32
+ "tui"
21
33
  ],
22
34
  "engines": {
23
35
  "bun": "1.3.14"
24
36
  },
25
37
  "files": [
26
38
  "dist",
39
+ "src/tui.tsx",
40
+ "src/tui/command-dialogs.tsx",
41
+ "src/sidebar-state.ts",
42
+ "src/tui-preferences.ts",
43
+ "src/rpc",
27
44
  "README.md",
28
45
  "LICENSE"
29
46
  ],
30
47
  "scripts": {
31
- "build": "rm -rf dist && bun build src/index.ts --outdir dist --target node --format esm --splitting --external @opencode-ai/plugin --minify && tsc -p tsconfig.build.json --emitDeclarationOnly",
48
+ "build": "rm -rf dist && bun build src/index.ts src/cli.ts src/sidebar-state.ts src/tui-preferences.ts src/rpc/rpc-client.ts src/rpc/port-file.ts src/rpc/protocol.ts src/rpc/rpc-dir.ts --outdir dist --target node --format esm --splitting --external @opencode-ai/plugin --minify && tsc -p tsconfig.build.json --emitDeclarationOnly",
32
49
  "build:dev": "rm -rf dist && tsc -p tsconfig.build.json",
33
50
  "test": "bun test src/tests",
34
51
  "typecheck": "tsc",
@@ -39,6 +56,10 @@
39
56
  "@opencode-ai/plugin": "*"
40
57
  },
41
58
  "dependencies": {
42
- "ai": "^5.0.88"
59
+ "@opentui/core": ">=0.4.0",
60
+ "@opentui/solid": ">=0.4.0",
61
+ "ai": "^5.0.88",
62
+ "jsonc-parser": "^3.3.1",
63
+ "solid-js": "^1.9.10"
43
64
  }
44
65
  }
@@ -0,0 +1,54 @@
1
+ import type { OpenDialogPayload, RpcNotification } from './protocol'
2
+
3
+ const QUEUE_CAP = 100
4
+ const TUI_CONNECTED_WINDOW_MS = 3_000
5
+
6
+ let queue: RpcNotification[] = []
7
+ let nextId = 1
8
+ let lastDrainAtAny = 0
9
+ const lastDrainAtBySession = new Map<string, number>()
10
+
11
+ export function pushNotification(
12
+ payload: OpenDialogPayload,
13
+ sessionId?: string,
14
+ ): void {
15
+ queue.push({ id: nextId++, type: 'open-dialog', payload, sessionId })
16
+ if (queue.length > QUEUE_CAP) queue = queue.slice(queue.length - QUEUE_CAP)
17
+ }
18
+
19
+ export function drainNotifications(
20
+ lastReceivedId = 0,
21
+ sessionId?: string,
22
+ ): RpcNotification[] {
23
+ const now = Date.now()
24
+ lastDrainAtAny = now
25
+ if (sessionId !== undefined) lastDrainAtBySession.set(sessionId, now)
26
+ const matches = (n: RpcNotification) =>
27
+ sessionId === undefined ||
28
+ n.sessionId === undefined ||
29
+ n.sessionId === sessionId
30
+ if (lastReceivedId > 0) {
31
+ queue = queue.filter((n) => {
32
+ if (n.id > lastReceivedId) return true
33
+ if (sessionId === undefined) return false
34
+ return n.sessionId !== sessionId
35
+ })
36
+ }
37
+ return queue.filter((n) => n.id > lastReceivedId && matches(n))
38
+ }
39
+
40
+ export function isTuiConnected(sessionId?: string): boolean {
41
+ const now = Date.now()
42
+ if (sessionId !== undefined) {
43
+ const at = lastDrainAtBySession.get(sessionId) ?? 0
44
+ return at > 0 && now - at < TUI_CONNECTED_WINDOW_MS
45
+ }
46
+ return lastDrainAtAny > 0 && now - lastDrainAtAny < TUI_CONNECTED_WINDOW_MS
47
+ }
48
+
49
+ export function resetNotificationsForTest(): void {
50
+ queue = []
51
+ nextId = 1
52
+ lastDrainAtAny = 0
53
+ lastDrainAtBySession.clear()
54
+ }
@@ -0,0 +1,70 @@
1
+ import {
2
+ mkdir,
3
+ readdir,
4
+ readFile,
5
+ rename,
6
+ unlink,
7
+ writeFile,
8
+ } from 'node:fs/promises'
9
+ import { join } from 'node:path'
10
+
11
+ export interface PortFileEntry {
12
+ port: number
13
+ token: string
14
+ pid: number
15
+ startedAt: number
16
+ }
17
+
18
+ function pidAlive(pid: number): boolean {
19
+ try {
20
+ process.kill(pid, 0)
21
+ return true
22
+ } catch (error) {
23
+ return (error as NodeJS.ErrnoException).code === 'EPERM'
24
+ }
25
+ }
26
+
27
+ export async function writePortFile(
28
+ dir: string,
29
+ entry: { port: number; token: string; pid: number },
30
+ ): Promise<string> {
31
+ await mkdir(dir, { recursive: true })
32
+ const full: PortFileEntry = { ...entry, startedAt: Date.now() }
33
+ const target = join(dir, `port-${entry.pid}.json`)
34
+ const tmp = `${target}.${process.pid}.tmp`
35
+ await writeFile(tmp, JSON.stringify(full), { encoding: 'utf8', mode: 0o600 })
36
+ await rename(tmp, target)
37
+ return target
38
+ }
39
+
40
+ export async function discoverPortFile(
41
+ dir: string,
42
+ expectedPid?: number,
43
+ ): Promise<PortFileEntry | null> {
44
+ let names: string[]
45
+ try {
46
+ names = await readdir(dir)
47
+ } catch {
48
+ return null
49
+ }
50
+ const live: PortFileEntry[] = []
51
+ for (const name of names) {
52
+ if (!name.startsWith('port-') || !name.endsWith('.json')) continue
53
+ try {
54
+ const parsed = JSON.parse(
55
+ await readFile(join(dir, name), 'utf8'),
56
+ ) as PortFileEntry
57
+ if (Number.isFinite(parsed.port)) {
58
+ if (pidAlive(parsed.pid)) live.push(parsed)
59
+ else unlink(join(dir, name)).catch(() => {})
60
+ }
61
+ } catch {}
62
+ }
63
+ if (live.length === 0) return null
64
+ const candidates =
65
+ expectedPid !== undefined && expectedPid >= 1
66
+ ? live.filter((entry) => entry.pid === expectedPid)
67
+ : []
68
+ const entries = candidates.length > 0 ? candidates : live
69
+ return entries.sort((a, b) => b.startedAt - a.startedAt)[0] ?? null
70
+ }
@@ -0,0 +1,31 @@
1
+ export type CommandModalName =
2
+ | 'openai-quota'
3
+ | 'openai-account'
4
+ | 'openai-routing'
5
+ | 'openai-killswitch'
6
+ | 'openai-dump'
7
+ | 'openai-logging'
8
+ | 'openai-cachekeep'
9
+
10
+ export interface OpenDialogPayload {
11
+ command: CommandModalName
12
+ text: string
13
+ knobs: Record<string, unknown>
14
+ }
15
+
16
+ export interface RpcNotification {
17
+ id: number
18
+ type: 'open-dialog'
19
+ payload: OpenDialogPayload
20
+ sessionId?: string
21
+ }
22
+
23
+ export interface ApplyRequest {
24
+ command: CommandModalName
25
+ arguments: string
26
+ }
27
+
28
+ export interface ApplyResult {
29
+ text: string
30
+ knobs: Record<string, unknown>
31
+ }
@@ -0,0 +1,78 @@
1
+ import { discoverPortFile, type PortFileEntry } from './port-file'
2
+ import type { ApplyRequest, ApplyResult, RpcNotification } from './protocol'
3
+
4
+ export interface RpcClient {
5
+ pending: (
6
+ lastReceivedId: number,
7
+ sessionId?: string,
8
+ ) => Promise<RpcNotification[]>
9
+ apply: (request: ApplyRequest) => Promise<ApplyResult>
10
+ }
11
+
12
+ async function call<T>(
13
+ dir: string,
14
+ expectedPid: number | undefined,
15
+ onSelected: ((entry: PortFileEntry | null) => void) | undefined,
16
+ method: string,
17
+ params: Record<string, unknown>,
18
+ ): Promise<T | null> {
19
+ const entry = await discoverPortFile(dir, expectedPid)
20
+ onSelected?.(entry)
21
+ if (!entry) return null
22
+ const controller = new AbortController()
23
+ const timer = setTimeout(() => controller.abort(), 2_000)
24
+ try {
25
+ const res = await fetch(`http://127.0.0.1:${entry.port}/rpc/${method}`, {
26
+ method: 'POST',
27
+ headers: {
28
+ 'content-type': 'application/json',
29
+ authorization: `Bearer ${entry.token}`,
30
+ },
31
+ body: JSON.stringify(params),
32
+ signal: controller.signal,
33
+ })
34
+ if (!res.ok) return null
35
+ return (await res.json()) as T
36
+ } catch {
37
+ return null
38
+ } finally {
39
+ clearTimeout(timer)
40
+ }
41
+ }
42
+
43
+ export function createRpcClient(
44
+ dir: string,
45
+ expectedPid?: number,
46
+ onSelected?: (entry: PortFileEntry | null) => void,
47
+ ): RpcClient {
48
+ let reportedSelection = false
49
+ const reportSelected = (entry: PortFileEntry | null) => {
50
+ if (reportedSelection) return
51
+ reportedSelection = true
52
+ onSelected?.(entry)
53
+ }
54
+ return {
55
+ async pending(lastReceivedId, sessionId) {
56
+ const out = await call<{ messages: RpcNotification[] }>(
57
+ dir,
58
+ expectedPid,
59
+ reportSelected,
60
+ 'pending-notifications',
61
+ { lastReceivedId, sessionId },
62
+ )
63
+ return out?.messages ?? []
64
+ },
65
+ async apply(request) {
66
+ const out = await call<ApplyResult>(
67
+ dir,
68
+ expectedPid,
69
+ reportSelected,
70
+ 'apply',
71
+ {
72
+ ...request,
73
+ },
74
+ )
75
+ return out ?? { text: 'apply failed', knobs: {} }
76
+ },
77
+ }
78
+ }