@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,13 +1,14 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
2
 
3
- import { readFileSync } from 'node:fs'
4
- import { dirname, join } from 'node:path'
5
- import { fileURLToPath } from 'node:url'
3
+ import { readFileSync } from "node:fs"
4
+ import { dirname, join } from "node:path"
5
+ import { fileURLToPath } from "node:url"
6
+ import { formatQuota, quotaTextParts } from "./shared/d5a8f1061ee2f114-index.js"
6
7
  import type {
7
8
  TuiPlugin,
8
9
  TuiPluginApi,
9
10
  TuiPluginModule,
10
- } from '@opencode-ai/plugin/tui'
11
+ } from "@opencode-ai/plugin/tui"
11
12
  import {
12
13
  createEffect,
13
14
  createSignal,
@@ -15,11 +16,11 @@ import {
15
16
  type JSX,
16
17
  onCleanup,
17
18
  Show,
18
- } from 'solid-js'
19
- import { createLogger } from './logger.ts'
20
- import type { ApplyRequest, CommandModalName } from './rpc-protocol.ts'
21
- import { createRpcClient } from './rpc-rpc-client.ts'
22
- import { resolveRpcDir } from './rpc-rpc-dir.ts'
19
+ } from "solid-js"
20
+ import { createLogger } from "./logger.js"
21
+ import { type ApplyRequest, isNotifyPayload } from "./rpc/protocol.js"
22
+ import { createRpcClient } from "./rpc/rpc-client.js"
23
+ import { resolveRpcDir } from "./rpc/rpc-dir.js"
23
24
  import {
24
25
  type AccountQuota,
25
26
  computeQuotaPacing,
@@ -34,8 +35,9 @@ import {
34
35
  resolveSessionStickyAccount,
35
36
  type SidebarState,
36
37
  type SpendControlReading,
37
- } from './sidebar-state.ts'
38
- import { openCommandDialog } from './tui-command-dialogs.tsx'
38
+ sidebarQuotaProjection,
39
+ } from "./sidebar-state.js"
40
+ import { openCommandDialog } from "./tui/command-dialogs.tsx"
39
41
  import {
40
42
  type AppearancePrefs,
41
43
  computeEffectiveOrder,
@@ -46,7 +48,7 @@ import {
46
48
  readTuiPreferencesFile,
47
49
  resolveOpenaiAuthPrefs,
48
50
  watchTuiPreferences,
49
- } from './tui-preferences.ts'
51
+ } from "./tui-preferences.js"
50
52
 
51
53
  const RPC_POLL_MS = 500
52
54
  let rpcPollStarted = false
@@ -54,14 +56,20 @@ const log = createLogger('rpc-tui')
54
56
 
55
57
  const ID = 'cortexkit.openai-auth'
56
58
 
59
+ /** One drawer action as the RPC apply request carries it, with its session. */
57
60
  export function buildApplyRequest(
58
- command: CommandModalName,
59
- arguments_: string,
61
+ request: Omit<ApplyRequest, 'sessionId'>,
60
62
  sessionId?: string,
61
63
  ): ApplyRequest {
62
- return { command, arguments: arguments_, sessionId }
64
+ return { ...request, ...(sessionId !== undefined ? { sessionId } : {}) }
63
65
  }
64
66
 
67
+ /**
68
+ * How long the drawer waits for one action: a reset-credit redemption polls
69
+ * the provider and can take most of this.
70
+ */
71
+ const MENU_APPLY_TIMEOUT_MS = 90_000
72
+
65
73
  // Read package metadata from the development source or either generated TUI
66
74
  // variant. Avoid a JSON import because package.json sits outside the declaration
67
75
  // build's rootDir.
@@ -245,6 +253,7 @@ export interface QuotaDisplayRow {
245
253
  key: 'primary' | 'secondary' | 'spendControl'
246
254
  label: string
247
255
  labelWidth: number
256
+ text: string
248
257
  window: QuotaWindow
249
258
  pacing: QuotaPacing | null
250
259
  }
@@ -276,7 +285,16 @@ function quotaRowLabels(quota: AccountQuota | null): string[] {
276
285
  export function renderedQuotas(state: {
277
286
  main?: { quota?: AccountQuota | null } | null
278
287
  fallbacks?: ReadonlyArray<{ enabled?: boolean; quota?: AccountQuota | null }>
288
+ vaultAccounts?: ReadonlyArray<{
289
+ enabled?: boolean
290
+ quota?: AccountQuota | null
291
+ }>
279
292
  }): ReadonlyArray<AccountQuota | null | undefined> {
293
+ // In vault mode only the vault accounts are rendered.
294
+ if (state.vaultAccounts !== undefined)
295
+ return state.vaultAccounts
296
+ .filter((account) => account.enabled)
297
+ .map((account) => account.quota)
280
298
  return [
281
299
  state.main?.quota ?? null,
282
300
  ...(state.fallbacks ?? [])
@@ -307,11 +325,20 @@ export function buildQuotaRowsForDisplay(
307
325
  pacingEnabled: boolean,
308
326
  labelWidth?: number,
309
327
  ): QuotaDisplayRow[] {
328
+ const projection = sidebarQuotaProjection(quota, now)
310
329
  const rows: Array<Omit<QuotaDisplayRow, 'labelWidth'>> =
311
330
  getPresentQuotaWindows(quota).map((row) => ({
312
331
  key: row.key,
313
332
  label: row.label,
314
333
  window: row.window,
334
+ text: formatQuota(
335
+ projection && {
336
+ ...projection,
337
+ budget: undefined,
338
+ limits: projection.limits.filter((limit) => limit.label === row.key),
339
+ },
340
+ { now, form: 'compact', staleAfterMs: Number.POSITIVE_INFINITY },
341
+ ),
315
342
  pacing:
316
343
  pacingEnabled && row.windowMs !== null
317
344
  ? computeQuotaPacing(row.window, row.windowMs, now)
@@ -322,6 +349,11 @@ export function buildQuotaRowsForDisplay(
322
349
  rows.push({
323
350
  key: 'spendControl',
324
351
  label: SPEND_CONTROL_LABEL,
352
+ text: formatQuota(projection && { ...projection, limits: [] }, {
353
+ now,
354
+ form: 'compact',
355
+ staleAfterMs: Number.POSITIVE_INFINITY,
356
+ }),
325
357
  window: {
326
358
  usedPercent: spendControl.usedPercent,
327
359
  remainingPercent: spendControl.remainingPercent,
@@ -332,6 +364,14 @@ export function buildQuotaRowsForDisplay(
332
364
  }
333
365
  const width =
334
366
  labelWidth ?? Math.max(3, ...rows.map((row) => row.label.length + 1))
367
+ const parts = quotaTextParts(projection, {
368
+ now,
369
+ form: 'compact',
370
+ staleAfterMs: Number.POSITIVE_INFINITY,
371
+ })
372
+ rows.sort(
373
+ (left, right) => parts.indexOf(left.text) - parts.indexOf(right.text),
374
+ )
335
375
  return rows.map((row) => ({ ...row, labelWidth: width }))
336
376
  }
337
377
 
@@ -385,8 +425,8 @@ export function getAccountMetadataRows(
385
425
  return rows
386
426
  }
387
427
 
388
- // Quota window row: muted label left, tone-colored bar + percentage right,
389
- // with an optional muted reset suffix. When pacing data is present, the bar
428
+ // Quota window row: muted label left, tone-colored usage bar and shared quota text.
429
+ // When pacing data is present, the bar
390
430
  // gains a pace segment and an off-pace window adds a muted subline with the
391
431
  // reserve/deficit delta and the projected runout.
392
432
  function QuotaRow(props: {
@@ -394,11 +434,11 @@ function QuotaRow(props: {
394
434
  appearance: AppearancePrefs
395
435
  label: string
396
436
  labelWidth: number
437
+ text: string
397
438
  window: { usedPercent: number; resetsAt?: string } | undefined
398
439
  pacing: QuotaPacing | null
399
440
  }) {
400
441
  const used = () => props.window?.usedPercent ?? 0
401
- const reset = () => formatResetIn(props.window?.resetsAt)
402
442
  const paceLine = () => {
403
443
  const pacing = props.pacing
404
444
  if (!pacing || pacing.state === 'on-pace') return null
@@ -420,9 +460,7 @@ function QuotaRow(props: {
420
460
  </box>
421
461
  }
422
462
  >
423
- {/* Left group (label · bar · pct) stays left-aligned in fixed columns so
424
- bars and percentages line up across rows; the reset time is pushed to
425
- the right edge so reset times align in their own right column. */}
463
+ {/* Bars show usage; the adjacent figure shows quota left, as in the dialog. */}
426
464
  <box width='100%' flexDirection='row' justifyContent='space-between'>
427
465
  <box flexDirection='row'>
428
466
  <text fg={props.theme.textMuted}>
@@ -438,12 +476,9 @@ function QuotaRow(props: {
438
476
  <text
439
477
  fg={toneColor(props.theme, usageTone(used(), props.appearance))}
440
478
  >
441
- {` ${String(Math.round(used())).padStart(3)}%`}
479
+ {` ${props.text.slice(props.label.length + 1)}`}
442
480
  </text>
443
481
  </box>
444
- <Show when={reset()}>
445
- <text fg={props.theme.textMuted}>{reset()}</text>
446
- </Show>
447
482
  </box>
448
483
  <Show when={paceLine()}>
449
484
  <box width='100%' flexDirection='row'>
@@ -513,6 +548,7 @@ function AccountBlock(props: {
513
548
  appearance={props.appearance}
514
549
  label={row.label}
515
550
  labelWidth={row.labelWidth}
551
+ text={row.text}
516
552
  window={row.window}
517
553
  pacing={row.pacing}
518
554
  />
@@ -520,6 +556,14 @@ function AccountBlock(props: {
520
556
  </For>
521
557
  </Show>
522
558
  </Show>
559
+ <For
560
+ each={quotaTextParts(sidebarQuotaProjection(props.quota, Date.now()), {
561
+ now: Date.now(),
562
+ form: 'compact',
563
+ }).filter((part) => part.startsWith('checked '))}
564
+ >
565
+ {(age) => <text fg={props.theme.textMuted}>{age}</text>}
566
+ </For>
523
567
  <For
524
568
  each={getAccountMetadataRows(
525
569
  props.resetCredits,
@@ -539,77 +583,6 @@ function AccountBlock(props: {
539
583
  )
540
584
  }
541
585
 
542
- // --- Quota dialog content ---------------------------------------------------
543
-
544
- // Renders the same rich visualization as the sidebar (account blocks with
545
- // quota bars + pacing) so the modal matches the sidebar look.
546
- function QuotaDialogContent(props: {
547
- api: TuiPluginApi
548
- controller: SidebarController
549
- sessionId: string | undefined
550
- }) {
551
- const prefs = props.controller.prefs
552
- const [state, setState] = createSignal<SidebarState>(DEFAULT_SIDEBAR_STATE)
553
- let lastUpdated = 0
554
- async function refresh() {
555
- const next = await readStateFromFile()
556
- if (next.lastUpdated !== lastUpdated) {
557
- lastUpdated = next.lastUpdated
558
- setState(next)
559
- }
560
- }
561
- createEffect(() => {
562
- const timer = setInterval(refresh, prefs().pollMs)
563
- onCleanup(() => clearInterval(timer))
564
- })
565
- setTimeout(refresh, 0)
566
- const theme = () => props.api.theme.current
567
- const enabledFallbacks = () =>
568
- (state().fallbacks ?? []).filter((f) => f.enabled)
569
- const activeId = () => resolveQuotaDialogActiveId(state(), props.sessionId)
570
- const quotaLabelWidth = () => computeQuotaLabelWidth(renderedQuotas(state()))
571
- return (
572
- <box flexDirection='column' padding={2} width='100%' alignItems='center'>
573
- <box flexDirection='column' width={58}>
574
- <box width='100%' justifyContent='center' marginBottom={1}>
575
- <text fg={theme().text}>
576
- <b>Codex Quota</b>
577
- </text>
578
- </box>
579
- <AccountBlock
580
- theme={theme()}
581
- appearance={prefs().appearance}
582
- name='main'
583
- quota={state().main?.quota ?? null}
584
- killed={state().main?.killed ?? false}
585
- active={activeId() === 'main'}
586
- pacingEnabled={prefs().sections.pacing}
587
- labelWidth={quotaLabelWidth()}
588
- resetCredits={state().main?.resetCredits}
589
- />
590
- <Show when={prefs().sections.fallbackAccounts}>
591
- <For each={enabledFallbacks()}>
592
- {(fb) => (
593
- <AccountBlock
594
- theme={theme()}
595
- appearance={prefs().appearance}
596
- name={fb.label ?? fb.id}
597
- quota={fb.quota}
598
- killed={fb.killed}
599
- active={activeId() === fb.id}
600
- pacingEnabled={prefs().sections.pacing}
601
- labelWidth={quotaLabelWidth()}
602
- resetCredits={fb.resetCredits}
603
- marginTop={1}
604
- />
605
- )}
606
- </For>
607
- </Show>
608
- </box>
609
- </box>
610
- )
611
- }
612
-
613
586
  // --- State plumbing ---------------------------------------------------------
614
587
 
615
588
  export async function readStateFromFile(): Promise<SidebarState> {
@@ -764,8 +737,15 @@ function QuotaSidebar(props: {
764
737
  const theme = () => props.api.theme.current
765
738
  const enabledFallbacks = () =>
766
739
  (state().fallbacks ?? []).filter((f) => f.enabled)
740
+ // Vault mode: the host uses only the vault's accounts, which the file lists
741
+ // in `vaultAccounts`; main and the local fallbacks are not shown.
742
+ const vaultMode = () => state().vaultAccounts !== undefined
743
+ const enabledVaultAccounts = () =>
744
+ (state().vaultAccounts ?? []).filter((account) => account.enabled)
767
745
  const hasData = () =>
768
- state().main?.quota != null || enabledFallbacks().length > 0
746
+ vaultMode()
747
+ ? enabledVaultAccounts().length > 0
748
+ : state().main?.quota != null || enabledFallbacks().length > 0
769
749
 
770
750
  const headerLabel = () => {
771
751
  const name = prefs().header.label
@@ -904,34 +884,56 @@ function QuotaSidebar(props: {
904
884
  {/* Quota */}
905
885
  <Show when={prefs().sections.quota}>
906
886
  <SectionHeader theme={theme()} title='Quota' />
907
- <AccountBlock
908
- theme={theme()}
909
- appearance={prefs().appearance}
910
- name='main'
911
- quota={state().main?.quota ?? null}
912
- killed={state().main?.killed ?? false}
913
- active={sessionRouting().activeId === 'main'}
914
- pacingEnabled={prefs().sections.pacing}
915
- labelWidth={quotaLabelWidth()}
916
- resetCredits={state().main?.resetCredits}
917
- />
918
- <Show when={prefs().sections.fallbackAccounts}>
919
- <For each={enabledFallbacks()}>
920
- {(fb) => (
921
- <AccountBlock
922
- theme={theme()}
923
- appearance={prefs().appearance}
924
- name={fb.label ?? fb.id}
925
- quota={fb.quota}
926
- killed={fb.killed}
927
- active={sessionRouting().activeId === fb.id}
928
- pacingEnabled={prefs().sections.pacing}
929
- labelWidth={quotaLabelWidth()}
930
- resetCredits={fb.resetCredits}
931
- marginTop={1}
932
- />
933
- )}
934
- </For>
887
+ <Show
888
+ when={!vaultMode()}
889
+ fallback={
890
+ <For each={enabledVaultAccounts()}>
891
+ {(account, index) => (
892
+ <AccountBlock
893
+ theme={theme()}
894
+ appearance={prefs().appearance}
895
+ name={account.label ?? account.id}
896
+ quota={account.quota}
897
+ killed={account.killed}
898
+ active={sessionRouting().activeId === account.id}
899
+ pacingEnabled={prefs().sections.pacing}
900
+ labelWidth={quotaLabelWidth()}
901
+ resetCredits={account.resetCredits}
902
+ marginTop={index() === 0 ? 0 : 1}
903
+ />
904
+ )}
905
+ </For>
906
+ }
907
+ >
908
+ <AccountBlock
909
+ theme={theme()}
910
+ appearance={prefs().appearance}
911
+ name='main'
912
+ quota={state().main?.quota ?? null}
913
+ killed={state().main?.killed ?? false}
914
+ active={sessionRouting().activeId === 'main'}
915
+ pacingEnabled={prefs().sections.pacing}
916
+ labelWidth={quotaLabelWidth()}
917
+ resetCredits={state().main?.resetCredits}
918
+ />
919
+ <Show when={prefs().sections.fallbackAccounts}>
920
+ <For each={enabledFallbacks()}>
921
+ {(fb) => (
922
+ <AccountBlock
923
+ theme={theme()}
924
+ appearance={prefs().appearance}
925
+ name={fb.label ?? fb.id}
926
+ quota={fb.quota}
927
+ killed={fb.killed}
928
+ active={sessionRouting().activeId === fb.id}
929
+ pacingEnabled={prefs().sections.pacing}
930
+ labelWidth={quotaLabelWidth()}
931
+ resetCredits={fb.resetCredits}
932
+ marginTop={1}
933
+ />
934
+ )}
935
+ </For>
936
+ </Show>
935
937
  </Show>
936
938
  </Show>
937
939
  </Show>
@@ -1041,26 +1043,16 @@ const tui: TuiPlugin = async (api) => {
1041
1043
  .then((messages) => {
1042
1044
  for (const message of [...messages].sort((a, b) => a.id - b.id)) {
1043
1045
  lastNotificationId = Math.max(lastNotificationId, message.id)
1044
- if (message.payload.command === 'openai-quota') {
1045
- api.ui.dialog.setSize('xlarge')
1046
- api.ui.dialog.replace(() => (
1047
- <QuotaDialogContent
1048
- api={api}
1049
- controller={controller}
1050
- sessionId={sessionId}
1051
- />
1052
- ))
1046
+ const payload = message.payload
1047
+ if (isNotifyPayload(payload)) {
1048
+ api.ui.toast({ message: payload.notify.message })
1053
1049
  continue
1054
1050
  }
1055
- openCommandDialog(
1056
- api,
1057
- message.payload,
1058
- (command, args) =>
1059
- rpcClient.apply(
1060
- buildApplyRequest(command, args, sessionId),
1061
- command === 'openai-reset' ? 90_000 : undefined,
1062
- ),
1063
- sessionId,
1051
+ openCommandDialog(api, payload, (request) =>
1052
+ rpcClient.apply(
1053
+ buildApplyRequest(request, sessionId),
1054
+ MENU_APPLY_TIMEOUT_MS,
1055
+ ),
1064
1056
  )
1065
1057
  }
1066
1058
  })
@@ -0,0 +1,24 @@
1
+ import { tmpdir } from "node:os";
2
+ import { join } from "node:path";
3
+ import {
4
+ flushLogs,
5
+ initLogger
6
+ } from "./shared/3c2d4169d00f907e-tui-support.js";
7
+ export {
8
+ createLogger,
9
+ flushForTest,
10
+ flushLogs,
11
+ redact,
12
+ redactStrings,
13
+ setLogLevel
14
+ } from "./shared/3c2d4169d00f907e-tui-support.js";
15
+ const LEVELS = ["error", "warn", "info", "debug", "trace"];
16
+ export function getLogFile() {
17
+ return process.env.OPENCODE_OPENAI_AUTH_LOG_FILE ?? join(tmpdir(), "opencode-openai-auth.log");
18
+ }
19
+ export function getEnvLogLevel() {
20
+ const env = process.env.OPENCODE_OPENAI_AUTH_LOG_LEVEL;
21
+ return env && LEVELS.includes(env) ? env : void 0;
22
+ }
23
+ initLogger({ file: getLogFile, level: getEnvLogLevel });
24
+ process.on("exit", flushLogs);
@@ -0,0 +1,3 @@
1
+ export {
2
+ isNotifyPayload
3
+ } from "../shared/3c2d4169d00f907e-tui-support.js";
@@ -0,0 +1,7 @@
1
+ import {
2
+ createRpcClient as createCommonRpcClient
3
+ } from "../shared/46f3dff1266160e8-client.js";
4
+ export { DEFAULT_RPC_TIMEOUT_MS } from "../shared/46f3dff1266160e8-client.js";
5
+ export function createRpcClient(dir, expectedPid, onSelected) {
6
+ return createCommonRpcClient(dir, expectedPid, onSelected);
7
+ }
@@ -0,0 +1,27 @@
1
+ import { createHash } from "node:crypto";
2
+ import { homedir, tmpdir } from "node:os";
3
+ import { dirname, join, resolve } from "node:path";
4
+ const RPC_DIR_ENV = "OPENCODE_OPENAI_AUTH_RPC_DIR";
5
+ export const RPC_DIRECTORY_PREFIX = "openai-auth-";
6
+ function rpcHash(projectDirectory) {
7
+ return createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
8
+ }
9
+ function defaultRpcRoot() {
10
+ const base = process.env.XDG_STATE_HOME || join(homedir(), ".local", "state");
11
+ return join(base, "cortexkit", "openai-auth", "rpc");
12
+ }
13
+ export function getRpcDir(projectDirectory) {
14
+ const override = process.env[RPC_DIR_ENV]?.trim();
15
+ if (override)
16
+ return resolve(projectDirectory, override);
17
+ return join(defaultRpcRoot(), `${RPC_DIRECTORY_PREFIX}${rpcHash(projectDirectory)}`);
18
+ }
19
+ export async function resolveRpcDir(projectDirectory) {
20
+ const override = process.env[RPC_DIR_ENV]?.trim();
21
+ if (override)
22
+ return { dir: resolve(projectDirectory, override), secureDir: !1 };
23
+ const dir = getRpcDir(projectDirectory);
24
+ return { dir, secureDir: !0, sweepRoot: dirname(dir) };
25
+ }
26
+
27
+ export { tmpdir };
@@ -0,0 +1,21 @@
1
+ import { isAbsolute } from 'node:path';
2
+ export async function loadTui({ rawEntry, runtimeEntry, importModule = (entry) => import(entry), }) {
3
+ for (const [option, entry] of Object.entries({ rawEntry, runtimeEntry })) {
4
+ if (!isAbsolute(entry) &&
5
+ !(entry.startsWith('file:') && new URL(entry).pathname.startsWith('/'))) {
6
+ throw new Error(`${option} must be an absolute path or file URL: ${entry}`);
7
+ }
8
+ }
9
+ let entry = runtimeEntry;
10
+ try {
11
+ await importModule(`opentui:runtime-module:${encodeURIComponent('@opentui/solid')}`);
12
+ }
13
+ catch (error) {
14
+ const message = error instanceof Error ? error.message : String(error);
15
+ if (!/Cannot find|Could not resolve|Module not found|Unable to resolve/.test(message) ||
16
+ !message.includes('opentui:runtime-module:'))
17
+ throw error;
18
+ entry = rawEntry;
19
+ }
20
+ return (await importModule(entry)).default;
21
+ }
@@ -0,0 +1,138 @@
1
+ // The one quota wording every surface shows: the command menu's dialogs on
2
+ // OpenCode and Pi, the terminal auth menu, and the plugins' sidebars. Each of
3
+ // them calls `formatQuota` (or `quotaTextParts` for one line per window), so
4
+ // the wording cannot drift between plugins or between a dialog and a sidebar.
5
+ //
6
+ // The rules: a window is named by its length (`5h`, `7d`), or, for a window
7
+ // that binds only one model family, by the label the plugin stored it under.
8
+ // Every figure is "% left". A window the provider did not report (an absence
9
+ // record or a retirement tombstone) is left out rather than written as "not
10
+ // reported". A reset is a short time from now (`resets 2h`). A reading older
11
+ // than fifteen minutes says how old it is (`checked 3h ago`).
12
+ //
13
+ // Like the rest of this module it reads no clock: `now` is a parameter.
14
+ import { ALL_SCOPE } from "./905bffc0220f43d0-map.js";
15
+ /** A reading older than this is marked with its age. */
16
+ export const QUOTA_STALE_AFTER_MS = 15 * 60_000;
17
+ /** What `formatQuota` returns when there is no reading at all. */
18
+ export const NO_QUOTA_READING = 'no quota reading yet';
19
+ /**
20
+ * What `formatQuota` returns when the provider answered but reported no
21
+ * limit for any window (every window is an absence record or a tombstone).
22
+ */
23
+ export const NO_LIMITS_REPORTED = 'no limits reported';
24
+ /**
25
+ * A length or a time span in its largest whole-ish unit: `45m`, `5h`, `3d`.
26
+ * Spans under an hour are minutes, under a day hours, otherwise days, each
27
+ * rounded to the nearest unit and never shown as zero.
28
+ */
29
+ function shortSpan(ms) {
30
+ const minutes = Math.max(1, Math.round(ms / 60_000));
31
+ if (minutes < 60)
32
+ return `${minutes}m`;
33
+ const hours = Math.round(minutes / 60);
34
+ if (hours < 24)
35
+ return `${hours}h`;
36
+ return `${Math.max(1, Math.round(minutes / 1440))}d`;
37
+ }
38
+ /** A stored window length in minutes as its exact name: `5h`, `7d`, `90m`. */
39
+ function lengthName(minutes) {
40
+ if (minutes % 1440 === 0)
41
+ return `${minutes / 1440}d`;
42
+ if (minutes % 60 === 0)
43
+ return `${minutes / 60}h`;
44
+ return `${minutes}m`;
45
+ }
46
+ /**
47
+ * The name a window is shown under. A general window (scope `all`) is named
48
+ * by its stored length; a model-scoped window keeps the label the plugin
49
+ * stored it under, which already names the model (`seven_day_opus`). A
50
+ * general window stored without a length (a snapshot from before lengths
51
+ * were recorded) falls back to its label.
52
+ */
53
+ export function quotaWindowName(limit) {
54
+ if (limit.scope !== ALL_SCOPE || limit.windowMinutes === undefined)
55
+ return limit.label;
56
+ return lengthName(limit.windowMinutes);
57
+ }
58
+ function percentLeft(remaining) {
59
+ return `${Math.round(Math.min(100, Math.max(0, remaining)))}% left`;
60
+ }
61
+ function resetText(resetsAt, now) {
62
+ if (resetsAt === undefined)
63
+ return '';
64
+ const at = Date.parse(resetsAt);
65
+ // A reset already passed says nothing about the future; leave it out.
66
+ if (!Number.isFinite(at) || at <= now)
67
+ return '';
68
+ return `, resets ${shortSpan(at - now)}`;
69
+ }
70
+ /** Shortest window first, so `5h` reads before `7d`; unknown lengths last. */
71
+ function displayOrder(left, right) {
72
+ const l = left.windowMinutes;
73
+ const r = right.windowMinutes;
74
+ if (l !== undefined && r !== undefined && l !== r)
75
+ return l - r;
76
+ if ((l === undefined) !== (r === undefined))
77
+ return l === undefined ? 1 : -1;
78
+ if (left.scope !== right.scope) {
79
+ if (left.scope === ALL_SCOPE)
80
+ return -1;
81
+ if (right.scope === ALL_SCOPE)
82
+ return 1;
83
+ return left.scope < right.scope ? -1 : 1;
84
+ }
85
+ return left.label < right.label ? -1 : left.label > right.label ? 1 : 0;
86
+ }
87
+ /**
88
+ * The pieces of a quota text, in order: one per reported window
89
+ * (`5h 58% left, resets 2h`), then the credit budget when there is one, then
90
+ * `checked 3h ago` when the oldest piece shown is stale. Empty when nothing
91
+ * was reported; `formatQuota` turns that into a sentence. A caller printing
92
+ * one line per window (the terminal auth menu) uses these directly.
93
+ */
94
+ export function quotaTextParts(quota, options) {
95
+ if (!quota)
96
+ return [];
97
+ const { now } = options;
98
+ const full = (options.form ?? 'full') === 'full';
99
+ const parts = [];
100
+ const times = [];
101
+ const readings = quota.limits
102
+ .filter((limit) => limit.kind === 'reading' && limit.remainingPercent !== undefined)
103
+ .sort(displayOrder);
104
+ for (const limit of readings) {
105
+ times.push(limit.checkedAt);
106
+ parts.push(`${quotaWindowName(limit)} ${percentLeft(limit.remainingPercent ?? 0)}${full ? resetText(limit.resetsAt, now) : ''}`);
107
+ }
108
+ const budget = quota.budget;
109
+ if (budget) {
110
+ times.push(budget.checkedAt);
111
+ const figure = budget.reached
112
+ ? 'credits spent'
113
+ : budget.remainingPercent !== undefined
114
+ ? `credits ${percentLeft(budget.remainingPercent)}`
115
+ : 'credits available';
116
+ parts.push(`${figure}${full ? resetText(budget.resetsAt, now) : ''}`);
117
+ }
118
+ if (times.length > 0) {
119
+ const age = now - Math.min(...times);
120
+ if (age > (options.staleAfterMs ?? QUOTA_STALE_AFTER_MS))
121
+ parts.push(`checked ${shortSpan(age)} ago`);
122
+ }
123
+ return parts;
124
+ }
125
+ /**
126
+ * A row's quota as one line: `5h 58% left · 7d 90% left` in the compact
127
+ * form, with `, resets 2h` after each window in the full form. With nothing
128
+ * reported it is `NO_QUOTA_READING`, or `NO_LIMITS_REPORTED` when the
129
+ * provider answered without a limit for any window.
130
+ */
131
+ export function formatQuota(quota, options) {
132
+ const parts = quotaTextParts(quota, options);
133
+ if (parts.length > 0)
134
+ return parts.join(' · ');
135
+ return quota && quota.limits.length > 0
136
+ ? NO_LIMITS_REPORTED
137
+ : NO_QUOTA_READING;
138
+ }