@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,645 @@
1
+ import { createTextNode as _$createTextNode } from "opentui:runtime-module:%40opentui%2Fsolid";
2
+ import { memo as _$memo } from "opentui:runtime-module:%40opentui%2Fsolid";
3
+ import { createComponent as _$createComponent } from "opentui:runtime-module:%40opentui%2Fsolid";
4
+ import { insertNode as _$insertNode } from "opentui:runtime-module:%40opentui%2Fsolid";
5
+ import { insert as _$insert } from "opentui:runtime-module:%40opentui%2Fsolid";
6
+ import { setProp as _$setProp } from "opentui:runtime-module:%40opentui%2Fsolid";
7
+ import { createElement as _$createElement } from "opentui:runtime-module:%40opentui%2Fsolid";
8
+ /** @jsxImportSource @opentui/solid */
9
+
10
+ import { getCollapsedQuotaSummary, getSidebarState, resolveSessionSidebarRouting } from "../sidebar-state.js";
11
+ import { openUrl } from "../util/open-url";
12
+ export function buildCachekeepDialogOptions(payload) {
13
+ const enabled = payload.knobs.enabled === true;
14
+ const subagents = payload.knobs.subagents === true;
15
+ const running = payload.knobs.running === true;
16
+ const tracked = Number(payload.knobs.tracked ?? 0);
17
+ const generatedAt = Number(payload.knobs.generatedAt ?? Date.now());
18
+ const lastWarmAt = Number(payload.knobs.lastWarmAt ?? 0);
19
+ const maxIdleWarmMs = Number(payload.knobs.maxIdleWarmMs ?? 60 * 60 * 1000);
20
+ const maxSubagentIdleMs = Number(payload.knobs.maxSubagentIdleMs ?? 30 * 60 * 1000);
21
+ const windowKnob = payload.knobs.window;
22
+ const windowLabel = windowKnob ? `${String(windowKnob.startHour).padStart(2, '0')}-${String(windowKnob.endHour).padStart(2, '0')}` : 'always';
23
+ const lastWarm = lastWarmAt ? `${Math.ceil((generatedAt - lastWarmAt) / 1000)}s ago` : 'none yet';
24
+ const idleWindow = Math.round(maxIdleWarmMs / 60_000);
25
+ const subIdleWindow = Math.round(maxSubagentIdleMs / 60_000);
26
+ const statusParts = [enabled ? '● Enabled' : '○ Disabled', `timer ${running ? 'armed' : 'idle'}`, `${tracked} tracked`, `last warm ${lastWarm}`, `window ${windowLabel}`, `${idleWindow}m idle cap`, `subagent idle ${subIdleWindow}m`].filter(part => part.length > 0);
27
+ return [{
28
+ title: statusParts.join(' | '),
29
+ value: 'status',
30
+ description: 'Current cachekeep state'
31
+ }, {
32
+ title: enabled ? 'Turn off' : 'Turn on',
33
+ value: enabled ? 'off' : 'on',
34
+ description: enabled ? 'Stop capturing and warming prompt-cache prefixes' : 'Persistently enable capture; the timer self-arms on request capture'
35
+ }, {
36
+ title: subagents ? 'Subagent warming: on' : 'Subagent warming: off',
37
+ value: subagents ? 'subagents off' : 'subagents on',
38
+ description: subagents ? `Warm subagent sessions (${subIdleWindow}m idle cap). Disable to skip subagents.` : `Skip subagent sessions. Enable to warm them too (${subIdleWindow}m idle cap).`
39
+ }, {
40
+ title: windowKnob ? `Warm window: ${windowLabel}` : 'Set warm window…',
41
+ value: 'set_window',
42
+ description: windowKnob ? `Currently only warming between ${windowLabel} local hours — pick to change` : 'Restrict capture + warm to a clock-hour window, e.g. 9-18 or 22-6'
43
+ }, {
44
+ title: 'Clear warm window',
45
+ value: 'clear_window',
46
+ description: windowKnob ? 'Remove the clock-hour restriction — return to always-warm (within idle caps)' : 'No clock-hour window is currently set'
47
+ }, {
48
+ title: 'Refresh status',
49
+ value: 'refresh',
50
+ description: 'Re-read cachekeep status'
51
+ }, {
52
+ title: 'Close',
53
+ value: 'close',
54
+ description: 'Close this dialog'
55
+ }];
56
+ }
57
+ function showText(api, text) {
58
+ api.ui.dialog.setSize('xlarge');
59
+ api.ui.dialog.replace(() => (() => {
60
+ var _el$ = _$createElement("box"),
61
+ _el$2 = _$createElement("text");
62
+ _$insertNode(_el$, _el$2);
63
+ _$setProp(_el$, "flexDirection", 'column');
64
+ _$setProp(_el$, "padding", 1);
65
+ _$setProp(_el$, "width", '100%');
66
+ _$insert(_el$2, text);
67
+ return _el$;
68
+ })());
69
+ }
70
+ function showSetCachekeepWindowPrompt(api, apply, state, render) {
71
+ const DialogPrompt = api.ui.DialogPrompt;
72
+ const knobWindow = state.knobs.window;
73
+ const seed = knobWindow ? `${knobWindow.startHour}-${knobWindow.endHour}` : '';
74
+ api.ui.dialog.setSize('xlarge');
75
+ api.ui.dialog.replace(() => _$createComponent(DialogPrompt, {
76
+ title: "Cachekeep warm window",
77
+ description: () => (() => {
78
+ var _el$3 = _$createElement("text");
79
+ _$insert(_el$3, () => state.text);
80
+ return _el$3;
81
+ })(),
82
+ placeholder: "HH-HH (e.g. 9-18 or 22-6)",
83
+ value: seed,
84
+ onConfirm: value => {
85
+ const trimmed = value.trim();
86
+ if (!trimmed) {
87
+ render(state);
88
+ return;
89
+ }
90
+ void apply('openai-cachekeep', trimmed).then(r => {
91
+ api.ui.toast({
92
+ message: r.text
93
+ });
94
+ render({
95
+ command: 'openai-cachekeep',
96
+ text: r.text,
97
+ knobs: r.knobs
98
+ });
99
+ });
100
+ },
101
+ onCancel: () => render(state)
102
+ }));
103
+ }
104
+ export function openCommandDialog(api, payload, apply, sessionId) {
105
+ if (payload.command === 'openai-routing') {
106
+ const current = payload.knobs.mode ?? 'main-first';
107
+ const DialogSelect = api.ui.DialogSelect;
108
+ api.ui.dialog.setSize('xlarge');
109
+ api.ui.dialog.replace(() => _$createComponent(DialogSelect, {
110
+ title: "OpenAI routing",
111
+ current: current,
112
+ options: [{
113
+ title: 'Main first',
114
+ value: 'main-first',
115
+ description: 'Use the main account until exhausted'
116
+ }, {
117
+ title: 'Fallback first',
118
+ value: 'fallback-first',
119
+ description: 'Prefer fallback accounts, preserve main'
120
+ }],
121
+ onSelect: option => {
122
+ void apply('openai-routing', String(option.value)).then(r => {
123
+ api.ui.toast({
124
+ message: r.text
125
+ });
126
+ api.ui.dialog.clear();
127
+ });
128
+ }
129
+ }));
130
+ return;
131
+ }
132
+ if (payload.command === 'openai-dump') {
133
+ const enabled = payload.knobs.enabled === true;
134
+ const DialogConfirm = api.ui.DialogConfirm;
135
+ api.ui.dialog.setSize('xlarge');
136
+ api.ui.dialog.replace(() => _$createComponent(DialogConfirm, {
137
+ title: "OpenAI request dump",
138
+ get message() {
139
+ return `${payload.text}\n\n${enabled ? 'Disable' : 'Enable'} request dump?`;
140
+ },
141
+ onConfirm: () => {
142
+ void apply('openai-dump', enabled ? 'off' : 'on').then(r => {
143
+ api.ui.toast({
144
+ message: r.text
145
+ });
146
+ api.ui.dialog.clear();
147
+ });
148
+ },
149
+ onCancel: () => api.ui.dialog.clear()
150
+ }));
151
+ return;
152
+ }
153
+ if (payload.command === 'openai-killswitch') {
154
+ const config = payload.knobs.config ?? {};
155
+ const accountIds = payload.knobs.accountIds ?? [];
156
+ const enabled = config.enabled === true;
157
+ const readT = t => {
158
+ const fh = t?.primary ?? t?.['5h'] ?? 5;
159
+ const sd = t?.secondary ?? t?.['1w'] ?? 10;
160
+ return {
161
+ fh,
162
+ sd
163
+ };
164
+ };
165
+ const mainT = readT(config.main);
166
+ const seedParts = [`main:${mainT.fh},${mainT.sd}`];
167
+ for (const id of accountIds) {
168
+ const t = readT(config.accounts?.[id] ?? config.main);
169
+ seedParts.push(`${id}:${t.fh},${t.sd}`);
170
+ }
171
+ const seed = seedParts.join(' ');
172
+ const openEdit = () => {
173
+ const DialogPrompt = api.ui.DialogPrompt;
174
+ api.ui.dialog.setSize('xlarge');
175
+ api.ui.dialog.replace(() => _$createComponent(DialogPrompt, {
176
+ title: "Killswitch thresholds",
177
+ description: () => (() => {
178
+ var _el$4 = _$createElement("text");
179
+ _$insert(_el$4, () => payload.text);
180
+ return _el$4;
181
+ })(),
182
+ placeholder: "main:5,10 work-alt:5,10",
183
+ value: seed,
184
+ onConfirm: value => {
185
+ void apply('openai-killswitch', `set ${value.trim()}`).then(r => {
186
+ api.ui.toast({
187
+ message: r.text
188
+ });
189
+ api.ui.dialog.clear();
190
+ });
191
+ },
192
+ onCancel: () => api.ui.dialog.clear()
193
+ }));
194
+ };
195
+ const DialogSelect = api.ui.DialogSelect;
196
+ api.ui.dialog.setSize('xlarge');
197
+ api.ui.dialog.replace(() => _$createComponent(DialogSelect, {
198
+ title: "OpenAI killswitch",
199
+ options: [{
200
+ title: enabled ? 'Disable killswitch' : 'Enable killswitch',
201
+ value: enabled ? 'off' : 'on',
202
+ description: enabled ? 'Stop hard-blocking on low quota' : 'Hard-block requests when quota drops below thresholds'
203
+ }, {
204
+ title: 'Edit thresholds\u2026',
205
+ value: 'edit',
206
+ description: 'Set per-account primary,secondary cutoffs'
207
+ }],
208
+ onSelect: option => {
209
+ if (option.value === 'edit') {
210
+ openEdit();
211
+ return;
212
+ }
213
+ void apply('openai-killswitch', String(option.value)).then(r => {
214
+ api.ui.toast({
215
+ message: r.text
216
+ });
217
+ api.ui.dialog.clear();
218
+ });
219
+ }
220
+ }));
221
+ return;
222
+ }
223
+ if (payload.command === 'openai-logging') {
224
+ const current = payload.knobs.level ?? 'info';
225
+ const levels = ['error', 'warn', 'info', 'debug', 'trace'];
226
+ const DialogSelect = api.ui.DialogSelect;
227
+ api.ui.dialog.setSize('xlarge');
228
+ api.ui.dialog.replace(() => _$createComponent(DialogSelect, {
229
+ title: "OpenAI logging",
230
+ current: current,
231
+ get options() {
232
+ return levels.map(level => ({
233
+ title: level,
234
+ value: level,
235
+ description: level === current ? 'currently active' : `Set log level to ${level}`
236
+ }));
237
+ },
238
+ onSelect: option => {
239
+ void apply('openai-logging', String(option.value)).then(r => {
240
+ api.ui.toast({
241
+ message: r.text
242
+ });
243
+ api.ui.dialog.clear();
244
+ });
245
+ }
246
+ }));
247
+ return;
248
+ }
249
+ if (payload.command === 'openai-cachekeep') {
250
+ const render = state => {
251
+ const options = buildCachekeepDialogOptions(state);
252
+ const DialogSelect = api.ui.DialogSelect;
253
+ api.ui.dialog.setSize('xlarge');
254
+ api.ui.dialog.replace(() => _$createComponent(DialogSelect, {
255
+ title: "OpenAI cachekeep",
256
+ current: "status",
257
+ options: options,
258
+ onSelect: option => {
259
+ if (option.value === 'close') {
260
+ api.ui.dialog.clear();
261
+ return;
262
+ }
263
+ if (option.value === 'set_window') {
264
+ showSetCachekeepWindowPrompt(api, apply, state, render);
265
+ return;
266
+ }
267
+ if (option.value === 'clear_window') {
268
+ // The option label is `clear_window`, but executeCachekeepCommand
269
+ // matches on `window clear` — forward the canonical form or the
270
+ // command falls through to the usage-text branch and the window
271
+ // is never cleared.
272
+ void apply('openai-cachekeep', 'window clear').then(r => {
273
+ render({
274
+ command: 'openai-cachekeep',
275
+ text: r.text,
276
+ knobs: r.knobs
277
+ });
278
+ });
279
+ return;
280
+ }
281
+ const args = option.value === 'refresh' ? 'status' : String(option.value);
282
+ void apply('openai-cachekeep', args).then(r => {
283
+ render({
284
+ command: 'openai-cachekeep',
285
+ text: r.text,
286
+ knobs: r.knobs
287
+ });
288
+ });
289
+ }
290
+ }));
291
+ };
292
+ render(payload);
293
+ return;
294
+ }
295
+ if (payload.command === 'openai-account') {
296
+ openAccountDialog(api, apply, sessionId);
297
+ return;
298
+ }
299
+
300
+ // fallback for quota (display-only)
301
+ showText(api, payload.text);
302
+ }
303
+
304
+ // -- Accounts dialog ---------------------------------------------------------
305
+
306
+ export function formatQuotaWindows(quota) {
307
+ const windowSummary = getCollapsedQuotaSummary(quota ?? null).text;
308
+ if (windowSummary) return windowSummary;
309
+ if (quota?.resetCreditsAvailable !== undefined) {
310
+ return `resets: ${quota.resetCreditsAvailable}`;
311
+ }
312
+ return 'no quota data';
313
+ }
314
+ function osc52Copy(api, text) {
315
+ try {
316
+ const renderer = api.renderer;
317
+ if (renderer && typeof renderer.copyToClipboardOSC52 === 'function') {
318
+ return renderer.copyToClipboardOSC52(text);
319
+ }
320
+ } catch {
321
+ // best-effort
322
+ }
323
+ return false;
324
+ }
325
+ export function buildAccountDialogRows(state, sessionId) {
326
+ const activeId = resolveSessionSidebarRouting(state, sessionId).activeId;
327
+ return [{
328
+ title: `main${activeId === 'main' ? ' \u2022 active' : ''}`,
329
+ value: 'main',
330
+ description: formatQuotaWindows(state.main.quota)
331
+ }, ...state.fallbacks.map(fb => ({
332
+ title: `${fb.label ?? fb.id}${activeId === fb.id ? ' \u2022 active' : ''}${!fb.enabled ? ' (disabled)' : ''}`,
333
+ value: fb.id,
334
+ description: formatQuotaWindows(fb.quota)
335
+ }))];
336
+ }
337
+ function openAccountDialog(api, apply, sessionId) {
338
+ const DialogConfirm = api.ui.DialogConfirm;
339
+ function showL1() {
340
+ void getSidebarState().then(state => {
341
+ const DialogSelectInner = api.ui.DialogSelect;
342
+ api.ui.dialog.setSize('xlarge');
343
+ api.ui.dialog.replace(() => _$createComponent(DialogSelectInner, {
344
+ title: "OpenAI Accounts",
345
+ get options() {
346
+ return [...buildAccountDialogRows(state, sessionId), {
347
+ title: 'Add account\u2026',
348
+ value: '__add__',
349
+ description: 'Sign in to a new OpenAI account'
350
+ }];
351
+ },
352
+ onSelect: option => {
353
+ if (option.value === '__add__') {
354
+ showAddFlow();
355
+ return;
356
+ }
357
+ // Main has no per-account actions (routing is mode-driven; main is
358
+ // not removable or reorderable) — the row is informational only.
359
+ if (option.value === 'main') return;
360
+ showL2Fallback(option.value);
361
+ }
362
+ }));
363
+ });
364
+ }
365
+ function showL2Fallback(id) {
366
+ void getSidebarState().then(state => {
367
+ const DialogSelectInner = api.ui.DialogSelect;
368
+ const fbIndex = state.fallbacks.findIndex(f => f.id === id);
369
+ const fb = state.fallbacks[fbIndex];
370
+ if (!fb) {
371
+ showL1();
372
+ return;
373
+ }
374
+ const options = [];
375
+ options.push({
376
+ title: 'Remove',
377
+ value: 'remove',
378
+ description: `Remove ${fb.label ?? fb.id}`
379
+ });
380
+ if (fbIndex > 0) {
381
+ const neighbor = state.fallbacks[fbIndex - 1];
382
+ if (neighbor) {
383
+ options.push({
384
+ title: 'Move up',
385
+ value: 'move_up',
386
+ description: `Swap with ${neighbor.label ?? neighbor.id}`
387
+ });
388
+ }
389
+ }
390
+ if (fbIndex < state.fallbacks.length - 1) {
391
+ const neighbor = state.fallbacks[fbIndex + 1];
392
+ if (neighbor) {
393
+ options.push({
394
+ title: 'Move down',
395
+ value: 'move_down',
396
+ description: `Swap with ${neighbor.label ?? neighbor.id}`
397
+ });
398
+ }
399
+ }
400
+ options.push({
401
+ title: 'Back',
402
+ value: 'back',
403
+ description: 'Return to account list'
404
+ });
405
+ api.ui.dialog.setSize('xlarge');
406
+ api.ui.dialog.replace(() => _$createComponent(DialogSelectInner, {
407
+ get title() {
408
+ return fb.label ?? fb.id;
409
+ },
410
+ options: options,
411
+ onSelect: option => {
412
+ if (option.value === 'remove') {
413
+ api.ui.dialog.setSize('xlarge');
414
+ api.ui.dialog.replace(() => _$createComponent(DialogConfirm, {
415
+ title: "Remove account",
416
+ get message() {
417
+ return `Remove ${fb.label ?? fb.id}?`;
418
+ },
419
+ onConfirm: () => {
420
+ void apply('openai-account', `remove ${id}`).then(r => {
421
+ api.ui.toast({
422
+ message: r.text
423
+ });
424
+ showL1();
425
+ });
426
+ },
427
+ onCancel: () => showL2Fallback(id)
428
+ }));
429
+ return;
430
+ }
431
+ if (option.value === 'move_up') {
432
+ const neighbor = state.fallbacks[fbIndex - 1];
433
+ if (neighbor) {
434
+ void apply('openai-account', `order ${id} ${neighbor.id}`).then(r => {
435
+ api.ui.toast({
436
+ message: r.text
437
+ });
438
+ showL1();
439
+ });
440
+ }
441
+ return;
442
+ }
443
+ if (option.value === 'move_down') {
444
+ const neighbor = state.fallbacks[fbIndex + 1];
445
+ if (neighbor) {
446
+ void apply('openai-account', `order ${id} ${neighbor.id}`).then(r => {
447
+ api.ui.toast({
448
+ message: r.text
449
+ });
450
+ showL1();
451
+ });
452
+ }
453
+ return;
454
+ }
455
+ showL1();
456
+ }
457
+ }));
458
+ });
459
+ }
460
+
461
+ // -- Add flow ---------------------------------------------------------------
462
+
463
+ function showAddFlow() {
464
+ const DialogSelectInner = api.ui.DialogSelect;
465
+ api.ui.dialog.setSize('xlarge');
466
+ api.ui.dialog.replace(() => _$createComponent(DialogSelectInner, {
467
+ title: "Add account",
468
+ options: [{
469
+ title: 'Browser sign-in (local)',
470
+ value: 'browser',
471
+ description: 'Opens a browser window for OAuth on this machine'
472
+ }, {
473
+ title: 'Device code (remote / no browser)',
474
+ value: 'device',
475
+ description: 'Enter a code on another device to authorize'
476
+ }, {
477
+ title: 'Back',
478
+ value: 'back',
479
+ description: 'Return to account list'
480
+ }],
481
+ onSelect: option => {
482
+ if (option.value === 'browser') {
483
+ showLabelPrompt('browser');
484
+ return;
485
+ }
486
+ if (option.value === 'device') {
487
+ showLabelPrompt('device');
488
+ return;
489
+ }
490
+ showL1();
491
+ }
492
+ }));
493
+ }
494
+ function showLabelPrompt(mode) {
495
+ const DialogPromptInner = api.ui.DialogPrompt;
496
+ api.ui.dialog.setSize('xlarge');
497
+ api.ui.dialog.replace(() => _$createComponent(DialogPromptInner, {
498
+ title: "Label (optional)",
499
+ description: () => (() => {
500
+ var _el$5 = _$createElement("text");
501
+ _$insertNode(_el$5, _$createTextNode(`Give this account a name (e.g. "work", "personal"). Leave empty to auto-detect.`));
502
+ return _el$5;
503
+ })(),
504
+ placeholder: "e.g. work",
505
+ value: "",
506
+ onConfirm: label => {
507
+ if (mode === 'browser') {
508
+ startBrowserAdd(label.trim() || undefined);
509
+ } else {
510
+ startDeviceAdd(label.trim() || undefined);
511
+ }
512
+ },
513
+ onCancel: () => showAddFlow()
514
+ }));
515
+ }
516
+ function startBrowserAdd(label) {
517
+ const args = label ? `add ${label}` : 'add';
518
+ void apply('openai-account', args).then(r => {
519
+ const url = r.knobs.url;
520
+ const instructions = r.knobs.instructions;
521
+ if (!url) {
522
+ api.ui.toast({
523
+ message: 'Failed to get auth URL',
524
+ variant: 'warning'
525
+ });
526
+ showL1();
527
+ return;
528
+ }
529
+
530
+ // Auto-open the browser (best-effort)
531
+ try {
532
+ openUrl(url);
533
+ } catch {
534
+ // best-effort
535
+ }
536
+ showBrowserAuthScreen(url, instructions);
537
+ });
538
+ }
539
+ function showBrowserAuthScreen(url, _instructions) {
540
+ const DialogSelectInner = api.ui.DialogSelect;
541
+ api.ui.dialog.setSize('xlarge');
542
+ api.ui.dialog.replace(() => _$createComponent(DialogSelectInner, {
543
+ title: "Browser sign-in",
544
+ options: [{
545
+ title: 'Copy auth URL',
546
+ value: 'copy_url',
547
+ description: url
548
+ }, {
549
+ title: 'Open in browser',
550
+ value: 'open',
551
+ description: 'Try to open the URL in your browser'
552
+ }, {
553
+ title: "Done / I've authorized",
554
+ value: 'done',
555
+ description: 'Return to account list'
556
+ }, {
557
+ title: 'Cancel',
558
+ value: 'cancel',
559
+ description: 'Return to account list without adding'
560
+ }],
561
+ skipFilter: true,
562
+ onSelect: option => {
563
+ if (option.value === 'copy_url') {
564
+ const ok = osc52Copy(api, url);
565
+ api.ui.toast({
566
+ message: ok ? 'Auth URL copied' : 'Copy unsupported — select the URL text manually',
567
+ variant: ok ? 'success' : 'warning'
568
+ });
569
+ return;
570
+ }
571
+ if (option.value === 'open') {
572
+ try {
573
+ openUrl(url);
574
+ } catch {
575
+ // best-effort
576
+ }
577
+ return;
578
+ }
579
+ showL1();
580
+ }
581
+ }));
582
+ }
583
+ function startDeviceAdd(label) {
584
+ const args = label ? `add --headless ${label}` : 'add --headless';
585
+ void apply('openai-account', args).then(r => {
586
+ const verificationUrl = r.knobs.verificationUrl;
587
+ const userCode = r.knobs.userCode;
588
+ const instructions = r.knobs.instructions;
589
+ if (!verificationUrl) {
590
+ api.ui.toast({
591
+ message: 'Failed to get device code',
592
+ variant: 'warning'
593
+ });
594
+ showL1();
595
+ return;
596
+ }
597
+ showDeviceCodeScreen(verificationUrl, userCode, instructions);
598
+ });
599
+ }
600
+ function showDeviceCodeScreen(verificationUrl, userCode, _instructions) {
601
+ const DialogSelectInner = api.ui.DialogSelect;
602
+ api.ui.dialog.setSize('xlarge');
603
+ api.ui.dialog.replace(() => _$createComponent(DialogSelectInner, {
604
+ title: "Device code",
605
+ options: [{
606
+ title: userCode ? `Copy code: ${userCode}` : 'Copy code',
607
+ value: 'copy_code',
608
+ description: 'Copy the user code to clipboard'
609
+ }, {
610
+ title: 'Copy URL',
611
+ value: 'copy_url',
612
+ description: verificationUrl
613
+ }, {
614
+ title: "Done / I've authorized",
615
+ value: 'done',
616
+ description: 'Return to account list'
617
+ }, {
618
+ title: 'Cancel',
619
+ value: 'cancel',
620
+ description: 'Return to account list without adding'
621
+ }],
622
+ skipFilter: true,
623
+ onSelect: option => {
624
+ if (option.value === 'copy_code' && userCode) {
625
+ const ok = osc52Copy(api, userCode);
626
+ api.ui.toast({
627
+ message: ok ? 'Code copied' : 'Copy unsupported — enter the code manually',
628
+ variant: ok ? 'success' : 'warning'
629
+ });
630
+ return;
631
+ }
632
+ if (option.value === 'copy_url') {
633
+ const ok = osc52Copy(api, verificationUrl);
634
+ api.ui.toast({
635
+ message: ok ? 'URL copied' : 'Copy unsupported — enter the URL manually',
636
+ variant: ok ? 'success' : 'warning'
637
+ });
638
+ return;
639
+ }
640
+ showL1();
641
+ }
642
+ }));
643
+ }
644
+ void showL1();
645
+ }