@cortexkit/opencode-openai-auth 0.11.1 → 1.0.1

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 (204) hide show
  1. package/README.md +15 -26
  2. package/dist/index-766sfrh5.js +1 -0
  3. package/dist/index-8kj7sa1p.js +12 -0
  4. package/dist/index-b3q3j39x.js +2 -0
  5. package/dist/index-bmd7x5aq.js +2 -0
  6. package/dist/index-eqh53k8v.js +2 -0
  7. package/dist/{index-sdt2d2ra.js → index-hy9030jh.js} +1 -1
  8. package/dist/index-jyr8y8k4.js +7 -0
  9. package/dist/index-rv08jmd4.js +2 -0
  10. package/dist/index-ww91hyzr.js +221 -0
  11. package/dist/index.d.ts +578 -145
  12. package/dist/index.js +1 -110
  13. package/dist/pool-lifecycle-DbDmgabd.d.ts +2430 -0
  14. package/dist/port-file-Do3lQIEk.d.ts +29 -0
  15. package/dist/protocol-BOBdkqqm.d.ts +198 -0
  16. package/dist/rpc/port-file.d.ts +4 -17
  17. package/dist/rpc/port-file.js +1 -1
  18. package/dist/rpc/protocol.d.ts +2 -2
  19. package/dist/rpc/protocol.js +1 -0
  20. package/dist/rpc/rpc-client.d.ts +5 -4
  21. package/dist/rpc/rpc-client.js +1 -1
  22. package/dist/rpc/rpc-dir.d.ts +2 -0
  23. package/dist/rpc/rpc-dir.js +1 -1
  24. package/dist/sidebar-state-D6cUKgkm.d.ts +385 -0
  25. package/dist/sidebar-state.d.ts +2 -2
  26. package/dist/sidebar-state.js +1 -1
  27. package/dist/token-util-wdza526f.js +1 -0
  28. package/dist/{token-9823y0pc.js → token-ypk61xj0.js} +1 -1
  29. package/dist/tui-preferences.js +5 -2
  30. package/dist/tui.d.ts +9 -4
  31. package/dist/v2/server.d.ts +54 -0
  32. package/dist/v2/server.js +4 -0
  33. package/package.json +21 -9
  34. package/src/tui/entry.mjs +26 -25
  35. package/src/tui-compiled/raw/logger.js +24 -0
  36. package/src/tui-compiled/raw/rpc/protocol.js +3 -0
  37. package/src/tui-compiled/raw/rpc/rpc-client.js +7 -0
  38. package/src/tui-compiled/raw/rpc/rpc-dir.js +27 -0
  39. package/src/tui-compiled/raw/selector.js +21 -0
  40. package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +138 -0
  41. package/src/tui-compiled/raw/shared/04a6357627de5ed0-errors.js +38 -0
  42. package/src/tui-compiled/raw/shared/053cc17b5419261e-codec.js +12 -0
  43. package/src/tui-compiled/raw/shared/0766d78c6f481f18-watcher.js +157 -0
  44. package/src/tui-compiled/raw/shared/08eef45626a94992-reserved.js +13 -0
  45. package/src/tui-compiled/raw/shared/39c3cb05738fcf6d-projection.js +134 -0
  46. package/src/tui-compiled/raw/shared/3c2d4169d00f907e-tui-support.js +5 -0
  47. package/src/tui-compiled/raw/shared/3e7d45a77c6ad4d5-index.js +4 -0
  48. package/src/tui-compiled/raw/shared/415792e85d80819e-capture-sink.js +13 -0
  49. package/src/tui-compiled/raw/shared/42ae93ab554fcec2-open-url.js +28 -0
  50. package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
  51. package/src/tui-compiled/raw/shared/458b934e92490331-mutate.js +500 -0
  52. package/src/tui-compiled/raw/shared/45bf5343bbbf9add-admission.js +142 -0
  53. package/src/tui-compiled/raw/shared/46f3dff1266160e8-client.js +2 -0
  54. package/src/tui-compiled/raw/shared/4825e03209d6ae3e-with-lock.js +48 -0
  55. package/src/tui-compiled/raw/shared/4f36d333d081d040-merge.js +162 -0
  56. package/src/tui-compiled/raw/shared/54eba94fd64c8c3c-index.js +7 -0
  57. package/src/tui-compiled/raw/shared/7211cfec06a298bc-atomic-write.js +80 -0
  58. package/src/tui-compiled/raw/shared/754d6017f8e419cf-builtins.js +534 -0
  59. package/src/tui-compiled/raw/shared/75bd8c3bd63b315f-identity.js +79 -0
  60. package/src/tui-compiled/raw/shared/7a91bb630e6f4804-index.js +1 -0
  61. package/src/tui-compiled/raw/shared/826342a4b6a8b89b-model.js +16 -0
  62. package/src/tui-compiled/raw/shared/83068c0dbe8a6f13-publication.js +304 -0
  63. package/src/tui-compiled/raw/shared/836fc615de6afbd9-settings.js +120 -0
  64. package/src/tui-compiled/raw/shared/83bc3ba9d0fe2761-runtime.js +68 -0
  65. package/src/tui-compiled/raw/shared/8a18abe4fa2977c8-menu.js +300 -0
  66. package/src/tui-compiled/raw/shared/901e149c5a0dbbff-sticky.js +339 -0
  67. package/src/tui-compiled/raw/shared/9020c407ce64f672-ordered.js +58 -0
  68. package/src/tui-compiled/raw/shared/905bffc0220f43d0-map.js +105 -0
  69. package/src/tui-compiled/raw/shared/94820e324f2b4c09-seam.js +301 -0
  70. package/src/tui-compiled/raw/shared/97484d9bb98c705b-provider-state.js +226 -0
  71. package/src/tui-compiled/raw/shared/9968e3cade6969a1-index.js +2 -0
  72. package/src/tui-compiled/raw/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
  73. package/src/tui-compiled/raw/shared/9d3fc9e37456504e-refresh.js +225 -0
  74. package/src/tui-compiled/raw/shared/9deae3c4da4fd63b-hooks.js +34 -0
  75. package/src/tui-compiled/raw/shared/a09b6a533c11858f-tui-preferences.js +77 -0
  76. package/src/tui-compiled/raw/shared/a62a27a204266acd-attribution.js +60 -0
  77. package/src/tui-compiled/raw/shared/a910277ac43a8626-pull.js +143 -0
  78. package/src/tui-compiled/raw/shared/b85c5301fddcb98a-paths.js +5 -0
  79. package/src/tui-compiled/raw/shared/c14988621dc60a75-port-file.js +217 -0
  80. package/src/tui-compiled/raw/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
  81. package/src/tui-compiled/raw/shared/c6b176708c446d6d-pins.js +34 -0
  82. package/src/tui-compiled/raw/shared/c7d252bedee01600-pi.js +206 -0
  83. package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +120 -0
  84. package/src/tui-compiled/raw/shared/cc9e0702a752cea7-index.js +3 -0
  85. package/src/tui-compiled/raw/shared/cd94316a888b0a8e-schema.js +754 -0
  86. package/src/tui-compiled/raw/shared/d319517e3ea6ad70-redact.js +83 -0
  87. package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +5 -0
  88. package/src/tui-compiled/raw/shared/d669d72c40e9e116-logger.js +19 -0
  89. package/src/tui-compiled/raw/shared/d7dcba96fea1685c-engine.js +246 -0
  90. package/src/tui-compiled/raw/shared/dbef697d1df1cd84-index.js +5 -0
  91. package/src/tui-compiled/raw/shared/e4acc4cb713f300c-protocol.js +3 -0
  92. package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
  93. package/src/tui-compiled/raw/shared/ec971142548df90e-index.js +6 -0
  94. package/src/tui-compiled/raw/shared/f37fcefc7aedbf25-error.js +11 -0
  95. package/src/tui-compiled/raw/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
  96. package/src/tui-compiled/raw/shared/fd45e82f2f9c0a0d-torn.js +294 -0
  97. package/src/tui-compiled/raw/shared/fe02abeadc535f77-rows.js +1258 -0
  98. package/src/tui-compiled/raw/sidebar-state.js +960 -0
  99. package/src/tui-compiled/raw/tui/command-dialogs.tsx +424 -0
  100. package/src/tui-compiled/raw/tui-preferences.js +135 -0
  101. package/src/tui-compiled/raw/tui.tsx +135 -143
  102. package/src/tui-compiled/runtime/logger.js +24 -0
  103. package/src/tui-compiled/runtime/rpc/protocol.js +3 -0
  104. package/src/tui-compiled/runtime/rpc/rpc-client.js +7 -0
  105. package/src/tui-compiled/runtime/rpc/rpc-dir.js +27 -0
  106. package/src/tui-compiled/runtime/selector.js +21 -0
  107. package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +138 -0
  108. package/src/tui-compiled/runtime/shared/04a6357627de5ed0-errors.js +38 -0
  109. package/src/tui-compiled/runtime/shared/053cc17b5419261e-codec.js +12 -0
  110. package/src/tui-compiled/runtime/shared/0766d78c6f481f18-watcher.js +157 -0
  111. package/src/tui-compiled/runtime/shared/08eef45626a94992-reserved.js +13 -0
  112. package/src/tui-compiled/runtime/shared/39c3cb05738fcf6d-projection.js +134 -0
  113. package/src/tui-compiled/runtime/shared/3c2d4169d00f907e-tui-support.js +5 -0
  114. package/src/tui-compiled/runtime/shared/3e7d45a77c6ad4d5-index.js +4 -0
  115. package/src/tui-compiled/runtime/shared/415792e85d80819e-capture-sink.js +13 -0
  116. package/src/tui-compiled/runtime/shared/42ae93ab554fcec2-open-url.js +28 -0
  117. package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
  118. package/src/tui-compiled/runtime/shared/458b934e92490331-mutate.js +500 -0
  119. package/src/tui-compiled/runtime/shared/45bf5343bbbf9add-admission.js +142 -0
  120. package/src/tui-compiled/runtime/shared/46f3dff1266160e8-client.js +2 -0
  121. package/src/tui-compiled/runtime/shared/4825e03209d6ae3e-with-lock.js +48 -0
  122. package/src/tui-compiled/runtime/shared/4f36d333d081d040-merge.js +162 -0
  123. package/src/tui-compiled/runtime/shared/54eba94fd64c8c3c-index.js +7 -0
  124. package/src/tui-compiled/runtime/shared/7211cfec06a298bc-atomic-write.js +80 -0
  125. package/src/tui-compiled/runtime/shared/754d6017f8e419cf-builtins.js +534 -0
  126. package/src/tui-compiled/runtime/shared/75bd8c3bd63b315f-identity.js +79 -0
  127. package/src/tui-compiled/runtime/shared/7a91bb630e6f4804-index.js +1 -0
  128. package/src/tui-compiled/runtime/shared/826342a4b6a8b89b-model.js +16 -0
  129. package/src/tui-compiled/runtime/shared/83068c0dbe8a6f13-publication.js +304 -0
  130. package/src/tui-compiled/runtime/shared/836fc615de6afbd9-settings.js +120 -0
  131. package/src/tui-compiled/runtime/shared/83bc3ba9d0fe2761-runtime.js +68 -0
  132. package/src/tui-compiled/runtime/shared/8a18abe4fa2977c8-menu.js +300 -0
  133. package/src/tui-compiled/runtime/shared/901e149c5a0dbbff-sticky.js +339 -0
  134. package/src/tui-compiled/runtime/shared/9020c407ce64f672-ordered.js +58 -0
  135. package/src/tui-compiled/runtime/shared/905bffc0220f43d0-map.js +105 -0
  136. package/src/tui-compiled/runtime/shared/94820e324f2b4c09-seam.js +301 -0
  137. package/src/tui-compiled/runtime/shared/97484d9bb98c705b-provider-state.js +226 -0
  138. package/src/tui-compiled/runtime/shared/9968e3cade6969a1-index.js +2 -0
  139. package/src/tui-compiled/runtime/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
  140. package/src/tui-compiled/runtime/shared/9d3fc9e37456504e-refresh.js +225 -0
  141. package/src/tui-compiled/runtime/shared/9deae3c4da4fd63b-hooks.js +34 -0
  142. package/src/tui-compiled/runtime/shared/a09b6a533c11858f-tui-preferences.js +77 -0
  143. package/src/tui-compiled/runtime/shared/a62a27a204266acd-attribution.js +60 -0
  144. package/src/tui-compiled/runtime/shared/a910277ac43a8626-pull.js +143 -0
  145. package/src/tui-compiled/runtime/shared/b85c5301fddcb98a-paths.js +5 -0
  146. package/src/tui-compiled/runtime/shared/c14988621dc60a75-port-file.js +217 -0
  147. package/src/tui-compiled/runtime/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
  148. package/src/tui-compiled/runtime/shared/c6b176708c446d6d-pins.js +34 -0
  149. package/src/tui-compiled/runtime/shared/c7d252bedee01600-pi.js +206 -0
  150. package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +120 -0
  151. package/src/tui-compiled/runtime/shared/cc9e0702a752cea7-index.js +3 -0
  152. package/src/tui-compiled/runtime/shared/cd94316a888b0a8e-schema.js +754 -0
  153. package/src/tui-compiled/runtime/shared/d319517e3ea6ad70-redact.js +83 -0
  154. package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +5 -0
  155. package/src/tui-compiled/runtime/shared/d669d72c40e9e116-logger.js +19 -0
  156. package/src/tui-compiled/runtime/shared/d7dcba96fea1685c-engine.js +246 -0
  157. package/src/tui-compiled/runtime/shared/dbef697d1df1cd84-index.js +5 -0
  158. package/src/tui-compiled/runtime/shared/e4acc4cb713f300c-protocol.js +3 -0
  159. package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
  160. package/src/tui-compiled/runtime/shared/ec971142548df90e-index.js +6 -0
  161. package/src/tui-compiled/runtime/shared/f37fcefc7aedbf25-error.js +11 -0
  162. package/src/tui-compiled/runtime/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
  163. package/src/tui-compiled/runtime/shared/fd45e82f2f9c0a0d-torn.js +294 -0
  164. package/src/tui-compiled/runtime/shared/fe02abeadc535f77-rows.js +1258 -0
  165. package/src/tui-compiled/runtime/sidebar-state.js +960 -0
  166. package/src/tui-compiled/runtime/tui/command-dialogs.js +378 -0
  167. package/src/tui-compiled/runtime/tui-preferences.js +135 -0
  168. package/src/tui-compiled/runtime/{tui.tsx → tui.js} +288 -316
  169. package/dist/index-4effjmr6.js +0 -2
  170. package/dist/index-9ehhb051.js +0 -2
  171. package/dist/index-g3hsdjh4.js +0 -2
  172. package/dist/index-hh52v8sb.js +0 -2
  173. package/dist/index-jwn4r3sw.js +0 -1
  174. package/dist/index-sq36b120.js +0 -211
  175. package/dist/internal-BTndPmW2.d.ts +0 -914
  176. package/dist/quota-normalize-08hpp51v.js +0 -1
  177. package/dist/sidebar-state-CoKYKcLn.d.ts +0 -220
  178. package/dist/token-util-ypgz7rge.js +0 -1
  179. package/src/tui-compiled/raw/core-account-paths.ts +0 -114
  180. package/src/tui-compiled/raw/core-custody-state.ts +0 -243
  181. package/src/tui-compiled/raw/logger.ts +0 -53
  182. package/src/tui-compiled/raw/rpc-port-file.ts +0 -177
  183. package/src/tui-compiled/raw/rpc-protocol.ts +0 -12
  184. package/src/tui-compiled/raw/rpc-rpc-client.ts +0 -82
  185. package/src/tui-compiled/raw/rpc-rpc-dir.ts +0 -46
  186. package/src/tui-compiled/raw/sidebar-state.ts +0 -1659
  187. package/src/tui-compiled/raw/tui-command-dialogs.tsx +0 -1096
  188. package/src/tui-compiled/raw/tui-preferences.ts +0 -328
  189. package/src/tui-compiled/runtime/core-account-paths.ts +0 -114
  190. package/src/tui-compiled/runtime/core-custody-state.ts +0 -243
  191. package/src/tui-compiled/runtime/logger.ts +0 -53
  192. package/src/tui-compiled/runtime/rpc-port-file.ts +0 -177
  193. package/src/tui-compiled/runtime/rpc-protocol.ts +0 -12
  194. package/src/tui-compiled/runtime/rpc-rpc-client.ts +0 -82
  195. package/src/tui-compiled/runtime/rpc-rpc-dir.ts +0 -46
  196. package/src/tui-compiled/runtime/sidebar-state.ts +0 -1659
  197. package/src/tui-compiled/runtime/tui-command-dialogs.tsx +0 -855
  198. package/src/tui-compiled/runtime/tui-preferences.ts +0 -328
  199. package/src/tui-compiled/shared/internal.ts +0 -6
  200. package/src/tui-compiled/shared/logger.ts +0 -264
  201. package/src/tui-compiled/shared/paths.ts +0 -33
  202. package/src/tui-compiled/shared/refresh-file-lock.ts +0 -384
  203. package/src/tui-compiled/shared/util/error.ts +0 -9
  204. package/src/tui-compiled/shared/util/open-url.ts +0 -30
@@ -1,1096 +0,0 @@
1
- /** @jsxImportSource @opentui/solid */
2
-
3
- import { errorMessage, openUrl } from '../shared/internal.ts'
4
- import type { TuiPluginApi } from '@opencode-ai/plugin/tui'
5
- import { createLogger } from './logger.ts'
6
- import type { OpenDialogPayload } from './rpc-protocol.ts'
7
- import {
8
- type AccountQuota,
9
- getCollapsedQuotaSummary,
10
- getSidebarState,
11
- resolveSessionSidebarRouting,
12
- type SidebarState,
13
- } from './sidebar-state.ts'
14
-
15
- const log = createLogger('rpc-tui')
16
-
17
- type ApplyFn = (
18
- command: OpenDialogPayload['command'],
19
- args: string,
20
- ) => Promise<{ text: string; knobs: Record<string, unknown> }>
21
-
22
- export function buildCachekeepDialogOptions(payload: OpenDialogPayload) {
23
- const enabled = payload.knobs.enabled === true
24
- const subagents = payload.knobs.subagents === true
25
- const sustain = payload.knobs.sustain === true
26
- const running = payload.knobs.running === true
27
- const tracked = Number(payload.knobs.tracked ?? 0)
28
- const generatedAt = Number(payload.knobs.generatedAt ?? Date.now())
29
- const lastWarmAt = Number(payload.knobs.lastWarmAt ?? 0)
30
- const maxIdleWarmMs = Number(payload.knobs.maxIdleWarmMs ?? 60 * 60 * 1000)
31
- const maxSubagentIdleMs = Number(
32
- payload.knobs.maxSubagentIdleMs ?? 30 * 60 * 1000,
33
- )
34
- const windowKnob = payload.knobs.window as
35
- | { startHour: number; endHour: number }
36
- | undefined
37
- const windowLabel = windowKnob
38
- ? `${String(windowKnob.startHour).padStart(2, '0')}-${String(windowKnob.endHour).padStart(2, '0')}`
39
- : 'always (no window)'
40
- const lastWarm = lastWarmAt
41
- ? `${Math.ceil((generatedAt - lastWarmAt) / 1000)}s ago`
42
- : 'none yet'
43
- const idleWindow = Math.round(maxIdleWarmMs / 60_000)
44
- const subIdleWindow = Math.round(maxSubagentIdleMs / 60_000)
45
- const statusParts = [
46
- enabled ? '● Enabled' : '○ Disabled',
47
- `timer ${running ? 'armed' : 'idle'}`,
48
- `${tracked} tracked`,
49
- `last warm ${lastWarm}`,
50
- `window ${windowLabel}`,
51
- `${idleWindow}m idle cap`,
52
- `sustain ${sustain ? 'on' : 'off'}`,
53
- `subagent idle ${subIdleWindow}m`,
54
- ].filter((part) => part.length > 0)
55
-
56
- return [
57
- {
58
- title: statusParts.join(' | '),
59
- value: 'status',
60
- description: 'Current cachekeep state',
61
- },
62
- {
63
- title: enabled ? 'Turn off' : 'Turn on',
64
- value: enabled ? 'off' : 'on',
65
- description: enabled
66
- ? 'Stop capturing and warming prompt-cache prefixes'
67
- : 'Persistently enable capture; the timer self-arms on request capture',
68
- },
69
- {
70
- title: subagents ? 'Subagent warming: on' : 'Subagent warming: off',
71
- value: subagents ? 'subagents off' : 'subagents on',
72
- description: subagents
73
- ? `Warm subagent sessions (${subIdleWindow}m idle cap). Disable to skip subagents.`
74
- : `Skip subagent sessions. Enable to warm them too (${subIdleWindow}m idle cap).`,
75
- },
76
- {
77
- title: sustain
78
- ? 'Sustain main sessions: on'
79
- : 'Sustain main sessions: off',
80
- value: sustain ? 'sustain off' : 'sustain on',
81
- description:
82
- 'main-only, idle-cap only; the configured clock window still applies.',
83
- },
84
- {
85
- title: windowKnob ? `Warm window: ${windowLabel}` : 'Set warm window…',
86
- value: 'set_window',
87
- description: windowKnob
88
- ? `Currently only warming between ${windowLabel} local hours — pick to change`
89
- : 'Restrict capture + warm to a clock-hour window, e.g. 9-18 or 22-6',
90
- },
91
- {
92
- title: 'Clear warm window',
93
- value: 'clear_window',
94
- description: windowKnob
95
- ? 'Remove the clock-hour restriction — return to always-warm (within idle caps)'
96
- : 'No clock-hour window is currently set',
97
- },
98
- {
99
- title: 'Refresh status',
100
- value: 'refresh',
101
- description: 'Re-read cachekeep status',
102
- },
103
- {
104
- title: 'Close',
105
- value: 'close',
106
- description: 'Close this dialog',
107
- },
108
- ]
109
- }
110
-
111
- type ResetAccountKnob = {
112
- accountKey: string
113
- label: string
114
- usedPercent?: number
115
- availableCount?: number
116
- applicableAvailableCount?: number
117
- eligible: boolean
118
- reason?: string
119
- selectedCreditId?: string
120
- selectedCreditExpiresAt?: string
121
- }
122
-
123
- type ResetPreviewKnob = ResetAccountKnob & {
124
- chatgptAccountId?: string
125
- resetTime?: string
126
- }
127
-
128
- type ResetDialogOption = {
129
- title: string
130
- value: string
131
- description: string
132
- disabled?: boolean
133
- }
134
-
135
- function resetAccountOptions(payload: OpenDialogPayload): ResetDialogOption[] {
136
- const accounts = Array.isArray(payload.knobs.accounts)
137
- ? (payload.knobs.accounts as ResetAccountKnob[])
138
- : []
139
- return [
140
- ...accounts.map((account) => {
141
- const used =
142
- account.usedPercent === undefined
143
- ? 'quota unavailable'
144
- : `${account.usedPercent}%`
145
- const counts = `${account.applicableAvailableCount === undefined ? '?' : account.applicableAvailableCount}/${account.availableCount ?? '?'}`
146
- const status = account.eligible
147
- ? 'eligible'
148
- : (account.reason ?? 'unavailable')
149
- const details = `${used} · ${counts}`
150
- const shortDate =
151
- account.selectedCreditExpiresAt?.slice(0, 10) ?? 'unknown'
152
- return {
153
- title: `${account.label} — ${status}`,
154
- value: `account:${account.accountKey}`,
155
- description: account.eligible
156
- ? `${details} · exp ${shortDate}`
157
- : details,
158
- }
159
- }),
160
- {
161
- title: 'Refresh',
162
- value: 'refresh',
163
- description: 'Re-check account quota and reset credits',
164
- },
165
- {
166
- title: 'Close',
167
- value: 'close',
168
- description: 'Close this dialog',
169
- },
170
- ]
171
- }
172
-
173
- function resetResultOptions(payload: OpenDialogPayload): ResetDialogOption[] {
174
- const accountKey =
175
- typeof payload.knobs.accountKey === 'string'
176
- ? payload.knobs.accountKey
177
- : undefined
178
- const chatgptAccountId =
179
- typeof payload.knobs.chatgptAccountId === 'string'
180
- ? payload.knobs.chatgptAccountId
181
- : undefined
182
- const code = typeof payload.knobs.code === 'string' ? payload.knobs.code : ''
183
- const retrySafe =
184
- (code === 'ambiguous' ||
185
- code === 'http_error' ||
186
- code === 'expired_unreconciled' ||
187
- payload.knobs.stateWriteFailed === true) &&
188
- typeof payload.knobs.retryGuidance === 'string' &&
189
- accountKey !== undefined &&
190
- chatgptAccountId !== undefined
191
- return [
192
- {
193
- title: payload.text,
194
- value: 'result',
195
- description: 'Command result',
196
- },
197
- ...(retrySafe
198
- ? [
199
- {
200
- title: 'Retry',
201
- value: 'retry',
202
- description:
203
- 'Reuse the bound request and credit identifiers for this uncertain outcome',
204
- },
205
- ]
206
- : []),
207
- {
208
- title: 'Refresh',
209
- value: 'refresh',
210
- description: 'Re-check account quota and reset credits',
211
- },
212
- {
213
- title: 'Close',
214
- value: 'close',
215
- description: 'Close this dialog',
216
- },
217
- ]
218
- }
219
-
220
- function openResetDialog(
221
- api: TuiPluginApi,
222
- payload: OpenDialogPayload,
223
- apply: ApplyFn,
224
- ) {
225
- let applyGeneration = 0
226
- const render = (state: OpenDialogPayload) => {
227
- const applyAndRender = (args: string) => {
228
- const generation = ++applyGeneration
229
- log.debug('reset dialog apply', { args, generation })
230
- void apply('openai-reset', args)
231
- .then((result) => {
232
- log.debug('reset dialog apply result', {
233
- generation,
234
- currentGeneration: applyGeneration,
235
- stage: result.knobs?.stage,
236
- accountRows: Array.isArray(result.knobs?.accounts)
237
- ? (result.knobs.accounts as unknown[]).length
238
- : undefined,
239
- })
240
- if (generation !== applyGeneration) return
241
- if (typeof result.knobs?.stage !== 'string') {
242
- render({
243
- command: 'openai-reset',
244
- text: 'The reset request is still completing or returned no result. Choose Refresh to check the current state — do NOT re-confirm.',
245
- knobs: { stage: 'result', code: 'unknown' },
246
- })
247
- return
248
- }
249
- render({
250
- command: 'openai-reset',
251
- text: result.text,
252
- knobs: result.knobs,
253
- })
254
- })
255
- .catch((error) => {
256
- if (generation !== applyGeneration) return
257
- log.warn('reset dialog apply rejected', {
258
- error: errorMessage(error),
259
- })
260
- render({
261
- command: 'openai-reset',
262
- text: 'Command failed — the plugin could not complete the request; the plugin log has details.',
263
- knobs: { stage: 'result', code: 'error' },
264
- })
265
- })
266
- }
267
- const stage = state.knobs.stage
268
- log.debug('reset dialog render', {
269
- stage,
270
- accountRows: Array.isArray(state.knobs.accounts)
271
- ? (state.knobs.accounts as unknown[]).length
272
- : undefined,
273
- knobKeys: Object.keys(state.knobs),
274
- })
275
- api.ui.dialog.setSize('xlarge')
276
-
277
- if (stage === 'confirm') {
278
- const preview = state.knobs.preview as ResetPreviewKnob | undefined
279
- const accountKey = preview?.accountKey
280
- const chatgptAccountId = preview?.chatgptAccountId
281
- const availableCount = preview?.availableCount ?? 'unknown'
282
- const DialogConfirm = api.ui.DialogConfirm
283
- api.ui.dialog.replace(() => (
284
- <DialogConfirm
285
- title='Reset quota window'
286
- message={`${state.text}\n\nThis SPENDS 1 of ${availableCount} reset credits — irreversible.\n\nChoose Reset to continue or Cancel to return.\n\nEnter = Cancel (host default). Press Tab then Enter to Reset.`}
287
- onConfirm={() => {
288
- if (!accountKey || !chatgptAccountId) return
289
- applyAndRender(
290
- `confirm ${encodeURIComponent(accountKey)} ${encodeURIComponent(chatgptAccountId)}`,
291
- )
292
- }}
293
- onCancel={() => applyAndRender('refresh')}
294
- />
295
- ))
296
- return
297
- }
298
-
299
- const DialogSelect = api.ui.DialogSelect<string>
300
- const options =
301
- stage === 'result'
302
- ? resetResultOptions(state)
303
- : resetAccountOptions(state)
304
- api.ui.dialog.replace(() => (
305
- <DialogSelect
306
- title={
307
- stage === 'result' ? 'OpenAI reset result' : 'OpenAI reset credits'
308
- }
309
- options={options}
310
- onSelect={(option) => {
311
- if (option.disabled) return
312
- if (option.value === 'result') return
313
- if (option.value === 'close') {
314
- applyGeneration += 1
315
- api.ui.dialog.clear()
316
- return
317
- }
318
- if (option.value === 'refresh') {
319
- applyAndRender('refresh')
320
- return
321
- }
322
- if (option.value === 'retry') {
323
- const accountKey = state.knobs.accountKey
324
- const chatgptAccountId = state.knobs.chatgptAccountId
325
- if (
326
- typeof accountKey !== 'string' ||
327
- typeof chatgptAccountId !== 'string'
328
- ) {
329
- return
330
- }
331
- applyAndRender(
332
- `retry ${encodeURIComponent(accountKey)} ${encodeURIComponent(chatgptAccountId)}`,
333
- )
334
- return
335
- }
336
- if (!option.value.startsWith('account:')) return
337
- const accountKey = option.value.slice('account:'.length)
338
- applyAndRender(`select ${encodeURIComponent(accountKey)}`)
339
- }}
340
- />
341
- ))
342
- }
343
-
344
- render(payload)
345
- }
346
-
347
- function showText(api: TuiPluginApi, text: string) {
348
- api.ui.dialog.setSize('xlarge')
349
- api.ui.dialog.replace(() => (
350
- <box flexDirection='column' padding={1} width='100%'>
351
- <text>{text}</text>
352
- </box>
353
- ))
354
- }
355
-
356
- function showSetCachekeepWindowPrompt(
357
- api: TuiPluginApi,
358
- apply: ApplyFn,
359
- state: OpenDialogPayload,
360
- render: (state: OpenDialogPayload) => void,
361
- ) {
362
- const DialogPrompt = api.ui.DialogPrompt
363
- const knobWindow = state.knobs.window as
364
- | { startHour: number; endHour: number }
365
- | undefined
366
- const seed = knobWindow ? `${knobWindow.startHour}-${knobWindow.endHour}` : ''
367
- api.ui.dialog.setSize('xlarge')
368
- api.ui.dialog.replace(() => (
369
- <DialogPrompt
370
- title='Cachekeep warm window'
371
- description={() => <text>{state.text}</text>}
372
- placeholder='HH-HH (e.g. 9-18 or 22-6)'
373
- value={seed}
374
- onConfirm={(value: string) => {
375
- const trimmed = value.trim()
376
- if (!trimmed) {
377
- render(state)
378
- return
379
- }
380
- void apply('openai-cachekeep', trimmed).then((r) => {
381
- api.ui.toast({ message: r.text })
382
- render({
383
- command: 'openai-cachekeep',
384
- text: r.text,
385
- knobs: r.knobs,
386
- })
387
- })
388
- }}
389
- onCancel={() => render(state)}
390
- />
391
- ))
392
- }
393
-
394
- export function openCommandDialog(
395
- api: TuiPluginApi,
396
- payload: OpenDialogPayload,
397
- apply: ApplyFn,
398
- sessionId?: string,
399
- ) {
400
- if (payload.command === 'openai-reset') {
401
- openResetDialog(api, payload, apply)
402
- return
403
- }
404
-
405
- if (payload.command === 'openai-routing') {
406
- const current = (payload.knobs.mode as string) ?? 'main-first'
407
- const DialogSelect = api.ui.DialogSelect<string>
408
- api.ui.dialog.setSize('xlarge')
409
- api.ui.dialog.replace(() => (
410
- <DialogSelect
411
- title='OpenAI routing'
412
- current={current}
413
- options={[
414
- {
415
- title: 'Main first',
416
- value: 'main-first',
417
- description: 'Use the main account until exhausted',
418
- },
419
- {
420
- title: 'Fallback first',
421
- value: 'fallback-first',
422
- description: 'Prefer fallback accounts, preserve main',
423
- },
424
- {
425
- title: 'Sticky balanced',
426
- value: 'sticky-balanced',
427
- description:
428
- 'Keep each session on one account while balancing new sessions',
429
- },
430
- {
431
- title: "Reset this session's pin",
432
- value: 'reset',
433
- description:
434
- 'Unpin this session; the next request may still choose the same account',
435
- },
436
- ]}
437
- onSelect={(option) => {
438
- void apply('openai-routing', String(option.value)).then((r) => {
439
- api.ui.toast({ message: r.text })
440
- api.ui.dialog.clear()
441
- })
442
- }}
443
- />
444
- ))
445
- return
446
- }
447
-
448
- if (payload.command === 'openai-dump') {
449
- const enabled = payload.knobs.enabled === true
450
- const DialogConfirm = api.ui.DialogConfirm
451
- api.ui.dialog.setSize('xlarge')
452
- api.ui.dialog.replace(() => (
453
- <DialogConfirm
454
- title='OpenAI request dump'
455
- message={`${payload.text}\n\n${enabled ? 'Disable' : 'Enable'} request dump?`}
456
- onConfirm={() => {
457
- void apply('openai-dump', enabled ? 'off' : 'on').then((r) => {
458
- api.ui.toast({ message: r.text })
459
- api.ui.dialog.clear()
460
- })
461
- }}
462
- onCancel={() => api.ui.dialog.clear()}
463
- />
464
- ))
465
- return
466
- }
467
-
468
- if (payload.command === 'openai-killswitch') {
469
- const config = (payload.knobs.config ?? {}) as {
470
- enabled?: boolean
471
- main?: Record<string, number>
472
- accounts?: Record<string, Record<string, number>>
473
- }
474
- const accountIds = (payload.knobs.accountIds as string[]) ?? []
475
- const enabled = config.enabled === true
476
- const readT = (t: Record<string, number> | undefined) => {
477
- const fh = t?.primary ?? t?.['5h'] ?? 5
478
- const sd = t?.secondary ?? t?.['1w'] ?? 10
479
- return { fh, sd }
480
- }
481
- const mainT = readT(config.main)
482
- const seedParts = [`main:${mainT.fh},${mainT.sd}`]
483
- for (const id of accountIds) {
484
- const t = readT(config.accounts?.[id] ?? config.main)
485
- seedParts.push(`${id}:${t.fh},${t.sd}`)
486
- }
487
- const seed = seedParts.join(' ')
488
-
489
- const openEdit = () => {
490
- const DialogPrompt = api.ui.DialogPrompt
491
- api.ui.dialog.setSize('xlarge')
492
- api.ui.dialog.replace(() => (
493
- <DialogPrompt
494
- title='Killswitch thresholds'
495
- description={() => <text>{payload.text}</text>}
496
- placeholder='main:5,10 work-alt:5,10'
497
- value={seed}
498
- onConfirm={(value: string) => {
499
- void apply('openai-killswitch', `set ${value.trim()}`).then((r) => {
500
- api.ui.toast({ message: r.text })
501
- api.ui.dialog.clear()
502
- })
503
- }}
504
- onCancel={() => api.ui.dialog.clear()}
505
- />
506
- ))
507
- }
508
-
509
- const DialogSelect = api.ui.DialogSelect<string>
510
- api.ui.dialog.setSize('xlarge')
511
- api.ui.dialog.replace(() => (
512
- <DialogSelect
513
- title='OpenAI killswitch'
514
- options={[
515
- {
516
- title: enabled ? 'Disable killswitch' : 'Enable killswitch',
517
- value: enabled ? 'off' : 'on',
518
- description: enabled
519
- ? 'Stop hard-blocking on low quota'
520
- : 'Hard-block requests when quota drops below thresholds',
521
- },
522
- {
523
- title: 'Edit thresholds\u2026',
524
- value: 'edit',
525
- description: 'Set per-account primary,secondary cutoffs',
526
- },
527
- ]}
528
- onSelect={(option) => {
529
- if (option.value === 'edit') {
530
- openEdit()
531
- return
532
- }
533
- void apply('openai-killswitch', String(option.value)).then((r) => {
534
- api.ui.toast({ message: r.text })
535
- api.ui.dialog.clear()
536
- })
537
- }}
538
- />
539
- ))
540
- return
541
- }
542
-
543
- if (payload.command === 'openai-logging') {
544
- const current = (payload.knobs.level as string) ?? 'info'
545
- const levels = ['error', 'warn', 'info', 'debug', 'trace']
546
- const DialogSelect = api.ui.DialogSelect<string>
547
- api.ui.dialog.setSize('xlarge')
548
- api.ui.dialog.replace(() => (
549
- <DialogSelect
550
- title='OpenAI logging'
551
- current={current}
552
- options={levels.map((level) => ({
553
- title: level,
554
- value: level,
555
- description:
556
- level === current
557
- ? 'currently active'
558
- : `Set log level to ${level}`,
559
- }))}
560
- onSelect={(option) => {
561
- void apply('openai-logging', String(option.value)).then((r) => {
562
- api.ui.toast({ message: r.text })
563
- api.ui.dialog.clear()
564
- })
565
- }}
566
- />
567
- ))
568
- return
569
- }
570
-
571
- if (payload.command === 'openai-cachekeep') {
572
- const render = (state: OpenDialogPayload) => {
573
- const options = buildCachekeepDialogOptions(state)
574
- const DialogSelect = api.ui.DialogSelect<string>
575
- api.ui.dialog.setSize('xlarge')
576
- api.ui.dialog.replace(() => (
577
- <DialogSelect
578
- title='OpenAI cachekeep'
579
- current='status'
580
- options={options}
581
- onSelect={(option) => {
582
- if (option.value === 'close') {
583
- api.ui.dialog.clear()
584
- return
585
- }
586
- if (option.value === 'set_window') {
587
- showSetCachekeepWindowPrompt(api, apply, state, render)
588
- return
589
- }
590
- if (option.value === 'clear_window') {
591
- // The option label is `clear_window`, but executeCachekeepCommand
592
- // matches on `window clear` — forward the canonical form or the
593
- // command falls through to the usage-text branch and the window
594
- // is never cleared.
595
- void apply('openai-cachekeep', 'window clear').then((r) => {
596
- render({
597
- command: 'openai-cachekeep',
598
- text: r.text,
599
- knobs: r.knobs,
600
- })
601
- })
602
- return
603
- }
604
- const args =
605
- option.value === 'refresh' ? 'status' : String(option.value)
606
- void apply('openai-cachekeep', args).then((r) => {
607
- render({
608
- command: 'openai-cachekeep',
609
- text: r.text,
610
- knobs: r.knobs,
611
- })
612
- })
613
- }}
614
- />
615
- ))
616
- }
617
- render(payload)
618
- return
619
- }
620
-
621
- if (payload.command === 'openai-account') {
622
- openAccountDialog(api, apply, sessionId, payload)
623
- return
624
- }
625
-
626
- // fallback for quota (display-only)
627
- showText(api, payload.text)
628
- }
629
-
630
- // -- Accounts dialog ---------------------------------------------------------
631
-
632
- export function formatQuotaWindows(
633
- quota: AccountQuota | null | undefined,
634
- ): string {
635
- const windowSummary = getCollapsedQuotaSummary(quota ?? null).text
636
- if (windowSummary) return windowSummary
637
- if (quota?.resetCreditsAvailable !== undefined) {
638
- return `resets: ${quota.resetCreditsAvailable}`
639
- }
640
- return 'no quota data'
641
- }
642
-
643
- function osc52Copy(api: TuiPluginApi, text: string): boolean {
644
- try {
645
- const renderer = api.renderer as unknown as
646
- | {
647
- copyToClipboardOSC52?: (t: string) => boolean
648
- }
649
- | null
650
- | undefined
651
- if (renderer && typeof renderer.copyToClipboardOSC52 === 'function') {
652
- return renderer.copyToClipboardOSC52(text)
653
- }
654
- } catch {
655
- // best-effort
656
- }
657
- return false
658
- }
659
-
660
- export function buildAccountDialogRows(
661
- state: SidebarState,
662
- sessionId?: string,
663
- ) {
664
- const activeId = resolveSessionSidebarRouting(state, sessionId).activeId
665
- return [
666
- {
667
- title: `main${activeId === 'main' ? ' \u2022 active' : ''}`,
668
- value: 'main',
669
- description: formatQuotaWindows(state.main.quota),
670
- },
671
- ...state.fallbacks.map((fb) => ({
672
- title: `${fb.label ?? fb.id}${activeId === fb.id ? ' \u2022 active' : ''}${!fb.enabled ? ' (disabled)' : ''}`,
673
- value: fb.id,
674
- description: formatQuotaWindows(fb.quota),
675
- })),
676
- ]
677
- }
678
-
679
- export function accountDialogModeOption(mode: unknown) {
680
- const claustrum = mode === 'claustrum'
681
- return {
682
- title: claustrum ? 'Leave Claustrum' : 'Enter Claustrum',
683
- value: claustrum ? '__local__' : '__claustrum__',
684
- description: claustrum
685
- ? 'Require fresh local logins before local refresh resumes'
686
- : 'Verify every enabled account before custody takes over',
687
- }
688
- }
689
-
690
- export function buildFallbackAccountOptions(input: {
691
- id: string
692
- label?: string
693
- enabled: boolean
694
- index: number
695
- count: number
696
- }) {
697
- const name = input.label ?? input.id
698
- const options: Array<{
699
- title: string
700
- value: string
701
- description: string
702
- }> = [
703
- {
704
- title: input.enabled ? 'Disable' : 'Enable',
705
- value: input.enabled ? 'disable' : 'enable',
706
- description: `${input.enabled ? 'Disable' : 'Enable'} ${name}`,
707
- },
708
- {
709
- title: 'Remove',
710
- value: 'remove',
711
- description: `Remove ${name}`,
712
- },
713
- ]
714
- if (input.index > 0) {
715
- options.push({
716
- title: 'Move up',
717
- value: 'move_up',
718
- description: 'Swap with the preceding fallback',
719
- })
720
- }
721
- if (input.index < input.count - 1) {
722
- options.push({
723
- title: 'Move down',
724
- value: 'move_down',
725
- description: 'Swap with the following fallback',
726
- })
727
- }
728
- options.push({
729
- title: 'Back',
730
- value: 'back',
731
- description: 'Return to account list',
732
- })
733
- return options
734
- }
735
-
736
- function openAccountDialog(
737
- api: TuiPluginApi,
738
- apply: ApplyFn,
739
- sessionId?: string,
740
- payload?: OpenDialogPayload,
741
- ) {
742
- const DialogConfirm = api.ui.DialogConfirm
743
- let claustrumMode = payload?.knobs.claustrumMode
744
-
745
- function showL1() {
746
- void getSidebarState().then((state) => {
747
- const DialogSelectInner = api.ui.DialogSelect<string>
748
- api.ui.dialog.setSize('xlarge')
749
- api.ui.dialog.replace(() => (
750
- <DialogSelectInner
751
- title='OpenAI Accounts'
752
- options={[
753
- accountDialogModeOption(claustrumMode),
754
- ...buildAccountDialogRows(state, sessionId),
755
- {
756
- title: 'Add account\u2026',
757
- value: '__add__',
758
- description: 'Sign in to a new OpenAI account',
759
- },
760
- ]}
761
- onSelect={(option) => {
762
- if (
763
- option.value === '__claustrum__' ||
764
- option.value === '__local__'
765
- ) {
766
- const args =
767
- option.value === '__claustrum__' ? 'claustrum' : 'local'
768
- void apply('openai-account', args).then((r) => {
769
- api.ui.toast({ message: r.text })
770
- claustrumMode = r.knobs.claustrumMode
771
- showL1()
772
- })
773
- return
774
- }
775
- if (option.value === '__add__') {
776
- showAddFlow()
777
- return
778
- }
779
- // Main has no per-account actions: it is not removable or reorderable,
780
- // regardless of whether the active session uses mode or sticky routing.
781
- if (option.value === 'main') return
782
- showL2Fallback(option.value)
783
- }}
784
- />
785
- ))
786
- })
787
- }
788
-
789
- function showL2Fallback(id: string) {
790
- void getSidebarState().then((state) => {
791
- const DialogSelectInner = api.ui.DialogSelect<string>
792
- const fbIndex = state.fallbacks.findIndex((f) => f.id === id)
793
- const fb = state.fallbacks[fbIndex]
794
- if (!fb) {
795
- showL1()
796
- return
797
- }
798
- const options = buildFallbackAccountOptions({
799
- id: fb.id,
800
- label: fb.label,
801
- enabled: fb.enabled,
802
- index: fbIndex,
803
- count: state.fallbacks.length,
804
- })
805
-
806
- api.ui.dialog.setSize('xlarge')
807
- api.ui.dialog.replace(() => (
808
- <DialogSelectInner
809
- title={fb.label ?? fb.id}
810
- options={options}
811
- onSelect={(option) => {
812
- if (option.value === 'remove') {
813
- api.ui.dialog.setSize('xlarge')
814
- api.ui.dialog.replace(() => (
815
- <DialogConfirm
816
- title='Remove account'
817
- message={`Remove ${fb.label ?? fb.id}?`}
818
- onConfirm={() => {
819
- void apply('openai-account', `remove ${id}`).then((r) => {
820
- api.ui.toast({ message: r.text })
821
- showL1()
822
- })
823
- }}
824
- onCancel={() => showL2Fallback(id)}
825
- />
826
- ))
827
- return
828
- }
829
- if (option.value === 'enable' || option.value === 'disable') {
830
- void apply('openai-account', `${option.value} ${id}`).then(
831
- (r) => {
832
- api.ui.toast({ message: r.text })
833
- showL1()
834
- },
835
- )
836
- return
837
- }
838
- if (option.value === 'move_up') {
839
- const neighbor = state.fallbacks[fbIndex - 1]
840
- if (neighbor) {
841
- void apply('openai-account', `order ${id} ${neighbor.id}`).then(
842
- (r) => {
843
- api.ui.toast({ message: r.text })
844
- showL1()
845
- },
846
- )
847
- }
848
- return
849
- }
850
- if (option.value === 'move_down') {
851
- const neighbor = state.fallbacks[fbIndex + 1]
852
- if (neighbor) {
853
- void apply('openai-account', `order ${id} ${neighbor.id}`).then(
854
- (r) => {
855
- api.ui.toast({ message: r.text })
856
- showL1()
857
- },
858
- )
859
- }
860
- return
861
- }
862
- showL1()
863
- }}
864
- />
865
- ))
866
- })
867
- }
868
-
869
- // -- Add flow ---------------------------------------------------------------
870
-
871
- function showAddFlow() {
872
- const DialogSelectInner = api.ui.DialogSelect<string>
873
- api.ui.dialog.setSize('xlarge')
874
- api.ui.dialog.replace(() => (
875
- <DialogSelectInner
876
- title='Add account'
877
- options={[
878
- {
879
- title: 'Browser sign-in (local)',
880
- value: 'browser',
881
- description: 'Opens a browser window for OAuth on this machine',
882
- },
883
- {
884
- title: 'Device code (remote / no browser)',
885
- value: 'device',
886
- description: 'Enter a code on another device to authorize',
887
- },
888
- {
889
- title: 'Back',
890
- value: 'back',
891
- description: 'Return to account list',
892
- },
893
- ]}
894
- onSelect={(option) => {
895
- if (option.value === 'browser') {
896
- showLabelPrompt('browser')
897
- return
898
- }
899
- if (option.value === 'device') {
900
- showLabelPrompt('device')
901
- return
902
- }
903
- showL1()
904
- }}
905
- />
906
- ))
907
- }
908
-
909
- function showLabelPrompt(mode: 'browser' | 'device') {
910
- const DialogPromptInner = api.ui.DialogPrompt
911
- api.ui.dialog.setSize('xlarge')
912
- api.ui.dialog.replace(() => (
913
- <DialogPromptInner
914
- title='Label (optional)'
915
- description={() => (
916
- <text>
917
- Give this account a name (e.g. "work", "personal"). Leave empty to
918
- auto-detect.
919
- </text>
920
- )}
921
- placeholder='e.g. work'
922
- value=''
923
- onConfirm={(label: string) => {
924
- if (mode === 'browser') {
925
- startBrowserAdd(label.trim() || undefined)
926
- } else {
927
- startDeviceAdd(label.trim() || undefined)
928
- }
929
- }}
930
- onCancel={() => showAddFlow()}
931
- />
932
- ))
933
- }
934
-
935
- function startBrowserAdd(label: string | undefined) {
936
- const args = label ? `add ${label}` : 'add'
937
- void apply('openai-account', args).then((r) => {
938
- const url = r.knobs.url as string | undefined
939
- const instructions = r.knobs.instructions as string | undefined
940
-
941
- if (!url) {
942
- api.ui.toast({ message: 'Failed to get auth URL', variant: 'warning' })
943
- showL1()
944
- return
945
- }
946
-
947
- // Auto-open the browser (best-effort)
948
- try {
949
- openUrl(url)
950
- } catch {
951
- // best-effort
952
- }
953
-
954
- showBrowserAuthScreen(url, instructions)
955
- })
956
- }
957
-
958
- function showBrowserAuthScreen(
959
- url: string,
960
- _instructions: string | undefined,
961
- ) {
962
- const DialogSelectInner = api.ui.DialogSelect<string>
963
- api.ui.dialog.setSize('xlarge')
964
- api.ui.dialog.replace(() => (
965
- <DialogSelectInner
966
- title='Browser sign-in'
967
- options={[
968
- {
969
- title: 'Copy auth URL',
970
- value: 'copy_url',
971
- description: url,
972
- },
973
- {
974
- title: 'Open in browser',
975
- value: 'open',
976
- description: 'Try to open the URL in your browser',
977
- },
978
- {
979
- title: "Done / I've authorized",
980
- value: 'done',
981
- description: 'Return to account list',
982
- },
983
- {
984
- title: 'Cancel',
985
- value: 'cancel',
986
- description: 'Return to account list without adding',
987
- },
988
- ]}
989
- skipFilter={true}
990
- onSelect={(option) => {
991
- if (option.value === 'copy_url') {
992
- const ok = osc52Copy(api, url)
993
- api.ui.toast({
994
- message: ok
995
- ? 'Auth URL copied'
996
- : 'Copy unsupported — select the URL text manually',
997
- variant: ok ? 'success' : 'warning',
998
- })
999
- return
1000
- }
1001
- if (option.value === 'open') {
1002
- try {
1003
- openUrl(url)
1004
- } catch {
1005
- // best-effort
1006
- }
1007
- return
1008
- }
1009
- showL1()
1010
- }}
1011
- />
1012
- ))
1013
- }
1014
-
1015
- function startDeviceAdd(label: string | undefined) {
1016
- const args = label ? `add --headless ${label}` : 'add --headless'
1017
- void apply('openai-account', args).then((r) => {
1018
- const verificationUrl = r.knobs.verificationUrl as string | undefined
1019
- const userCode = r.knobs.userCode as string | undefined
1020
- const instructions = r.knobs.instructions as string | undefined
1021
-
1022
- if (!verificationUrl) {
1023
- api.ui.toast({
1024
- message: 'Failed to get device code',
1025
- variant: 'warning',
1026
- })
1027
- showL1()
1028
- return
1029
- }
1030
-
1031
- showDeviceCodeScreen(verificationUrl, userCode, instructions)
1032
- })
1033
- }
1034
-
1035
- function showDeviceCodeScreen(
1036
- verificationUrl: string,
1037
- userCode: string | undefined,
1038
- _instructions: string | undefined,
1039
- ) {
1040
- const DialogSelectInner = api.ui.DialogSelect<string>
1041
- api.ui.dialog.setSize('xlarge')
1042
- api.ui.dialog.replace(() => (
1043
- <DialogSelectInner
1044
- title='Device code'
1045
- options={[
1046
- {
1047
- title: userCode ? `Copy code: ${userCode}` : 'Copy code',
1048
- value: 'copy_code',
1049
- description: 'Copy the user code to clipboard',
1050
- },
1051
- {
1052
- title: 'Copy URL',
1053
- value: 'copy_url',
1054
- description: verificationUrl,
1055
- },
1056
- {
1057
- title: "Done / I've authorized",
1058
- value: 'done',
1059
- description: 'Return to account list',
1060
- },
1061
- {
1062
- title: 'Cancel',
1063
- value: 'cancel',
1064
- description: 'Return to account list without adding',
1065
- },
1066
- ]}
1067
- skipFilter={true}
1068
- onSelect={(option) => {
1069
- if (option.value === 'copy_code' && userCode) {
1070
- const ok = osc52Copy(api, userCode)
1071
- api.ui.toast({
1072
- message: ok
1073
- ? 'Code copied'
1074
- : 'Copy unsupported — enter the code manually',
1075
- variant: ok ? 'success' : 'warning',
1076
- })
1077
- return
1078
- }
1079
- if (option.value === 'copy_url') {
1080
- const ok = osc52Copy(api, verificationUrl)
1081
- api.ui.toast({
1082
- message: ok
1083
- ? 'URL copied'
1084
- : 'Copy unsupported — enter the URL manually',
1085
- variant: ok ? 'success' : 'warning',
1086
- })
1087
- return
1088
- }
1089
- showL1()
1090
- }}
1091
- />
1092
- ))
1093
- }
1094
-
1095
- void showL1()
1096
- }