@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.
- package/README.md +1 -0
- package/dist/cli.js +1 -1
- package/dist/codex-http.d.ts +3 -0
- package/dist/config.d.ts +3 -0
- package/dist/core/accounts.d.ts +9 -3
- package/dist/core/oauth.d.ts +1 -1
- package/dist/core/quota-manager.d.ts +2 -2
- package/dist/{index-k5zwf3j1.js → index-1anzb8jh.js} +1 -1
- package/dist/index-1r02s323.js +6 -0
- package/dist/index-2ng03cby.js +2 -0
- package/dist/{index-e7m7qbne.js → index-6y6ss1kc.js} +1 -1
- package/dist/{index-1xmasv0x.js → index-b6kp9ydc.js} +1 -1
- package/dist/index-cz1bkmjz.js +2 -0
- package/dist/index-dzg88nw2.js +89 -0
- package/dist/index-y67qn95a.js +2 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +65 -65
- package/dist/model-costs.d.ts +29 -0
- package/dist/prompt-context.d.ts +1 -1
- package/dist/quota-normalize-8yzxqy78.js +1 -0
- package/dist/quota-normalize.d.ts +13 -4
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/sidebar-state.d.ts +47 -2
- package/dist/sidebar-state.js +1 -1
- package/dist/token-util-h46jb3eg.js +1 -0
- package/dist/token-yzcb1p3e.js +1 -0
- package/dist/tui/command-dialogs.d.ts +8 -1
- package/dist/tui-preferences.js +1 -1
- package/dist/tui.d.ts +18 -1
- package/dist/version.d.ts +1 -1
- package/dist/ws-pool.d.ts +1 -0
- package/package.json +11 -8
- package/src/core/refresh-file-lock.ts +293 -0
- package/src/sidebar-state.ts +392 -13
- package/src/tui/command-dialogs.tsx +43 -25
- package/src/tui/entry.mjs +37 -0
- package/src/tui-compiled/logger.ts +161 -0
- package/src/tui-compiled/rpc/port-file.ts +70 -0
- package/src/tui-compiled/rpc/protocol.ts +31 -0
- package/src/tui-compiled/rpc/rpc-client.ts +78 -0
- package/src/tui-compiled/rpc/rpc-dir.ts +22 -0
- package/src/tui-compiled/sidebar-state.ts +695 -0
- package/src/tui-compiled/tui/command-dialogs.tsx +645 -0
- package/src/tui-compiled/tui-preferences.ts +328 -0
- package/src/tui-compiled/tui.tsx +1098 -0
- package/src/tui-compiled/util/open-url.ts +30 -0
- package/src/tui.tsx +158 -77
- package/dist/index-5s67238f.js +0 -2
- package/dist/index-d9hq0tmc.js +0 -2
- package/dist/index-mrg2fms9.js +0 -2
- package/dist/index-phrx93zy.js +0 -92
- package/dist/index-qdh7pt5a.js +0 -3
- package/dist/quota-normalize-dfdpwwm3.js +0 -1
- package/dist/token-01dcf92a.js +0 -1
- 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;
|