@cortexkit/opencode-openai-auth 1.0.5 → 1.2.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 (104) hide show
  1. package/dist/bin.js +9 -8
  2. package/dist/{pool-lifecycle-C_6YRmsh.d.ts → codex-request-CjXETpTA.d.ts} +190 -27
  3. package/dist/index-03em8hv1.js +69 -0
  4. package/dist/index-6n4pnfkg.js +2 -0
  5. package/dist/{index-pg305s90.js → index-bc89akak.js} +1 -1
  6. package/dist/{index-y4gxyxqd.js → index-ehjj89rx.js} +2 -2
  7. package/dist/index-f21addzm.js +90 -0
  8. package/dist/{index-fsky1rsh.js → index-ffg0sgg5.js} +2 -2
  9. package/dist/{index-nx2qh047.js → index-ftkhhr6d.js} +1 -1
  10. package/dist/{index-826s1yxa.js → index-htvcmtcx.js} +2 -2
  11. package/dist/{index-fsxbncpw.js → index-qp5hdpw8.js} +1 -1
  12. package/dist/index-qxbchh2d.js +8 -0
  13. package/dist/{index-116q849r.js → index-r2s51ac1.js} +2 -2
  14. package/dist/index-rwbrhkse.js +65 -0
  15. package/dist/{index-66asrje2.js → index-xwvze96g.js} +1 -1
  16. package/dist/index.d.ts +18 -8
  17. package/dist/index.js +1 -1
  18. package/dist/{protocol-DctqemRI.d.ts → model-CQcw10oW.d.ts} +67 -25
  19. package/dist/{port-file-ZPlxXB4G.d.ts → port-file-BDJnzgHH.d.ts} +1 -1
  20. package/dist/protocol-C-kagE6O.d.ts +29 -0
  21. package/dist/rpc/port-file.d.ts +1 -1
  22. package/dist/rpc/port-file.js +1 -1
  23. package/dist/rpc/protocol.d.ts +1 -1
  24. package/dist/rpc/protocol.js +1 -1
  25. package/dist/rpc/rpc-client.d.ts +3 -3
  26. package/dist/rpc/rpc-client.js +1 -1
  27. package/dist/rpc/rpc-dir.js +1 -1
  28. package/dist/{sidebar-state-z_M7Trnd.d.ts → sidebar-state-BfQ4mpZO.d.ts} +109 -10
  29. package/dist/sidebar-state.d.ts +2 -2
  30. package/dist/sidebar-state.js +1 -1
  31. package/dist/token-util-cf0nyh7n.js +1 -0
  32. package/dist/{token-bzhkgc29.js → token-z6qq7a1d.js} +1 -1
  33. package/dist/tui-preferences.js +1 -1
  34. package/dist/tui.d.ts +20 -2
  35. package/dist/v2/server.d.ts +180 -1
  36. package/dist/v2/server.js +3 -3
  37. package/package.json +3 -3
  38. package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +148 -26
  39. package/src/tui-compiled/raw/shared/0f77ebc3c914c9c6-custody.js +371 -0
  40. package/src/tui-compiled/raw/shared/134b008900698fe0-backoff.js +92 -0
  41. package/src/tui-compiled/raw/shared/169b2b4bf30909bc-consumer.js +430 -0
  42. package/src/tui-compiled/raw/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
  43. package/src/tui-compiled/raw/shared/2dce6e9613993329-quota-manager.js +473 -0
  44. package/src/tui-compiled/raw/shared/30f5bc480f31f51c-roster.js +566 -0
  45. package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
  46. package/src/tui-compiled/raw/shared/4593a4d71edf69fd-tombstone.js +25 -0
  47. package/src/tui-compiled/raw/shared/4d05c2d7a531aece-oauth.js +500 -0
  48. package/src/tui-compiled/raw/shared/6371b24a0573d79e-any-signal.js +42 -0
  49. package/src/tui-compiled/raw/shared/7c140680fc81be92-interlock.js +29 -0
  50. package/src/tui-compiled/raw/shared/85512cc5263ba259-provider.js +87 -0
  51. package/src/tui-compiled/raw/shared/abd062d633e29fd2-pool-routing.js +192 -0
  52. package/src/tui-compiled/raw/shared/b9588ad4725c1d10-enrollment.js +806 -0
  53. package/src/tui-compiled/raw/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
  54. package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +0 -1
  55. package/src/tui-compiled/raw/shared/ca0fccaba1d87af4-errors.js +8 -0
  56. package/src/tui-compiled/raw/shared/d0f0b6be200311af-insula.js +152 -0
  57. package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +1 -1
  58. package/src/tui-compiled/raw/shared/d9d2892a4c371301-host-slot.js +72 -0
  59. package/src/tui-compiled/raw/shared/dbc61cd405941828-accounts.js +1470 -0
  60. package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
  61. package/src/tui-compiled/raw/shared/e8d3c8da776ef9cd-index.js +24 -0
  62. package/src/tui-compiled/raw/sidebar-state.js +103 -14
  63. package/src/tui-compiled/raw/tui/command-dialogs.tsx +3 -34
  64. package/src/tui-compiled/raw/tui/section-text.js +21 -0
  65. package/src/tui-compiled/raw/tui/v2-dialog.js +137 -0
  66. package/src/tui-compiled/raw/tui/v2.tsx +133 -0
  67. package/src/tui-compiled/raw/tui.tsx +228 -70
  68. package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +148 -26
  69. package/src/tui-compiled/runtime/shared/0f77ebc3c914c9c6-custody.js +371 -0
  70. package/src/tui-compiled/runtime/shared/134b008900698fe0-backoff.js +92 -0
  71. package/src/tui-compiled/runtime/shared/169b2b4bf30909bc-consumer.js +430 -0
  72. package/src/tui-compiled/runtime/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
  73. package/src/tui-compiled/runtime/shared/2dce6e9613993329-quota-manager.js +473 -0
  74. package/src/tui-compiled/runtime/shared/30f5bc480f31f51c-roster.js +566 -0
  75. package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
  76. package/src/tui-compiled/runtime/shared/4593a4d71edf69fd-tombstone.js +25 -0
  77. package/src/tui-compiled/runtime/shared/4d05c2d7a531aece-oauth.js +500 -0
  78. package/src/tui-compiled/runtime/shared/6371b24a0573d79e-any-signal.js +42 -0
  79. package/src/tui-compiled/runtime/shared/7c140680fc81be92-interlock.js +29 -0
  80. package/src/tui-compiled/runtime/shared/85512cc5263ba259-provider.js +87 -0
  81. package/src/tui-compiled/runtime/shared/abd062d633e29fd2-pool-routing.js +192 -0
  82. package/src/tui-compiled/runtime/shared/b9588ad4725c1d10-enrollment.js +806 -0
  83. package/src/tui-compiled/runtime/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
  84. package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +0 -1
  85. package/src/tui-compiled/runtime/shared/ca0fccaba1d87af4-errors.js +8 -0
  86. package/src/tui-compiled/runtime/shared/d0f0b6be200311af-insula.js +152 -0
  87. package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +1 -1
  88. package/src/tui-compiled/runtime/shared/d9d2892a4c371301-host-slot.js +72 -0
  89. package/src/tui-compiled/runtime/shared/dbc61cd405941828-accounts.js +1470 -0
  90. package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
  91. package/src/tui-compiled/runtime/shared/e8d3c8da776ef9cd-index.js +24 -0
  92. package/src/tui-compiled/runtime/sidebar-state.js +103 -14
  93. package/src/tui-compiled/runtime/tui/command-dialogs.js +3 -15
  94. package/src/tui-compiled/runtime/tui/section-text.js +21 -0
  95. package/src/tui-compiled/runtime/tui/v2-dialog.js +137 -0
  96. package/src/tui-compiled/runtime/tui/v2.js +95 -0
  97. package/src/tui-compiled/runtime/tui.js +351 -222
  98. package/dist/index-4fnghj9e.js +0 -65
  99. package/dist/index-a69gr76b.js +0 -2
  100. package/dist/index-h263aftt.js +0 -157
  101. package/dist/index-ssj7m2rd.js +0 -8
  102. package/dist/index-ydz3hbhc.js +0 -2
  103. package/dist/map-m6f5a_IA.d.ts +0 -56
  104. package/dist/token-util-4a2xxnpg.js +0 -1
@@ -3,7 +3,12 @@
3
3
  import { readFileSync } from "node:fs"
4
4
  import { dirname, join } from "node:path"
5
5
  import { fileURLToPath } from "node:url"
6
- import { formatQuota, quotaTextParts } from "./shared/d5a8f1061ee2f114-index.js"
6
+ import {
7
+ formatQuota,
8
+ quotaTextParts,
9
+ quotaWindowParts,
10
+ } from "./shared/d5a8f1061ee2f114-index.js"
11
+ import type { Plugin } from "@opencode/plugin/tui"
7
12
  import type {
8
13
  TuiPlugin,
9
14
  TuiPluginApi,
@@ -13,7 +18,7 @@ import {
13
18
  createEffect,
14
19
  createSignal,
15
20
  For,
16
- type JSX,
21
+ Index,
17
22
  onCleanup,
18
23
  Show,
19
24
  } from "solid-js"
@@ -23,8 +28,12 @@ import { createRpcClient } from "./rpc/rpc-client.js"
23
28
  import { resolveRpcDir } from "./rpc/rpc-dir.js"
24
29
  import {
25
30
  type AccountQuota,
31
+ type CollapsedAccountLine,
26
32
  computeQuotaPacing,
27
33
  DEFAULT_SIDEBAR_STATE,
34
+ displayWidth,
35
+ fitCollapsedLabel,
36
+ getCollapsedAccountLines,
28
37
  getCollapsedQuotaSummary,
29
38
  getPresentQuotaWindows,
30
39
  getSidebarState,
@@ -38,6 +47,7 @@ import {
38
47
  sidebarQuotaProjection,
39
48
  } from "./sidebar-state.js"
40
49
  import { openCommandDialog } from "./tui/command-dialogs.tsx"
50
+ import { setupTui, type TuiTransportOptions } from "./tui/v2.tsx"
41
51
  import {
42
52
  type AppearancePrefs,
43
53
  computeEffectiveOrder,
@@ -95,7 +105,17 @@ const PLUGIN_VERSION: string = (() => {
95
105
  const SINGLE_BORDER = { type: 'single' } as any
96
106
 
97
107
  type Tone = 'ok' | 'warn' | 'err' | 'muted' | 'accent' | 'text'
98
- type ThemeCurrent = TuiPluginApi['theme']['current']
108
+ type ThemeCurrent = Pick<
109
+ TuiPluginApi['theme']['current'],
110
+ | 'text'
111
+ | 'textMuted'
112
+ | 'accent'
113
+ | 'background'
114
+ | 'borderActive'
115
+ | 'success'
116
+ | 'warning'
117
+ | 'error'
118
+ >
99
119
  type ThemeColor = ThemeCurrent['text']
100
120
 
101
121
  // Tone -> theme token. Theme tokens only — never hardcoded colors. Fallbacks
@@ -234,17 +254,72 @@ function StatRow(props: {
234
254
  )
235
255
  }
236
256
 
237
- // Compact row for the collapsed view: muted label left, caller-provided value
238
- // right. Mirrors StatRow's layout so columns line up.
239
- function CollapsedRow(props: {
257
+ // One account in the collapsed view: label left; narrow quota text and the
258
+ // status dot right (red ⊘ when killed). The session's account keeps the
259
+ // usage tone; other accounts, and any account whose reading is stale, are
260
+ // drawn muted. The row measures its own width so only the label is cut (with
261
+ // …) when the line does not fit; the quota text is never cut.
262
+ function CollapsedAccountRow(props: {
240
263
  theme: ThemeCurrent
241
- label: string
242
- children: JSX.Element
264
+ line: CollapsedAccountLine
265
+ tone: Tone
243
266
  }) {
267
+ const [width, setWidth] = createSignal(0)
268
+ let row: { width: number } | undefined
269
+ const dim = () => props.line.stale || !props.line.active
270
+ const label = () => {
271
+ const columns = width()
272
+ if (columns <= 0) return props.line.name
273
+ // The value side is the text plus ` ●`, or a lone `—`; one column
274
+ // separates it from the label.
275
+ const value =
276
+ props.line.text === null ? 1 : displayWidth(props.line.text) + 2
277
+ return fitCollapsedLabel(props.line.name, columns - value - 1)
278
+ }
244
279
  return (
245
- <box width='100%' flexDirection='row' justifyContent='space-between'>
246
- <text fg={props.theme.textMuted}>{props.label}</text>
247
- {props.children}
280
+ <box
281
+ ref={(element: { width: number }) => {
282
+ row = element
283
+ }}
284
+ onSizeChange={() => setWidth(row?.width ?? 0)}
285
+ width='100%'
286
+ flexDirection='row'
287
+ justifyContent='space-between'
288
+ >
289
+ <text
290
+ fg={toneColor(props.theme, dim() ? 'muted' : 'text')}
291
+ wrapMode='none'
292
+ flexShrink={1}
293
+ >
294
+ {label()}
295
+ </text>
296
+ <Show
297
+ when={props.line.text !== null}
298
+ fallback={<text fg={props.theme.textMuted}>{'\u2014'}</text>}
299
+ >
300
+ <box flexDirection='row' flexShrink={0}>
301
+ <Show
302
+ when={!dim()}
303
+ fallback={
304
+ <text fg={toneColor(props.theme, 'muted')}>
305
+ {props.line.text}
306
+ </text>
307
+ }
308
+ >
309
+ <text fg={toneColor(props.theme, props.tone)}>
310
+ <b>{props.line.text}</b>
311
+ </text>
312
+ </Show>
313
+ <text
314
+ fg={toneColor(
315
+ props.theme,
316
+ props.line.killed ? 'err' : dim() ? 'muted' : props.tone,
317
+ )}
318
+ >
319
+ {props.line.killed ? ' \u2298' : ' \u25cf'}
320
+ </text>
321
+ </box>
322
+ </Show>
248
323
  </box>
249
324
  )
250
325
  }
@@ -254,6 +329,7 @@ export interface QuotaDisplayRow {
254
329
  label: string
255
330
  labelWidth: number
256
331
  text: string
332
+ resetText?: string
257
333
  window: QuotaWindow
258
334
  pacing: QuotaPacing | null
259
335
  }
@@ -292,9 +368,7 @@ export function renderedQuotas(state: {
292
368
  }): ReadonlyArray<AccountQuota | null | undefined> {
293
369
  // In vault mode only the vault accounts are rendered.
294
370
  if (state.vaultAccounts !== undefined)
295
- return state.vaultAccounts
296
- .filter((account) => account.enabled)
297
- .map((account) => account.quota)
371
+ return state.vaultAccounts.map((account) => account.quota)
298
372
  return [
299
373
  state.main?.quota ?? null,
300
374
  ...(state.fallbacks ?? [])
@@ -327,33 +401,50 @@ export function buildQuotaRowsForDisplay(
327
401
  ): QuotaDisplayRow[] {
328
402
  const projection = sidebarQuotaProjection(quota, now)
329
403
  const rows: Array<Omit<QuotaDisplayRow, 'labelWidth'>> =
330
- getPresentQuotaWindows(quota).map((row) => ({
331
- key: row.key,
332
- label: row.label,
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
- ),
342
- pacing:
343
- pacingEnabled && row.windowMs !== null
344
- ? computeQuotaPacing(row.window, row.windowMs, now)
345
- : null,
346
- }))
404
+ getPresentQuotaWindows(quota).map((row) => {
405
+ const rowProjection = projection && {
406
+ ...projection,
407
+ budget: undefined,
408
+ limits: projection.limits.filter((limit) => limit.label === row.key),
409
+ }
410
+ const reset = quotaWindowParts(rowProjection, {
411
+ now,
412
+ staleAfterMs: Number.POSITIVE_INFINITY,
413
+ }).windows[0]
414
+ return {
415
+ key: row.key,
416
+ label: row.label,
417
+ window: row.window,
418
+ text: formatQuota(rowProjection, {
419
+ now,
420
+ form: 'compact',
421
+ staleAfterMs: Number.POSITIVE_INFINITY,
422
+ }),
423
+ resetText:
424
+ reset?.resetIn ?? (reset?.resetSinceReading ? 'reset' : undefined),
425
+ pacing:
426
+ pacingEnabled && row.windowMs !== null
427
+ ? computeQuotaPacing(row.window, row.windowMs, now)
428
+ : null,
429
+ }
430
+ })
347
431
  const spendControl = quota?.spendControl
348
432
  if (spendControl) {
433
+ const rowProjection = projection && { ...projection, limits: [] }
434
+ const reset = quotaWindowParts(rowProjection, {
435
+ now,
436
+ staleAfterMs: Number.POSITIVE_INFINITY,
437
+ }).windows[0]
349
438
  rows.push({
350
439
  key: 'spendControl',
351
440
  label: SPEND_CONTROL_LABEL,
352
- text: formatQuota(projection && { ...projection, limits: [] }, {
441
+ text: formatQuota(rowProjection, {
353
442
  now,
354
443
  form: 'compact',
355
444
  staleAfterMs: Number.POSITIVE_INFINITY,
356
445
  }),
446
+ resetText:
447
+ reset?.resetIn ?? (reset?.resetSinceReading ? 'reset' : undefined),
357
448
  window: {
358
449
  usedPercent: spendControl.usedPercent,
359
450
  remainingPercent: spendControl.remainingPercent,
@@ -435,6 +526,7 @@ function QuotaRow(props: {
435
526
  label: string
436
527
  labelWidth: number
437
528
  text: string
529
+ resetText?: string
438
530
  window: { usedPercent: number; resetsAt?: string } | undefined
439
531
  pacing: QuotaPacing | null
440
532
  }) {
@@ -479,6 +571,9 @@ function QuotaRow(props: {
479
571
  {` ${props.text.slice(props.label.length + 1)}`}
480
572
  </text>
481
573
  </box>
574
+ <Show when={props.resetText}>
575
+ <text fg={props.theme.textMuted}>{props.resetText}</text>
576
+ </Show>
482
577
  </box>
483
578
  <Show when={paceLine()}>
484
579
  <box width='100%' flexDirection='row'>
@@ -506,6 +601,8 @@ function AccountBlock(props: {
506
601
  name: string
507
602
  quota: AccountQuota | null
508
603
  killed: boolean
604
+ vaultState?: string
605
+ hostDisabled?: boolean
509
606
  active: boolean
510
607
  pacingEnabled: boolean
511
608
  labelWidth: number
@@ -533,6 +630,12 @@ function AccountBlock(props: {
533
630
  <b>{statusWord()}</b>
534
631
  </text>
535
632
  </box>
633
+ <Show when={props.vaultState}>
634
+ <text fg={props.theme.warning}>{props.vaultState}</text>
635
+ </Show>
636
+ <Show when={props.hostDisabled}>
637
+ <text fg={props.theme.textMuted}>off in /openai</text>
638
+ </Show>
536
639
  <Show
537
640
  when={isQuotaLoaded(props.quota)}
538
641
  fallback={<text fg={props.theme.textMuted}>{'checking\u2026'}</text>}
@@ -549,6 +652,7 @@ function AccountBlock(props: {
549
652
  label={row.label}
550
653
  labelWidth={row.labelWidth}
551
654
  text={row.text}
655
+ resetText={row.resetText}
552
656
  window={row.window}
553
657
  pacing={row.pacing}
554
658
  />
@@ -629,6 +733,7 @@ interface SidebarController {
629
733
  prefs: () => OpenaiAuthTuiPrefs
630
734
  collapsed: () => boolean
631
735
  toggleCollapsed: () => void
736
+ dispose: () => void
632
737
  }
633
738
 
634
739
  // The TUI may unmount and remount sidebar_content when the user switches
@@ -651,14 +756,13 @@ function createSidebarController(
651
756
  let lastPersistedCollapsed: boolean | null = initialPrefs.collapsed
652
757
  let lastApplied = JSON.stringify(initialPrefs)
653
758
 
654
- // The watcher lives for the plugin/process lifetime — it is intentionally
655
- // never disposed. Collapse guard mirrors the race-fix in toggleCollapsed:
656
- // lastPersistedCollapsed is advanced only once our own write lands, so
657
- // watcher echoes of the previous persisted value are rejected by the
658
- // `!==` check and cannot revert a user click.
659
- watchTuiPreferences(() => {
759
+ let disposed = false
760
+ // Remember the last value written to disk, not an unpersisted click. A watcher
761
+ // echo of the previous disk value must not undo a click whose write is pending.
762
+ const unwatch = watchTuiPreferences(() => {
660
763
  void (async () => {
661
764
  const next = resolveOpenaiAuthPrefs(await readTuiPreferencesFile())
765
+ if (disposed) return
662
766
  const serialized = JSON.stringify(next)
663
767
  if (serialized === lastApplied) return
664
768
  lastApplied = serialized
@@ -686,11 +790,27 @@ function createSidebarController(
686
790
  }
687
791
  }
688
792
 
689
- return { prefs, collapsed, toggleCollapsed }
793
+ return {
794
+ prefs,
795
+ collapsed,
796
+ toggleCollapsed,
797
+ dispose() {
798
+ disposed = true
799
+ unwatch()
800
+ },
801
+ }
690
802
  }
691
803
 
692
804
  function QuotaSidebar(props: {
693
- api: TuiPluginApi
805
+ api: {
806
+ theme: { readonly current: ThemeCurrent }
807
+ event: {
808
+ on(
809
+ type: 'session.updated' | 'message.updated',
810
+ listener: () => void,
811
+ ): () => void
812
+ }
813
+ }
694
814
  controller: SidebarController
695
815
  sessionId: string
696
816
  }) {
@@ -699,10 +819,12 @@ function QuotaSidebar(props: {
699
819
  const [state, setState] = createSignal<SidebarState>(DEFAULT_SIDEBAR_STATE)
700
820
  let lastUpdated = 0
701
821
  let debounce: ReturnType<typeof setTimeout> | null = null
822
+ let disposed = false
702
823
 
703
824
  async function refresh() {
825
+ if (disposed) return
704
826
  const next = await readStateFromFile()
705
- if (next.lastUpdated !== lastUpdated) {
827
+ if (!disposed && next.lastUpdated !== lastUpdated) {
706
828
  lastUpdated = next.lastUpdated
707
829
  setState(next)
708
830
  }
@@ -727,9 +849,11 @@ function QuotaSidebar(props: {
727
849
  props.api.event.on('session.updated', scheduleRefresh),
728
850
  props.api.event.on('message.updated', scheduleRefresh),
729
851
  ]
730
- setTimeout(refresh, 300)
852
+ const initialRefresh = setTimeout(refresh, 300)
731
853
 
732
854
  onCleanup(() => {
855
+ disposed = true
856
+ clearTimeout(initialRefresh)
733
857
  if (debounce) clearTimeout(debounce)
734
858
  for (const u of unsubs) u()
735
859
  })
@@ -740,11 +864,10 @@ function QuotaSidebar(props: {
740
864
  // Vault mode: the host uses only the vault's accounts, which the file lists
741
865
  // in `vaultAccounts`; main and the local fallbacks are not shown.
742
866
  const vaultMode = () => state().vaultAccounts !== undefined
743
- const enabledVaultAccounts = () =>
744
- (state().vaultAccounts ?? []).filter((account) => account.enabled)
867
+ const vaultAccounts = () => state().vaultAccounts ?? []
745
868
  const hasData = () =>
746
869
  vaultMode()
747
- ? enabledVaultAccounts().length > 0
870
+ ? vaultAccounts().length > 0
748
871
  : state().main?.quota != null || enabledFallbacks().length > 0
749
872
 
750
873
  const headerLabel = () => {
@@ -759,6 +882,10 @@ function QuotaSidebar(props: {
759
882
  route: sessionRouting().route,
760
883
  })
761
884
  const activeAccount = () => resolveActiveAccount(sessionState())
885
+ const collapsedLines = () =>
886
+ getCollapsedAccountLines(sessionState(), {
887
+ showFallbacks: prefs().sections.fallbackAccounts,
888
+ })
762
889
  const quotaLabelWidth = () => computeQuotaLabelWidth(renderedQuotas(state()))
763
890
  const activeQuotaSummary = () =>
764
891
  getCollapsedQuotaSummary(activeAccount().quota)
@@ -788,8 +915,8 @@ function QuotaSidebar(props: {
788
915
  }
789
916
  const killedNames = () =>
790
917
  [
791
- state().main?.killed ? 'main' : '',
792
- ...enabledFallbacks()
918
+ !vaultMode() && state().main?.killed ? 'main' : '',
919
+ ...(vaultMode() ? vaultAccounts() : enabledFallbacks())
793
920
  .filter((f) => f.killed)
794
921
  .map((f) => f.label ?? f.id),
795
922
  ].filter(Boolean)
@@ -846,28 +973,17 @@ function QuotaSidebar(props: {
846
973
  </Show>
847
974
  </box>
848
975
 
849
- {/* Collapsed: active account's present quota windows + dot (red ⊘ when killed) */}
976
+ {/* Collapsed: one line per account, narrow quota text + dot (red ⊘ when killed) */}
850
977
  <Show when={collapsed() && hasData()}>
851
- <CollapsedRow theme={theme()} label={activeAccount().name}>
852
- <Show
853
- when={activeQuotaSummary().text != null}
854
- fallback={<text fg={theme().textMuted}>{'\u2014'}</text>}
855
- >
856
- <box flexDirection='row'>
857
- <text fg={toneColor(theme(), activeQuotaTone())}>
858
- <b>{activeQuotaSummary().text}</b>
859
- </text>
860
- <text
861
- fg={toneColor(
862
- theme(),
863
- activeAccount().killed ? 'err' : activeQuotaTone(),
864
- )}
865
- >
866
- {activeAccount().killed ? ' \u2298' : ' \u25cf'}
867
- </text>
868
- </box>
869
- </Show>
870
- </CollapsedRow>
978
+ <Index each={collapsedLines()}>
979
+ {(line) => (
980
+ <CollapsedAccountRow
981
+ theme={theme()}
982
+ line={line()}
983
+ tone={activeQuotaTone()}
984
+ />
985
+ )}
986
+ </Index>
871
987
  </Show>
872
988
 
873
989
  {/* Expanded: full sections. Also render when there's no data so the
@@ -887,7 +1003,7 @@ function QuotaSidebar(props: {
887
1003
  <Show
888
1004
  when={!vaultMode()}
889
1005
  fallback={
890
- <For each={enabledVaultAccounts()}>
1006
+ <For each={vaultAccounts()}>
891
1007
  {(account, index) => (
892
1008
  <AccountBlock
893
1009
  theme={theme()}
@@ -895,6 +1011,8 @@ function QuotaSidebar(props: {
895
1011
  name={account.label ?? account.id}
896
1012
  quota={account.quota}
897
1013
  killed={account.killed}
1014
+ vaultState={account.vaultState}
1015
+ hostDisabled={!account.enabled}
898
1016
  active={sessionRouting().activeId === account.id}
899
1017
  pacingEnabled={prefs().sections.pacing}
900
1018
  labelWidth={quotaLabelWidth()}
@@ -1064,8 +1182,48 @@ const tui: TuiPlugin = async (api) => {
1064
1182
  }
1065
1183
  }
1066
1184
 
1067
- const plugin: TuiPluginModule & { id: string } = {
1185
+ async function setup(
1186
+ context: Plugin.Context,
1187
+ options: TuiTransportOptions = {},
1188
+ ) {
1189
+ if (options.sidebarText !== undefined || options.sidebar)
1190
+ return setupTui(context, options)
1191
+ const root = await readTuiPreferencesFile()
1192
+ const controller = createSidebarController(resolveOpenaiAuthPrefs(root))
1193
+ const api = {
1194
+ theme: {
1195
+ get current() {
1196
+ return context.theme
1197
+ },
1198
+ },
1199
+ event: {
1200
+ on: (type: 'session.updated' | 'message.updated', listener: () => void) =>
1201
+ context.data.on(
1202
+ type === 'session.updated'
1203
+ ? 'session.metadata.updated'
1204
+ : 'session.usage.updated',
1205
+ listener,
1206
+ ),
1207
+ },
1208
+ }
1209
+ return setupTui(context, {
1210
+ ...options,
1211
+ sidebar: {
1212
+ render: (input) => (
1213
+ <QuotaSidebar
1214
+ api={api}
1215
+ controller={controller}
1216
+ sessionId={input.sessionID}
1217
+ />
1218
+ ),
1219
+ dispose: controller.dispose,
1220
+ },
1221
+ })
1222
+ }
1223
+
1224
+ const plugin: TuiPluginModule & { id: string; setup: typeof setup } = {
1068
1225
  id: ID,
1226
+ setup,
1069
1227
  tui,
1070
1228
  }
1071
1229