@cortexkit/opencode-openai-auth 0.3.3 → 0.4.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 (57) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.js +1 -1
  3. package/dist/codex-http.d.ts +3 -0
  4. package/dist/config.d.ts +3 -0
  5. package/dist/core/accounts.d.ts +9 -3
  6. package/dist/core/oauth.d.ts +1 -1
  7. package/dist/core/quota-manager.d.ts +2 -2
  8. package/dist/{index-k5zwf3j1.js → index-1anzb8jh.js} +1 -1
  9. package/dist/index-1r02s323.js +6 -0
  10. package/dist/index-2ng03cby.js +2 -0
  11. package/dist/{index-e7m7qbne.js → index-6y6ss1kc.js} +1 -1
  12. package/dist/{index-1xmasv0x.js → index-b6kp9ydc.js} +1 -1
  13. package/dist/index-cz1bkmjz.js +2 -0
  14. package/dist/index-dzg88nw2.js +89 -0
  15. package/dist/index-y67qn95a.js +2 -0
  16. package/dist/index.d.ts +8 -1
  17. package/dist/index.js +65 -65
  18. package/dist/model-costs.d.ts +29 -0
  19. package/dist/prompt-context.d.ts +1 -1
  20. package/dist/quota-normalize-8yzxqy78.js +1 -0
  21. package/dist/quota-normalize.d.ts +13 -4
  22. package/dist/rpc/port-file.js +1 -1
  23. package/dist/rpc/rpc-client.js +1 -1
  24. package/dist/rpc/rpc-dir.js +1 -1
  25. package/dist/sidebar-state.d.ts +47 -2
  26. package/dist/sidebar-state.js +1 -1
  27. package/dist/token-util-h46jb3eg.js +1 -0
  28. package/dist/token-yzcb1p3e.js +1 -0
  29. package/dist/tui/command-dialogs.d.ts +8 -1
  30. package/dist/tui-preferences.js +1 -1
  31. package/dist/tui.d.ts +18 -1
  32. package/dist/version.d.ts +1 -1
  33. package/dist/ws-pool.d.ts +1 -0
  34. package/package.json +11 -8
  35. package/src/core/refresh-file-lock.ts +293 -0
  36. package/src/sidebar-state.ts +392 -13
  37. package/src/tui/command-dialogs.tsx +43 -25
  38. package/src/tui/entry.mjs +37 -0
  39. package/src/tui-compiled/logger.ts +161 -0
  40. package/src/tui-compiled/rpc/port-file.ts +70 -0
  41. package/src/tui-compiled/rpc/protocol.ts +31 -0
  42. package/src/tui-compiled/rpc/rpc-client.ts +78 -0
  43. package/src/tui-compiled/rpc/rpc-dir.ts +22 -0
  44. package/src/tui-compiled/sidebar-state.ts +695 -0
  45. package/src/tui-compiled/tui/command-dialogs.tsx +645 -0
  46. package/src/tui-compiled/tui-preferences.ts +328 -0
  47. package/src/tui-compiled/tui.tsx +1098 -0
  48. package/src/tui-compiled/util/open-url.ts +30 -0
  49. package/src/tui.tsx +158 -77
  50. package/dist/index-5s67238f.js +0 -2
  51. package/dist/index-d9hq0tmc.js +0 -2
  52. package/dist/index-mrg2fms9.js +0 -2
  53. package/dist/index-phrx93zy.js +0 -92
  54. package/dist/index-qdh7pt5a.js +0 -3
  55. package/dist/quota-normalize-dfdpwwm3.js +0 -1
  56. package/dist/token-01dcf92a.js +0 -1
  57. package/dist/token-util-rkqd82q9.js +0 -1
@@ -0,0 +1,1098 @@
1
+ import { memo as _$memo } from "opentui:runtime-module:%40opentui%2Fsolid";
2
+ import { createTextNode as _$createTextNode } from "opentui:runtime-module:%40opentui%2Fsolid";
3
+ import { createComponent as _$createComponent } from "opentui:runtime-module:%40opentui%2Fsolid";
4
+ import { effect as _$effect } from "opentui:runtime-module:%40opentui%2Fsolid";
5
+ import { insertNode as _$insertNode } from "opentui:runtime-module:%40opentui%2Fsolid";
6
+ import { insert as _$insert } from "opentui:runtime-module:%40opentui%2Fsolid";
7
+ import { setProp as _$setProp } from "opentui:runtime-module:%40opentui%2Fsolid";
8
+ import { createElement as _$createElement } from "opentui:runtime-module:%40opentui%2Fsolid";
9
+ /** @jsxImportSource @opentui/solid */
10
+
11
+ import { readFileSync } from "node:fs";
12
+ import { dirname, join } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { createEffect, createSignal, For, onCleanup, Show } from "opentui:runtime-module:solid-js";
15
+ import { createLogger } from "./logger.js";
16
+ import { createRpcClient } from "./rpc/rpc-client.js";
17
+ import { getRpcDir } from "./rpc/rpc-dir.js";
18
+ import { computeQuotaPacing, DEFAULT_SIDEBAR_STATE, getCollapsedQuotaSummary, getPresentQuotaWindows, getSidebarState, resolveActiveAccount, resolveSessionSidebarRouting } from "./sidebar-state.js";
19
+ import { openCommandDialog } from "./tui/command-dialogs.js";
20
+ import { computeEffectiveOrder, DEFAULT_SLOT_ORDER, PLUGIN_KEY, queueTuiPreferenceUpdate, readTuiPreferencesFile, resolveOpenaiAuthPrefs, watchTuiPreferences } from "./tui-preferences.js";
21
+ const RPC_POLL_MS = 500;
22
+ let rpcPollStarted = false;
23
+ const log = createLogger('rpc-tui');
24
+ const ID = 'cortexkit.openai-auth';
25
+
26
+ // Read package metadata from either the raw src/ entry or its generated
27
+ // src/tui-compiled/ counterpart. Avoid a JSON import because package.json sits
28
+ // outside the declaration build's rootDir.
29
+ const PLUGIN_VERSION = (() => {
30
+ const here = dirname(fileURLToPath(import.meta.url));
31
+ for (const packageFile of [join(here, '..', 'package.json'), join(here, '..', '..', 'package.json')]) {
32
+ try {
33
+ const raw = readFileSync(packageFile, 'utf8');
34
+ const version = JSON.parse(raw).version;
35
+ if (version) return version;
36
+ } catch {
37
+ // Try the path for the other TUI entry layout.
38
+ }
39
+ }
40
+ return '';
41
+ })();
42
+
43
+ // biome-ignore lint/suspicious/noExplicitAny: opentui border prop not typed in plugin tui surface
44
+ const SINGLE_BORDER = {
45
+ type: 'single'
46
+ };
47
+ // Tone -> theme token. Theme tokens only — never hardcoded colors. Fallbacks
48
+ // resolve to other theme tokens so the sidebar always tracks the active theme.
49
+ function toneColor(theme, tone) {
50
+ switch (tone) {
51
+ case 'ok':
52
+ return theme.success ?? theme.accent;
53
+ case 'warn':
54
+ return theme.warning ?? theme.accent;
55
+ case 'err':
56
+ return theme.error ?? theme.accent;
57
+ case 'muted':
58
+ return theme.textMuted ?? theme.text;
59
+ case 'accent':
60
+ return theme.accent ?? theme.text;
61
+ default:
62
+ return theme.text;
63
+ }
64
+ }
65
+ function usageTone(usedPct, appearance) {
66
+ if (usedPct < appearance.warnThreshold) return 'ok';
67
+ if (usedPct < appearance.errorThreshold) return 'warn';
68
+ return 'err';
69
+ }
70
+ const PACE_RESERVE_CHAR = '\u2592';
71
+ const PACE_DEFICIT_CHAR = '\u2593';
72
+
73
+ // Variant C pacing bar: normal fill up to min(used, pace), then a pace
74
+ // segment covering the gap — headroom being banked (reserve, ok tone) or the
75
+ // overshoot itself (deficit, err tone) — then empty cells. Without pacing the
76
+ // bar renders as a single fill+empty pair, identical to the pre-pacing look.
77
+ // Empty text nodes still occupy a phantom cell in the opentui flex row, so
78
+ // zero-length segments are dropped from every return path (plain and pacing).
79
+ function quotaBarSegments(usedPct, appearance, pacing) {
80
+ const width = appearance.barWidth;
81
+ const cells = pct => Math.max(0, Math.min(Math.round(pct / 100 * width), width));
82
+ const usedCells = cells(usedPct);
83
+ const fillTone = usageTone(usedPct, appearance);
84
+ const plain = [{
85
+ text: appearance.barFilledChar.repeat(usedCells),
86
+ tone: fillTone
87
+ }, {
88
+ text: appearance.barEmptyChar.repeat(width - usedCells),
89
+ tone: fillTone
90
+ }];
91
+ if (!pacing) return plain.filter(segment => segment.text.length > 0);
92
+ const paceCells = cells(pacing.pacePercent);
93
+ const lo = Math.min(usedCells, paceCells);
94
+ const hi = Math.max(usedCells, paceCells);
95
+ if (hi === lo) return plain.filter(segment => segment.text.length > 0);
96
+ const overspent = usedCells > paceCells;
97
+ return [{
98
+ text: appearance.barFilledChar.repeat(lo),
99
+ tone: fillTone
100
+ }, {
101
+ text: (overspent ? PACE_DEFICIT_CHAR : PACE_RESERVE_CHAR).repeat(hi - lo),
102
+ tone: overspent ? 'err' : 'ok'
103
+ }, {
104
+ text: appearance.barEmptyChar.repeat(width - hi),
105
+ tone: fillTone
106
+ }].filter(segment => segment.text.length > 0);
107
+ }
108
+ export function formatResetIn(resetsAt) {
109
+ if (!resetsAt) return '';
110
+ const ms = new Date(resetsAt).getTime() - Date.now();
111
+ if (Number.isNaN(ms)) return '';
112
+ if (ms <= 0) return 'now';
113
+ const mins = Math.floor(ms / 60_000);
114
+ if (mins < 60) return `${mins}m`;
115
+ const hrs = Math.floor(mins / 60);
116
+ if (hrs < 24) {
117
+ const rm = mins % 60;
118
+ return rm > 0 ? `${hrs}h${rm}m` : `${hrs}h`;
119
+ }
120
+ const days = Math.floor(hrs / 24);
121
+ const rh = hrs % 24;
122
+ return rh > 0 ? `${days}d${rh}h` : `${days}d`;
123
+ }
124
+ function formatUntil(until) {
125
+ if (!until) return '';
126
+ const ms = until - Date.now();
127
+ if (ms <= 0) return 'now';
128
+ const mins = Math.ceil(ms / 60_000);
129
+ if (mins < 60) return `${mins}m`;
130
+ const hrs = Math.floor(mins / 60);
131
+ const rm = mins % 60;
132
+ return rm > 0 ? `${hrs}h${rm}m` : `${hrs}h`;
133
+ }
134
+
135
+ // --- Reusable components (aft-style) ---------------------------------------
136
+
137
+ function SectionHeader(props) {
138
+ return (() => {
139
+ var _el$ = _$createElement("box"),
140
+ _el$2 = _$createElement("text"),
141
+ _el$3 = _$createElement("b");
142
+ _$insertNode(_el$, _el$2);
143
+ _$setProp(_el$, "width", '100%');
144
+ _$insertNode(_el$2, _el$3);
145
+ _$insert(_el$3, () => props.title);
146
+ _$effect(_p$ => {
147
+ var _v$ = props.marginTop ?? 1,
148
+ _v$2 = props.theme.text;
149
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$, "marginTop", _v$, _p$.e));
150
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$2, "fg", _v$2, _p$.t));
151
+ return _p$;
152
+ }, {
153
+ e: undefined,
154
+ t: undefined
155
+ });
156
+ return _el$;
157
+ })();
158
+ }
159
+ function StatRow(props) {
160
+ return (() => {
161
+ var _el$4 = _$createElement("box"),
162
+ _el$5 = _$createElement("text"),
163
+ _el$6 = _$createElement("text"),
164
+ _el$7 = _$createElement("b");
165
+ _$insertNode(_el$4, _el$5);
166
+ _$insertNode(_el$4, _el$6);
167
+ _$setProp(_el$4, "width", '100%');
168
+ _$setProp(_el$4, "flexDirection", 'row');
169
+ _$setProp(_el$4, "justifyContent", 'space-between');
170
+ _$insert(_el$5, () => props.label);
171
+ _$insertNode(_el$6, _el$7);
172
+ _$insert(_el$7, () => props.value);
173
+ _$effect(_p$ => {
174
+ var _v$3 = props.theme.textMuted,
175
+ _v$4 = toneColor(props.theme, props.tone ?? 'text');
176
+ _v$3 !== _p$.e && (_p$.e = _$setProp(_el$5, "fg", _v$3, _p$.e));
177
+ _v$4 !== _p$.t && (_p$.t = _$setProp(_el$6, "fg", _v$4, _p$.t));
178
+ return _p$;
179
+ }, {
180
+ e: undefined,
181
+ t: undefined
182
+ });
183
+ return _el$4;
184
+ })();
185
+ }
186
+
187
+ // Compact row for the collapsed view: muted label left, caller-provided value
188
+ // right. Mirrors StatRow's layout so columns line up.
189
+ function CollapsedRow(props) {
190
+ return (() => {
191
+ var _el$8 = _$createElement("box"),
192
+ _el$9 = _$createElement("text");
193
+ _$insertNode(_el$8, _el$9);
194
+ _$setProp(_el$8, "width", '100%');
195
+ _$setProp(_el$8, "flexDirection", 'row');
196
+ _$setProp(_el$8, "justifyContent", 'space-between');
197
+ _$insert(_el$9, () => props.label);
198
+ _$insert(_el$8, () => props.children, null);
199
+ _$effect(_$p => _$setProp(_el$9, "fg", props.theme.textMuted, _$p));
200
+ return _el$8;
201
+ })();
202
+ }
203
+ // Maps present quota windows onto display rows and computes pacing against each
204
+ // dynamic duration or the historical 5h/7d duration for legacy snapshots.
205
+ export function buildQuotaRowsForDisplay(quota, now, pacingEnabled) {
206
+ return getPresentQuotaWindows(quota).map(row => ({
207
+ key: row.key,
208
+ label: row.label,
209
+ window: row.window,
210
+ pacing: pacingEnabled && row.windowMs !== null ? computeQuotaPacing(row.window, row.windowMs, now) : null
211
+ }));
212
+ }
213
+ export function isQuotaLoaded(quota) {
214
+ return quota !== null;
215
+ }
216
+ export function getQuotaMetadataRows(state) {
217
+ const rows = [];
218
+ if (state.planType) rows.push({
219
+ label: 'Plan',
220
+ value: state.planType
221
+ });
222
+ if (state.credits !== undefined) {
223
+ rows.push({
224
+ label: 'Credits',
225
+ value: String(state.credits)
226
+ });
227
+ }
228
+ return rows;
229
+ }
230
+ export function getAccountMetadataRows(resetCredits) {
231
+ return resetCredits === undefined ? [] : [{
232
+ label: 'resets',
233
+ value: String(resetCredits)
234
+ }];
235
+ }
236
+
237
+ // Quota window row: muted label left, tone-colored bar + percentage right,
238
+ // with an optional muted reset suffix. When pacing data is present, the bar
239
+ // gains a pace segment and an off-pace window adds a muted subline with the
240
+ // reserve/deficit delta and the projected runout.
241
+ function QuotaRow(props) {
242
+ const used = () => props.window?.usedPercent ?? 0;
243
+ const reset = () => formatResetIn(props.window?.resetsAt);
244
+ const paceLine = () => {
245
+ const pacing = props.pacing;
246
+ if (!pacing || pacing.state === 'on-pace') return null;
247
+ const pct = Math.round(Math.abs(pacing.deltaPercent));
248
+ if (pacing.state === 'reserve') return `reserve ${pct}% \u00b7 lasts`;
249
+ return pacing.runsOutAt ? `deficit ${pct}% \u00b7 out in ${formatResetIn(pacing.runsOutAt)}` : `deficit ${pct}% \u00b7 lasts`;
250
+ };
251
+ return _$createComponent(Show, {
252
+ get when() {
253
+ return props.window;
254
+ },
255
+ get fallback() {
256
+ return (() => {
257
+ var _el$17 = _$createElement("box"),
258
+ _el$18 = _$createElement("text"),
259
+ _el$19 = _$createElement("text");
260
+ _$insertNode(_el$17, _el$18);
261
+ _$insertNode(_el$17, _el$19);
262
+ _$setProp(_el$17, "width", '100%');
263
+ _$setProp(_el$17, "flexDirection", 'row');
264
+ _$insert(_el$18, () => props.label.padEnd(3));
265
+ _$insertNode(_el$19, _$createTextNode(`—`));
266
+ _$effect(_p$ => {
267
+ var _v$9 = props.theme.textMuted,
268
+ _v$0 = props.theme.textMuted;
269
+ _v$9 !== _p$.e && (_p$.e = _$setProp(_el$18, "fg", _v$9, _p$.e));
270
+ _v$0 !== _p$.t && (_p$.t = _$setProp(_el$19, "fg", _v$0, _p$.t));
271
+ return _p$;
272
+ }, {
273
+ e: undefined,
274
+ t: undefined
275
+ });
276
+ return _el$17;
277
+ })();
278
+ },
279
+ get children() {
280
+ return [(() => {
281
+ var _el$0 = _$createElement("box"),
282
+ _el$1 = _$createElement("box"),
283
+ _el$10 = _$createElement("text"),
284
+ _el$11 = _$createElement("text");
285
+ _$insertNode(_el$0, _el$1);
286
+ _$setProp(_el$0, "width", '100%');
287
+ _$setProp(_el$0, "flexDirection", 'row');
288
+ _$setProp(_el$0, "justifyContent", 'space-between');
289
+ _$insertNode(_el$1, _el$10);
290
+ _$insertNode(_el$1, _el$11);
291
+ _$setProp(_el$1, "flexDirection", 'row');
292
+ _$insert(_el$10, () => props.label.padEnd(3));
293
+ _$insert(_el$1, _$createComponent(For, {
294
+ get each() {
295
+ return quotaBarSegments(used(), props.appearance, props.pacing);
296
+ },
297
+ children: segment => (() => {
298
+ var _el$21 = _$createElement("text");
299
+ _$insert(_el$21, () => segment.text);
300
+ _$effect(_$p => _$setProp(_el$21, "fg", toneColor(props.theme, segment.tone), _$p));
301
+ return _el$21;
302
+ })()
303
+ }), _el$11);
304
+ _$insert(_el$11, () => ` ${String(Math.round(used())).padStart(3)}%`);
305
+ _$insert(_el$0, _$createComponent(Show, {
306
+ get when() {
307
+ return reset();
308
+ },
309
+ get children() {
310
+ var _el$12 = _$createElement("text");
311
+ _$insert(_el$12, reset);
312
+ _$effect(_$p => _$setProp(_el$12, "fg", props.theme.textMuted, _$p));
313
+ return _el$12;
314
+ }
315
+ }), null);
316
+ _$effect(_p$ => {
317
+ var _v$5 = props.theme.textMuted,
318
+ _v$6 = toneColor(props.theme, usageTone(used(), props.appearance));
319
+ _v$5 !== _p$.e && (_p$.e = _$setProp(_el$10, "fg", _v$5, _p$.e));
320
+ _v$6 !== _p$.t && (_p$.t = _$setProp(_el$11, "fg", _v$6, _p$.t));
321
+ return _p$;
322
+ }, {
323
+ e: undefined,
324
+ t: undefined
325
+ });
326
+ return _el$0;
327
+ })(), _$createComponent(Show, {
328
+ get when() {
329
+ return paceLine();
330
+ },
331
+ get children() {
332
+ var _el$13 = _$createElement("box"),
333
+ _el$14 = _$createElement("text"),
334
+ _el$16 = _$createElement("text");
335
+ _$insertNode(_el$13, _el$14);
336
+ _$insertNode(_el$13, _el$16);
337
+ _$setProp(_el$13, "width", '100%');
338
+ _$setProp(_el$13, "flexDirection", 'row');
339
+ _$insertNode(_el$14, _$createTextNode(` `));
340
+ _$insert(_el$16, paceLine);
341
+ _$effect(_p$ => {
342
+ var _v$7 = props.theme.textMuted,
343
+ _v$8 = toneColor(props.theme, props.pacing?.state === 'deficit' ? 'warn' : 'muted');
344
+ _v$7 !== _p$.e && (_p$.e = _$setProp(_el$14, "fg", _v$7, _p$.e));
345
+ _v$8 !== _p$.t && (_p$.t = _$setProp(_el$16, "fg", _v$8, _p$.t));
346
+ return _p$;
347
+ }, {
348
+ e: undefined,
349
+ t: undefined
350
+ });
351
+ return _el$13;
352
+ }
353
+ })];
354
+ }
355
+ });
356
+ }
357
+
358
+ // Account block: header row (name + status word) then per-window quota rows.
359
+ // Pacing is computed here — per window, against the wall clock at render time
360
+ // (re-evaluated on every state poll) — and disabled by passing false.
361
+ function AccountBlock(props) {
362
+ const statusWord = () => props.killed ? 'blocked' : props.active ? 'active' : 'idle';
363
+ const statusTone = () => props.killed ? 'err' : props.active ? 'ok' : 'muted';
364
+ const rows = () => buildQuotaRowsForDisplay(props.quota, Date.now(), props.pacingEnabled);
365
+ return (() => {
366
+ var _el$22 = _$createElement("box"),
367
+ _el$23 = _$createElement("box"),
368
+ _el$24 = _$createElement("text"),
369
+ _el$25 = _$createElement("b"),
370
+ _el$26 = _$createElement("text"),
371
+ _el$27 = _$createElement("b");
372
+ _$insertNode(_el$22, _el$23);
373
+ _$setProp(_el$22, "width", '100%');
374
+ _$setProp(_el$22, "flexDirection", 'column');
375
+ _$insertNode(_el$23, _el$24);
376
+ _$insertNode(_el$23, _el$26);
377
+ _$setProp(_el$23, "width", '100%');
378
+ _$setProp(_el$23, "flexDirection", 'row');
379
+ _$setProp(_el$23, "justifyContent", 'space-between');
380
+ _$insertNode(_el$24, _el$25);
381
+ _$insert(_el$25, () => props.name);
382
+ _$insertNode(_el$26, _el$27);
383
+ _$insert(_el$27, statusWord);
384
+ _$insert(_el$22, _$createComponent(Show, {
385
+ get when() {
386
+ return isQuotaLoaded(props.quota);
387
+ },
388
+ get fallback() {
389
+ return (() => {
390
+ var _el$28 = _$createElement("text");
391
+ _$insertNode(_el$28, _$createTextNode(`checking…`));
392
+ _$effect(_$p => _$setProp(_el$28, "fg", props.theme.textMuted, _$p));
393
+ return _el$28;
394
+ })();
395
+ },
396
+ get children() {
397
+ return _$createComponent(Show, {
398
+ get when() {
399
+ return rows().length > 0;
400
+ },
401
+ get fallback() {
402
+ return (() => {
403
+ var _el$30 = _$createElement("text");
404
+ _$insertNode(_el$30, _$createTextNode(`—`));
405
+ _$effect(_$p => _$setProp(_el$30, "fg", props.theme.textMuted, _$p));
406
+ return _el$30;
407
+ })();
408
+ },
409
+ get children() {
410
+ return _$createComponent(For, {
411
+ get each() {
412
+ return rows();
413
+ },
414
+ children: row => _$createComponent(QuotaRow, {
415
+ get theme() {
416
+ return props.theme;
417
+ },
418
+ get appearance() {
419
+ return props.appearance;
420
+ },
421
+ get label() {
422
+ return row.label;
423
+ },
424
+ get window() {
425
+ return row.window;
426
+ },
427
+ get pacing() {
428
+ return row.pacing;
429
+ }
430
+ })
431
+ });
432
+ }
433
+ });
434
+ }
435
+ }), null);
436
+ _$insert(_el$22, _$createComponent(For, {
437
+ get each() {
438
+ return getAccountMetadataRows(props.resetCredits);
439
+ },
440
+ children: row => _$createComponent(StatRow, {
441
+ get theme() {
442
+ return props.theme;
443
+ },
444
+ get label() {
445
+ return row.label;
446
+ },
447
+ get value() {
448
+ return row.value;
449
+ },
450
+ tone: "text"
451
+ })
452
+ }), null);
453
+ _$effect(_p$ => {
454
+ var _v$1 = props.marginTop ?? 0,
455
+ _v$10 = props.theme.text,
456
+ _v$11 = toneColor(props.theme, statusTone());
457
+ _v$1 !== _p$.e && (_p$.e = _$setProp(_el$22, "marginTop", _v$1, _p$.e));
458
+ _v$10 !== _p$.t && (_p$.t = _$setProp(_el$24, "fg", _v$10, _p$.t));
459
+ _v$11 !== _p$.a && (_p$.a = _$setProp(_el$26, "fg", _v$11, _p$.a));
460
+ return _p$;
461
+ }, {
462
+ e: undefined,
463
+ t: undefined,
464
+ a: undefined
465
+ });
466
+ return _el$22;
467
+ })();
468
+ }
469
+
470
+ // --- Quota dialog content ---------------------------------------------------
471
+
472
+ // Renders the same rich visualization as the sidebar (account blocks with
473
+ // quota bars + pacing) so the modal matches the sidebar look.
474
+ function QuotaDialogContent(props) {
475
+ const prefs = props.controller.prefs;
476
+ const [state, setState] = createSignal(DEFAULT_SIDEBAR_STATE);
477
+ let lastUpdated = 0;
478
+ async function refresh() {
479
+ const next = await readStateFromFile();
480
+ if (next.lastUpdated !== lastUpdated) {
481
+ lastUpdated = next.lastUpdated;
482
+ setState(next);
483
+ }
484
+ }
485
+ createEffect(() => {
486
+ const timer = setInterval(refresh, prefs().pollMs);
487
+ onCleanup(() => clearInterval(timer));
488
+ });
489
+ setTimeout(refresh, 0);
490
+ const theme = () => props.api.theme.current;
491
+ const enabledFallbacks = () => (state().fallbacks ?? []).filter(f => f.enabled);
492
+ const activeId = () => resolveQuotaDialogActiveId(state(), props.sessionId);
493
+ return (() => {
494
+ var _el$32 = _$createElement("box"),
495
+ _el$33 = _$createElement("box"),
496
+ _el$34 = _$createElement("box"),
497
+ _el$35 = _$createElement("text"),
498
+ _el$36 = _$createElement("b");
499
+ _$insertNode(_el$32, _el$33);
500
+ _$setProp(_el$32, "flexDirection", 'column');
501
+ _$setProp(_el$32, "padding", 2);
502
+ _$setProp(_el$32, "width", '100%');
503
+ _$setProp(_el$32, "alignItems", 'center');
504
+ _$insertNode(_el$33, _el$34);
505
+ _$setProp(_el$33, "flexDirection", 'column');
506
+ _$setProp(_el$33, "width", 58);
507
+ _$insertNode(_el$34, _el$35);
508
+ _$setProp(_el$34, "width", '100%');
509
+ _$setProp(_el$34, "justifyContent", 'center');
510
+ _$setProp(_el$34, "marginBottom", 1);
511
+ _$insertNode(_el$35, _el$36);
512
+ _$insertNode(_el$36, _$createTextNode(`Codex Quota`));
513
+ _$insert(_el$33, _$createComponent(AccountBlock, {
514
+ get theme() {
515
+ return theme();
516
+ },
517
+ get appearance() {
518
+ return prefs().appearance;
519
+ },
520
+ name: "main",
521
+ get quota() {
522
+ return state().main?.quota ?? null;
523
+ },
524
+ get killed() {
525
+ return state().main?.killed ?? false;
526
+ },
527
+ get active() {
528
+ return activeId() === 'main';
529
+ },
530
+ get pacingEnabled() {
531
+ return prefs().sections.pacing;
532
+ },
533
+ get resetCredits() {
534
+ return state().main?.resetCredits;
535
+ }
536
+ }), null);
537
+ _$insert(_el$33, _$createComponent(Show, {
538
+ get when() {
539
+ return prefs().sections.fallbackAccounts;
540
+ },
541
+ get children() {
542
+ return _$createComponent(For, {
543
+ get each() {
544
+ return enabledFallbacks();
545
+ },
546
+ children: fb => _$createComponent(AccountBlock, {
547
+ get theme() {
548
+ return theme();
549
+ },
550
+ get appearance() {
551
+ return prefs().appearance;
552
+ },
553
+ get name() {
554
+ return fb.label ?? fb.id;
555
+ },
556
+ get quota() {
557
+ return fb.quota;
558
+ },
559
+ get killed() {
560
+ return fb.killed;
561
+ },
562
+ get active() {
563
+ return activeId() === fb.id;
564
+ },
565
+ get pacingEnabled() {
566
+ return prefs().sections.pacing;
567
+ },
568
+ get resetCredits() {
569
+ return fb.resetCredits;
570
+ },
571
+ marginTop: 1
572
+ })
573
+ });
574
+ }
575
+ }), null);
576
+ _$effect(_$p => _$setProp(_el$35, "fg", theme().text, _$p));
577
+ return _el$32;
578
+ })();
579
+ }
580
+
581
+ // --- State plumbing ---------------------------------------------------------
582
+
583
+ export async function readStateFromFile() {
584
+ return getSidebarState();
585
+ }
586
+ export function resolveQuotaDialogActiveId(state, sessionId, now = Date.now()) {
587
+ return sessionId ? resolveSessionSidebarRouting(state, sessionId, now).activeId : state.activeId;
588
+ }
589
+ // The TUI may unmount and remount sidebar_content when the user switches
590
+ // views (e.g. main -> subagent -> main). A remount re-runs the component
591
+ // body, so any signal created inside the component would reset to its
592
+ // seed. The controller lives in the plugin closure (process lifetime)
593
+ // and owns the durable prefs/collapse signals plus the single shared
594
+ // watcher subscription, so collapse and live pref reloads survive the
595
+ // remount. No effects or memos here — those need a Solid owner, so the
596
+ // poll-interval createEffect stays inside the component.
597
+ function createSidebarController(initialPrefs) {
598
+ const [prefs, setPrefs] = createSignal(initialPrefs);
599
+ const seedCollapsed = initialPrefs.rememberCollapsed && initialPrefs.collapsed != null ? initialPrefs.collapsed : initialPrefs.startCollapsed;
600
+ const [collapsed, setCollapsed] = createSignal(seedCollapsed);
601
+ let lastPersistedCollapsed = initialPrefs.collapsed;
602
+ let lastApplied = JSON.stringify(initialPrefs);
603
+
604
+ // The watcher lives for the plugin/process lifetime — it is intentionally
605
+ // never disposed. Collapse guard mirrors the race-fix in toggleCollapsed:
606
+ // lastPersistedCollapsed is advanced only once our own write lands, so
607
+ // watcher echoes of the previous persisted value are rejected by the
608
+ // `!==` check and cannot revert a user click.
609
+ watchTuiPreferences(() => {
610
+ void (async () => {
611
+ const next = resolveOpenaiAuthPrefs(await readTuiPreferencesFile());
612
+ const serialized = JSON.stringify(next);
613
+ if (serialized === lastApplied) return;
614
+ lastApplied = serialized;
615
+ setPrefs(next);
616
+ if (next.rememberCollapsed && next.collapsed != null && next.collapsed !== lastPersistedCollapsed) {
617
+ lastPersistedCollapsed = next.collapsed;
618
+ setCollapsed(next.collapsed);
619
+ }
620
+ })();
621
+ });
622
+ function toggleCollapsed() {
623
+ const next = !collapsed();
624
+ setCollapsed(next);
625
+ if (prefs().rememberCollapsed) {
626
+ void queueTuiPreferenceUpdate(PLUGIN_KEY, ['collapsed'], next).then(() => {
627
+ lastPersistedCollapsed = next;
628
+ });
629
+ }
630
+ }
631
+ return {
632
+ prefs,
633
+ collapsed,
634
+ toggleCollapsed
635
+ };
636
+ }
637
+ function QuotaSidebar(props) {
638
+ const prefs = props.controller.prefs;
639
+ const collapsed = props.controller.collapsed;
640
+ const [state, setState] = createSignal(DEFAULT_SIDEBAR_STATE);
641
+ let lastUpdated = 0;
642
+ let debounce = null;
643
+ async function refresh() {
644
+ const next = await readStateFromFile();
645
+ if (next.lastUpdated !== lastUpdated) {
646
+ lastUpdated = next.lastUpdated;
647
+ setState(next);
648
+ }
649
+ }
650
+ function scheduleRefresh() {
651
+ if (debounce) clearTimeout(debounce);
652
+ debounce = setTimeout(() => {
653
+ debounce = null;
654
+ void refresh();
655
+ }, prefs().refreshDebounceMs);
656
+ }
657
+
658
+ // Background poller (server and TUI run as separate module instances, so we
659
+ // sync through the state file) plus event-driven refreshes for low latency.
660
+ // The interval rebuilds whenever pollMs changes.
661
+ createEffect(() => {
662
+ const timer = setInterval(refresh, prefs().pollMs);
663
+ onCleanup(() => clearInterval(timer));
664
+ });
665
+ const unsubs = [props.api.event.on('session.updated', scheduleRefresh), props.api.event.on('message.updated', scheduleRefresh)];
666
+ setTimeout(refresh, 300);
667
+ onCleanup(() => {
668
+ if (debounce) clearTimeout(debounce);
669
+ for (const u of unsubs) u();
670
+ });
671
+ const theme = () => props.api.theme.current;
672
+ const enabledFallbacks = () => (state().fallbacks ?? []).filter(f => f.enabled);
673
+ const hasData = () => state().main?.quota != null || enabledFallbacks().length > 0;
674
+ const headerLabel = () => {
675
+ const name = prefs().header.label;
676
+ return !hasData() ? name : collapsed() ? `\u25b6 ${name}` : `\u25bc ${name}`;
677
+ };
678
+ const sessionRouting = () => resolveSessionSidebarRouting(state(), props.sessionId);
679
+ const sessionState = () => ({
680
+ ...state(),
681
+ activeId: sessionRouting().activeId,
682
+ route: sessionRouting().route
683
+ });
684
+ const activeAccount = () => resolveActiveAccount(sessionState());
685
+ const activeQuotaSummary = () => getCollapsedQuotaSummary(activeAccount().quota);
686
+ const activePacingDeficit = () => {
687
+ if (!prefs().sections.pacing) return false;
688
+ return buildQuotaRowsForDisplay(activeAccount().quota, Date.now(), true).some(row => row.pacing?.state === 'deficit');
689
+ };
690
+ const activeQuotaTone = () => {
691
+ const summary = activeQuotaSummary();
692
+ const values = [summary.primaryUsedPercent, summary.secondaryUsedPercent].filter(value => value != null);
693
+ // Pacing deficit is an advisory projection, not actual quota exhaustion,
694
+ // so it can only BUMP the usage tone up to warn at most — never soften a
695
+ // real warn/err usage reading and never paint a true red.
696
+ const base = values.length > 0 ? usageTone(Math.max(...values), prefs().appearance) : 'muted';
697
+ if (!activePacingDeficit()) return base;
698
+ return base === 'ok' || base === 'muted' ? 'warn' : base;
699
+ };
700
+ const killedNames = () => [state().main?.killed ? 'main' : '', ...enabledFallbacks().filter(f => f.killed).map(f => f.label ?? f.id)].filter(Boolean);
701
+ const quotaBackedOff = () => state().main?.quotaBackedOff === true;
702
+ const refreshBackedOff = () => state().main?.refreshBackedOff === true;
703
+ const degraded = () => killedNames().length > 0 || quotaBackedOff() || refreshBackedOff();
704
+ return (() => {
705
+ var _el$38 = _$createElement("box"),
706
+ _el$39 = _$createElement("box"),
707
+ _el$40 = _$createElement("box"),
708
+ _el$41 = _$createElement("text"),
709
+ _el$42 = _$createElement("b");
710
+ _$insertNode(_el$38, _el$39);
711
+ _$setProp(_el$38, "width", '100%');
712
+ _$setProp(_el$38, "flexDirection", 'column');
713
+ _$setProp(_el$38, "border", SINGLE_BORDER);
714
+ _$setProp(_el$38, "paddingTop", 1);
715
+ _$setProp(_el$38, "paddingBottom", 1);
716
+ _$setProp(_el$38, "paddingLeft", 1);
717
+ _$setProp(_el$38, "paddingRight", 1);
718
+ _$insertNode(_el$39, _el$40);
719
+ _$setProp(_el$39, "width", '100%');
720
+ _$setProp(_el$39, "flexDirection", 'row');
721
+ _$setProp(_el$39, "justifyContent", 'space-between');
722
+ _$setProp(_el$39, "alignItems", 'center');
723
+ _$setProp(_el$39, "onMouseDown", () => {
724
+ if (hasData()) props.controller.toggleCollapsed();
725
+ });
726
+ _$insertNode(_el$40, _el$41);
727
+ _$setProp(_el$40, "paddingLeft", 1);
728
+ _$setProp(_el$40, "paddingRight", 1);
729
+ _$insertNode(_el$41, _el$42);
730
+ _$insert(_el$42, headerLabel);
731
+ _$insert(_el$39, _$createComponent(Show, {
732
+ get when() {
733
+ return degraded();
734
+ },
735
+ get fallback() {
736
+ return _$createComponent(Show, {
737
+ get when() {
738
+ return prefs().header.showVersion && PLUGIN_VERSION !== '';
739
+ },
740
+ get children() {
741
+ var _el$51 = _$createElement("text");
742
+ _$insert(_el$51, `v${PLUGIN_VERSION}`);
743
+ _$effect(_$p => _$setProp(_el$51, "fg", theme().textMuted, _$p));
744
+ return _el$51;
745
+ }
746
+ });
747
+ },
748
+ get children() {
749
+ var _el$43 = _$createElement("box"),
750
+ _el$44 = _$createElement("text"),
751
+ _el$45 = _$createElement("b");
752
+ _$insertNode(_el$43, _el$44);
753
+ _$setProp(_el$43, "paddingLeft", 1);
754
+ _$setProp(_el$43, "paddingRight", 1);
755
+ _$insertNode(_el$44, _el$45);
756
+ _$insertNode(_el$45, _$createTextNode(`LIMITED`));
757
+ _$effect(_p$ => {
758
+ var _v$12 = theme().warning,
759
+ _v$13 = theme().background;
760
+ _v$12 !== _p$.e && (_p$.e = _$setProp(_el$43, "backgroundColor", _v$12, _p$.e));
761
+ _v$13 !== _p$.t && (_p$.t = _$setProp(_el$44, "fg", _v$13, _p$.t));
762
+ return _p$;
763
+ }, {
764
+ e: undefined,
765
+ t: undefined
766
+ });
767
+ return _el$43;
768
+ }
769
+ }), null);
770
+ _$insert(_el$38, _$createComponent(Show, {
771
+ get when() {
772
+ return _$memo(() => !!collapsed())() && hasData();
773
+ },
774
+ get children() {
775
+ return _$createComponent(CollapsedRow, {
776
+ get theme() {
777
+ return theme();
778
+ },
779
+ get label() {
780
+ return activeAccount().name;
781
+ },
782
+ get children() {
783
+ return _$createComponent(Show, {
784
+ get when() {
785
+ return activeQuotaSummary().text != null;
786
+ },
787
+ get fallback() {
788
+ return (() => {
789
+ var _el$52 = _$createElement("text");
790
+ _$insertNode(_el$52, _$createTextNode(`—`));
791
+ _$effect(_$p => _$setProp(_el$52, "fg", theme().textMuted, _$p));
792
+ return _el$52;
793
+ })();
794
+ },
795
+ get children() {
796
+ var _el$47 = _$createElement("box"),
797
+ _el$48 = _$createElement("text"),
798
+ _el$49 = _$createElement("b"),
799
+ _el$50 = _$createElement("text");
800
+ _$insertNode(_el$47, _el$48);
801
+ _$insertNode(_el$47, _el$50);
802
+ _$setProp(_el$47, "flexDirection", 'row');
803
+ _$insertNode(_el$48, _el$49);
804
+ _$insert(_el$49, () => activeQuotaSummary().text);
805
+ _$insert(_el$50, () => activeAccount().killed ? ' \u2298' : ' \u25cf');
806
+ _$effect(_p$ => {
807
+ var _v$14 = toneColor(theme(), activeQuotaTone()),
808
+ _v$15 = toneColor(theme(), activeAccount().killed ? 'err' : activeQuotaTone());
809
+ _v$14 !== _p$.e && (_p$.e = _$setProp(_el$48, "fg", _v$14, _p$.e));
810
+ _v$15 !== _p$.t && (_p$.t = _$setProp(_el$50, "fg", _v$15, _p$.t));
811
+ return _p$;
812
+ }, {
813
+ e: undefined,
814
+ t: undefined
815
+ });
816
+ return _el$47;
817
+ }
818
+ });
819
+ }
820
+ });
821
+ }
822
+ }), null);
823
+ _$insert(_el$38, _$createComponent(Show, {
824
+ get when() {
825
+ return !collapsed() || !hasData();
826
+ },
827
+ get children() {
828
+ return [_$createComponent(Show, {
829
+ get when() {
830
+ return hasData();
831
+ },
832
+ get fallback() {
833
+ return (() => {
834
+ var _el$54 = _$createElement("box"),
835
+ _el$55 = _$createElement("text");
836
+ _$insertNode(_el$54, _el$55);
837
+ _$setProp(_el$54, "marginTop", 1);
838
+ _$setProp(_el$54, "width", '100%');
839
+ _$insertNode(_el$55, _$createTextNode(`Waiting for quota…`));
840
+ _$effect(_$p => _$setProp(_el$55, "fg", theme().textMuted, _$p));
841
+ return _el$54;
842
+ })();
843
+ },
844
+ get children() {
845
+ return _$createComponent(Show, {
846
+ get when() {
847
+ return prefs().sections.quota;
848
+ },
849
+ get children() {
850
+ return [_$createComponent(SectionHeader, {
851
+ get theme() {
852
+ return theme();
853
+ },
854
+ title: "Quota"
855
+ }), _$createComponent(AccountBlock, {
856
+ get theme() {
857
+ return theme();
858
+ },
859
+ get appearance() {
860
+ return prefs().appearance;
861
+ },
862
+ name: "main",
863
+ get quota() {
864
+ return state().main?.quota ?? null;
865
+ },
866
+ get killed() {
867
+ return state().main?.killed ?? false;
868
+ },
869
+ get active() {
870
+ return sessionRouting().activeId === 'main';
871
+ },
872
+ get pacingEnabled() {
873
+ return prefs().sections.pacing;
874
+ },
875
+ get resetCredits() {
876
+ return state().main?.resetCredits;
877
+ }
878
+ }), _$createComponent(Show, {
879
+ get when() {
880
+ return prefs().sections.fallbackAccounts;
881
+ },
882
+ get children() {
883
+ return _$createComponent(For, {
884
+ get each() {
885
+ return enabledFallbacks();
886
+ },
887
+ children: fb => _$createComponent(AccountBlock, {
888
+ get theme() {
889
+ return theme();
890
+ },
891
+ get appearance() {
892
+ return prefs().appearance;
893
+ },
894
+ get name() {
895
+ return fb.label ?? fb.id;
896
+ },
897
+ get quota() {
898
+ return fb.quota;
899
+ },
900
+ get killed() {
901
+ return fb.killed;
902
+ },
903
+ get active() {
904
+ return sessionRouting().activeId === fb.id;
905
+ },
906
+ get pacingEnabled() {
907
+ return prefs().sections.pacing;
908
+ },
909
+ get resetCredits() {
910
+ return fb.resetCredits;
911
+ },
912
+ marginTop: 1
913
+ })
914
+ });
915
+ }
916
+ })];
917
+ }
918
+ });
919
+ }
920
+ }), _$createComponent(Show, {
921
+ get when() {
922
+ return prefs().sections.routing;
923
+ },
924
+ get children() {
925
+ return [_$createComponent(SectionHeader, {
926
+ get theme() {
927
+ return theme();
928
+ },
929
+ title: "Routing"
930
+ }), _$createComponent(StatRow, {
931
+ get theme() {
932
+ return theme();
933
+ },
934
+ label: "Route",
935
+ get value() {
936
+ return sessionRouting().route;
937
+ },
938
+ tone: "accent"
939
+ })];
940
+ }
941
+ }), _$createComponent(For, {
942
+ get each() {
943
+ return getQuotaMetadataRows(state());
944
+ },
945
+ children: row => _$createComponent(StatRow, {
946
+ get theme() {
947
+ return theme();
948
+ },
949
+ get label() {
950
+ return row.label;
951
+ },
952
+ get value() {
953
+ return row.value;
954
+ },
955
+ tone: "text"
956
+ })
957
+ }), _$createComponent(Show, {
958
+ get when() {
959
+ return _$memo(() => !!degraded())() && prefs().sections.health;
960
+ },
961
+ get children() {
962
+ return [_$createComponent(SectionHeader, {
963
+ get theme() {
964
+ return theme();
965
+ },
966
+ title: "Health"
967
+ }), _$createComponent(Show, {
968
+ get when() {
969
+ return quotaBackedOff();
970
+ },
971
+ get children() {
972
+ return _$createComponent(StatRow, {
973
+ get theme() {
974
+ return theme();
975
+ },
976
+ label: "Quota API",
977
+ get value() {
978
+ return `backoff ${formatUntil(state().main?.quotaBackoffUntil)}`;
979
+ },
980
+ tone: "warn"
981
+ });
982
+ }
983
+ }), _$createComponent(Show, {
984
+ get when() {
985
+ return refreshBackedOff();
986
+ },
987
+ get children() {
988
+ return _$createComponent(StatRow, {
989
+ get theme() {
990
+ return theme();
991
+ },
992
+ label: "Token refresh",
993
+ get value() {
994
+ return `backoff ${formatUntil(state().main?.refreshBackoffUntil)}`;
995
+ },
996
+ tone: "warn"
997
+ });
998
+ }
999
+ })];
1000
+ }
1001
+ }), _$createComponent(Show, {
1002
+ get when() {
1003
+ return killedNames().length > 0;
1004
+ },
1005
+ get children() {
1006
+ return _$createComponent(StatRow, {
1007
+ get theme() {
1008
+ return theme();
1009
+ },
1010
+ label: "Killswitch",
1011
+ get value() {
1012
+ return `${killedNames().join(', ')} blocked`;
1013
+ },
1014
+ tone: "err"
1015
+ });
1016
+ }
1017
+ })];
1018
+ }
1019
+ }), null);
1020
+ _$effect(_p$ => {
1021
+ var _v$16 = theme().borderActive,
1022
+ _v$17 = theme().accent,
1023
+ _v$18 = theme().background;
1024
+ _v$16 !== _p$.e && (_p$.e = _$setProp(_el$38, "borderColor", _v$16, _p$.e));
1025
+ _v$17 !== _p$.t && (_p$.t = _$setProp(_el$40, "backgroundColor", _v$17, _p$.t));
1026
+ _v$18 !== _p$.a && (_p$.a = _$setProp(_el$41, "fg", _v$18, _p$.a));
1027
+ return _p$;
1028
+ }, {
1029
+ e: undefined,
1030
+ t: undefined,
1031
+ a: undefined
1032
+ });
1033
+ return _el$38;
1034
+ })();
1035
+ }
1036
+ const tui = async api => {
1037
+ const root = await readTuiPreferencesFile();
1038
+ const controller = createSidebarController(resolveOpenaiAuthPrefs(root));
1039
+ api.slots.register({
1040
+ order: computeEffectiveOrder(root, PLUGIN_KEY, DEFAULT_SLOT_ORDER),
1041
+ slots: {
1042
+ sidebar_content(_ctx, props) {
1043
+ return _$createComponent(QuotaSidebar, {
1044
+ api: api,
1045
+ controller: controller,
1046
+ get sessionId() {
1047
+ return props.session_id;
1048
+ }
1049
+ });
1050
+ }
1051
+ }
1052
+ });
1053
+ if (!rpcPollStarted) {
1054
+ rpcPollStarted = true;
1055
+ const myPid = process.pid;
1056
+ const rpcClient = createRpcClient(getRpcDir(api.state.path.directory ?? ''), myPid, entry => {
1057
+ log.debug('rpc tui pid', {
1058
+ myPid,
1059
+ matchedPortFilePid: entry?.pid ?? null,
1060
+ rpcPort: entry?.port ?? null
1061
+ });
1062
+ });
1063
+ let lastNotificationId = 0;
1064
+ let rpcInFlight = false;
1065
+ setInterval(() => {
1066
+ if (rpcInFlight) return;
1067
+ const current = api.route.current;
1068
+ const resolved = typeof current === 'function' ? current() : current;
1069
+ const sessionId = resolved?.params?.sessionID;
1070
+ rpcInFlight = true;
1071
+ void rpcClient.pending(lastNotificationId, sessionId).then(messages => {
1072
+ for (const message of [...messages].sort((a, b) => a.id - b.id)) {
1073
+ lastNotificationId = Math.max(lastNotificationId, message.id);
1074
+ if (message.payload.command === 'openai-quota') {
1075
+ api.ui.dialog.setSize('xlarge');
1076
+ api.ui.dialog.replace(() => _$createComponent(QuotaDialogContent, {
1077
+ api: api,
1078
+ controller: controller,
1079
+ sessionId: sessionId
1080
+ }));
1081
+ continue;
1082
+ }
1083
+ openCommandDialog(api, message.payload, (command, args) => rpcClient.apply({
1084
+ command,
1085
+ arguments: args
1086
+ }), sessionId);
1087
+ }
1088
+ }).catch(() => {}).finally(() => {
1089
+ rpcInFlight = false;
1090
+ });
1091
+ }, RPC_POLL_MS);
1092
+ }
1093
+ };
1094
+ const plugin = {
1095
+ id: ID,
1096
+ tui
1097
+ };
1098
+ export default plugin;