@cortexkit/opencode-anthropic-auth 2.0.2 → 2.1.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 (59) hide show
  1. package/README.md +35 -845
  2. package/dist/cli-failure.d.ts +38 -7
  3. package/dist/cli.js +50 -102
  4. package/dist/custody-mode.d.ts +1 -0
  5. package/dist/fable-fallback.d.ts +6 -2
  6. package/dist/index-0kxy8k2c.js +2 -0
  7. package/dist/index-1nwcnxp8.js +42 -0
  8. package/dist/index-53nnxq39.js +2 -0
  9. package/dist/{index-2arfb1y3.js → index-6chd6jnr.js} +1 -1
  10. package/dist/{index-1tbpt45x.js → index-ah5s0bew.js} +1 -1
  11. package/dist/index-b1dvkp15.js +2 -0
  12. package/dist/{index-mge0rw54.js → index-f2ne65w4.js} +1 -1
  13. package/dist/{index-rqync0pc.js → index-hazrk5c3.js} +1 -1
  14. package/dist/{index-1jkyhgyr.js → index-jexctf5w.js} +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/quota-preflight.d.ts +1 -0
  17. package/dist/request-class.d.ts +10 -0
  18. package/dist/rpc/port-file.js +1 -1
  19. package/dist/rpc/protocol.js +1 -1
  20. package/dist/rpc/rpc-client.js +1 -1
  21. package/dist/rpc/rpc-dir.js +1 -1
  22. package/dist/setup/command.d.ts +5 -14
  23. package/dist/setup/native-activation.d.ts +21 -8
  24. package/dist/setup/native-enrollment.d.ts +21 -0
  25. package/dist/setup/native-local.d.ts +31 -1
  26. package/dist/setup/native-login.d.ts +12 -1
  27. package/dist/setup/process-fence.d.ts +0 -6
  28. package/dist/setup/types.d.ts +9 -0
  29. package/dist/sidebar-state.d.ts +20 -2
  30. package/dist/sidebar-state.js +1 -1
  31. package/dist/transform.d.ts +2 -8
  32. package/dist/tui-preferences.js +1 -1
  33. package/dist/tui.d.ts +0 -2
  34. package/dist/v2/activation.d.ts +2 -0
  35. package/dist/v2/adapter.d.ts +11 -1
  36. package/dist/v2/cachekeep.d.ts +19 -0
  37. package/dist/v2/menu.d.ts +2 -1
  38. package/dist/v2/server.js +4 -4
  39. package/dist/v2/setup.d.ts +2 -0
  40. package/dist/v2/source.d.ts +1 -0
  41. package/dist/v2/tui.js +1 -1
  42. package/package.json +10 -6
  43. package/src/sidebar-state.ts +105 -30
  44. package/src/tui-compiled/sidebar-state.ts +105 -30
  45. package/src/tui-compiled/tui.tsx +395 -241
  46. package/src/tui.tsx +199 -58
  47. package/dist/index-227rsmfv.js +0 -42
  48. package/dist/index-74r84r5f.js +0 -1
  49. package/dist/index-b3mxpp4w.js +0 -2
  50. package/dist/index-fn0zqnnw.js +0 -3
  51. package/dist/index-j0wsgy54.js +0 -449
  52. package/dist/index-jc8p0tkv.js +0 -2
  53. package/dist/setup/activation.d.ts +0 -7
  54. package/dist/setup/command.test.d.ts +0 -1
  55. package/dist/setup/detect.d.ts +0 -8
  56. package/dist/setup/host-executable.d.ts +0 -3
  57. package/dist/setup/jsonc.d.ts +0 -9
  58. package/dist/setup/opencode-config.d.ts +0 -18
  59. package/dist/setup/pi.d.ts +0 -18
package/src/tui.tsx CHANGED
@@ -3,16 +3,23 @@
3
3
  import { readFileSync } from 'node:fs'
4
4
  import { dirname, join } from 'node:path'
5
5
  import { fileURLToPath } from 'node:url'
6
- import { formatQuotaMoney } from '@cortexkit/anthropic-auth-core'
6
+ import {
7
+ formatCreditAmounts,
8
+ type QuotaWindowPart,
9
+ quotaDisplayProjection,
10
+ quotaWindowParts,
11
+ } from '@cortexkit/anthropic-auth-core'
7
12
  import type {
8
13
  TuiPlugin,
9
14
  TuiPluginApi,
10
15
  TuiPluginModule,
11
16
  } from '@opencode-ai/plugin/tui'
17
+ import { type BoxRenderable, TextAttributes } from '@opentui/core'
12
18
  import {
13
19
  createEffect,
14
20
  createSignal,
15
21
  For,
22
+ Index,
16
23
  type JSX,
17
24
  onCleanup,
18
25
  Show,
@@ -25,7 +32,8 @@ import {
25
32
  DEFAULT_SIDEBAR_STATE,
26
33
  FIVE_HOUR_MS,
27
34
  formatPrimeAccountValue,
28
- formatScopedQuotaLabel,
35
+ formatQuotaRowLabel,
36
+ getCollapsedAccounts,
29
37
  getCollapsedQuotaSummary,
30
38
  getFableRecoverySummary,
31
39
  getSidebarState,
@@ -232,7 +240,60 @@ function CollapsedRow(props: {
232
240
  )
233
241
  }
234
242
 
235
- // Quota window row: muted label left, tone-colored bar + percentage right,
243
+ function CollapsedAccountRow(props: {
244
+ theme: ThemeCurrent
245
+ account: ReturnType<typeof getCollapsedAccounts>[number]
246
+ tone: Tone
247
+ }) {
248
+ const [width, setWidth] = createSignal(40)
249
+ let row: BoxRenderable | undefined
250
+ const summary = () => getCollapsedQuotaSummary(props.account.quota)
251
+ const value = () => summary().text ?? '—'
252
+ const labelWidth = () => Math.max(0, width() - Bun.stringWidth(value()) - 1)
253
+ const dim = () =>
254
+ !props.account.active || props.account.quotaDated || summary().stale
255
+ const attributes = () => (dim() ? TextAttributes.DIM : TextAttributes.BOLD)
256
+ const color = () =>
257
+ dim() ? props.theme.textMuted : toneColor(props.theme, props.tone)
258
+ return (
259
+ <box
260
+ width='100%'
261
+ height={1}
262
+ flexDirection='row'
263
+ ref={(element: BoxRenderable) => {
264
+ row = element
265
+ setWidth(element.width || 40)
266
+ }}
267
+ onSizeChange={() => setWidth(row?.width || 40)}
268
+ >
269
+ <Show when={labelWidth() > 0}>
270
+ <text
271
+ width={labelWidth()}
272
+ flexShrink={0}
273
+ wrapMode='none'
274
+ fg={color()}
275
+ attributes={attributes()}
276
+ >
277
+ {formatQuotaRowLabel(props.account.name, labelWidth())}
278
+ </text>
279
+ <text width={1} flexShrink={0}>
280
+ {' '}
281
+ </text>
282
+ </Show>
283
+ <text
284
+ width={Bun.stringWidth(value())}
285
+ flexShrink={0}
286
+ wrapMode='none'
287
+ fg={color()}
288
+ attributes={attributes()}
289
+ >
290
+ {value()}
291
+ </text>
292
+ </box>
293
+ )
294
+ }
295
+
296
+ // Quota window row: muted label left, tone-colored bar + shared figure right,
236
297
  // with an optional muted reset suffix. When pacing data is present, the bar
237
298
  // gains a pace segment and an off-pace window adds a muted subline with the
238
299
  // reserve/deficit delta and the projected runout.
@@ -240,12 +301,13 @@ function QuotaRow(props: {
240
301
  theme: ThemeCurrent
241
302
  appearance: AppearancePrefs
242
303
  label: string
243
- window: { usedPercent: number; resetsAt?: string } | undefined
304
+ part: QuotaWindowPart | undefined
305
+ usedPercent: number | undefined
244
306
  pacing: QuotaPacing | null
245
307
  binding?: boolean
246
308
  }) {
247
- const used = () => props.window?.usedPercent ?? 0
248
- const reset = () => formatResetIn(props.window?.resetsAt)
309
+ const used = () => props.usedPercent ?? 0
310
+ const reset = () => props.part?.resetIn
249
311
  const paceLine = () => {
250
312
  const pacing = props.pacing
251
313
  if (!pacing || pacing.state === 'on-pace') return null
@@ -257,20 +319,24 @@ function QuotaRow(props: {
257
319
  }
258
320
  return (
259
321
  <Show
260
- when={props.window}
322
+ when={props.part}
261
323
  fallback={
262
324
  <box width='100%' flexDirection='row'>
263
- <text fg={props.theme.textMuted}>{props.label.padEnd(3)}</text>
325
+ <text
326
+ fg={props.theme.textMuted}
327
+ >{`${formatQuotaRowLabel(props.label)} `}</text>
264
328
  <text fg={props.theme.textMuted}>{'\u2014'}</text>
265
329
  </box>
266
330
  }
267
331
  >
268
- {/* Left group (label · bar · pct) stays left-aligned in fixed columns so
269
- bars and percentages line up across rows; the reset time is pushed to
332
+ {/* Left group (label · bar · figure) stays left-aligned in fixed columns so
333
+ bars and figures line up across rows; the reset time is pushed to
270
334
  the right edge so reset times align in their own right column. */}
271
335
  <box width='100%' flexDirection='row' justifyContent='space-between'>
272
336
  <box flexDirection='row'>
273
- <text fg={props.theme.textMuted}>{props.label.padEnd(3)}</text>
337
+ <text
338
+ fg={props.theme.textMuted}
339
+ >{`${formatQuotaRowLabel(props.label)} `}</text>
274
340
  <For each={quotaBarSegments(used(), props.appearance, props.pacing)}>
275
341
  {(segment) => (
276
342
  <text fg={toneColor(props.theme, segment.tone)}>
@@ -281,7 +347,7 @@ function QuotaRow(props: {
281
347
  <text
282
348
  fg={toneColor(props.theme, usageTone(used(), props.appearance))}
283
349
  >
284
- {` ${String(Math.round(used())).padStart(3)}%${props.binding ? ' •' : ''}`}
350
+ {` ${props.part?.figure}${props.binding ? ' •' : ''}`}
285
351
  </text>
286
352
  </box>
287
353
  <Show when={reset()}>
@@ -290,7 +356,7 @@ function QuotaRow(props: {
290
356
  </box>
291
357
  <Show when={paceLine()}>
292
358
  <box width='100%' flexDirection='row'>
293
- <text fg={props.theme.textMuted}>{' '}</text>
359
+ <text fg={props.theme.textMuted}>{formatQuotaRowLabel('', 11)}</text>
294
360
  <text
295
361
  fg={toneColor(
296
362
  props.theme,
@@ -305,6 +371,22 @@ function QuotaRow(props: {
305
371
  )
306
372
  }
307
373
 
374
+ // The shared credit figure followed by the spent and limit amounts:
375
+ // `credits 75% left · $25.00/$100.00`. When that line is wider than the
376
+ // measured width, the amounts drop their cents (`$1,250/$5,000`) instead of
377
+ // wrapping onto a second line.
378
+ function creditLine(
379
+ figure: string,
380
+ extraUsage: AccountQuota['extraUsage'],
381
+ width: number | undefined,
382
+ ) {
383
+ const amounts = formatCreditAmounts(extraUsage)
384
+ if (!amounts) return figure
385
+ const line = `${figure} · ${amounts}`
386
+ if (width === undefined || Bun.stringWidth(line) <= width) return line
387
+ return `${figure} · ${formatCreditAmounts(extraUsage, { wholeUnits: true })}`
388
+ }
389
+
308
390
  // Account block: header row (name + status word) then per-window quota rows.
309
391
  // Pacing is computed here — per window, against the wall clock at render time
310
392
  // (re-evaluated on every state poll) — and disabled by passing false.
@@ -317,19 +399,23 @@ function AccountBlock(props: {
317
399
  pacingEnabled: boolean
318
400
  needsReauth?: boolean
319
401
  vaultReauth?: boolean
402
+ stateLine?: string
403
+ quotaDated?: boolean
320
404
  tierLabel?: string
321
405
  marginTop?: number
322
406
  }) {
323
407
  const statusWord = () =>
324
- props.needsReauth
325
- ? 're-login'
326
- : props.vaultReauth
327
- ? 'vault reauth'
328
- : props.active
329
- ? 'active'
330
- : 'idle'
408
+ props.quotaDated && props.quota
409
+ ? 'dated'
410
+ : props.needsReauth
411
+ ? 're-login'
412
+ : props.vaultReauth
413
+ ? 'needs sign-in in the vault'
414
+ : props.active
415
+ ? 'active'
416
+ : 'idle'
331
417
  const statusTone = (): Tone =>
332
- props.needsReauth || props.vaultReauth
418
+ props.needsReauth || props.vaultReauth || props.stateLine
333
419
  ? 'warn'
334
420
  : props.active
335
421
  ? 'ok'
@@ -340,9 +426,15 @@ function AccountBlock(props: {
340
426
  | undefined,
341
427
  windowMs: number,
342
428
  ) =>
343
- props.pacingEnabled && window
429
+ props.pacingEnabled && !props.quotaDated && window
344
430
  ? computeQuotaPacing(window, windowMs, Date.now())
345
431
  : null
432
+ const parts = () => {
433
+ const now = Date.now()
434
+ return quotaWindowParts(quotaDisplayProjection(props.quota, now), { now })
435
+ }
436
+ const partFor = (name: string) =>
437
+ parts().windows.find((part) => part.name === name)
346
438
  return (
347
439
  <box width='100%' flexDirection='column' marginTop={props.marginTop ?? 0}>
348
440
  <box width='100%' flexDirection='row' justifyContent='space-between'>
@@ -353,6 +445,9 @@ function AccountBlock(props: {
353
445
  <b>{statusWord()}</b>
354
446
  </text>
355
447
  </box>
448
+ <Show when={props.stateLine}>
449
+ <text fg={props.theme.textMuted}>{props.stateLine}</text>
450
+ </Show>
356
451
  <Show
357
452
  when={props.quota}
358
453
  fallback={<text fg={props.theme.textMuted}>{'checking\u2026'}</text>}
@@ -361,7 +456,8 @@ function AccountBlock(props: {
361
456
  theme={props.theme}
362
457
  appearance={props.appearance}
363
458
  label='5h'
364
- window={props.quota?.five_hour}
459
+ part={partFor('5h')}
460
+ usedPercent={props.quota?.five_hour?.usedPercent}
365
461
  pacing={pacingFor(props.quota?.five_hour, FIVE_HOUR_MS)}
366
462
  binding={props.quota?.bindingWindow === 'five_hour'}
367
463
  />
@@ -369,7 +465,8 @@ function AccountBlock(props: {
369
465
  theme={props.theme}
370
466
  appearance={props.appearance}
371
467
  label='7d'
372
- window={props.quota?.seven_day}
468
+ part={partFor('7d')}
469
+ usedPercent={props.quota?.seven_day?.usedPercent}
373
470
  pacing={pacingFor(props.quota?.seven_day, SEVEN_DAY_MS)}
374
471
  binding={props.quota?.bindingWindow === 'seven_day'}
375
472
  />
@@ -378,29 +475,52 @@ function AccountBlock(props: {
378
475
  <QuotaRow
379
476
  theme={props.theme}
380
477
  appearance={props.appearance}
381
- label={formatScopedQuotaLabel(window.title)}
382
- window={window}
478
+ label={partFor(window.title)?.name ?? window.title}
479
+ part={partFor(window.title)}
480
+ usedPercent={window.usedPercent}
383
481
  pacing={pacingFor(window, SEVEN_DAY_MS)}
384
482
  binding={props.quota?.bindingWindow === window.id}
385
483
  />
386
484
  )}
387
485
  </For>
388
- <Show when={props.quota?.extraUsage}>
389
- {(extraUsage: () => NonNullable<AccountQuota['extraUsage']>) => (
390
- <text
391
- fg={toneColor(
392
- props.theme,
393
- extraUsage().severity === 'critical'
394
- ? 'err'
395
- : extraUsage().severity === 'warning' ||
396
- extraUsage().exhausted
397
- ? 'warn'
398
- : 'muted',
399
- )}
400
- >
401
- {`credits ${formatQuotaMoney(extraUsage().used)}/${formatQuotaMoney(extraUsage().limit)}${extraUsage().exhausted ? ' · exhausted' : ''}`}
402
- </text>
403
- )}
486
+ <Show when={partFor('credits')}>
487
+ {(budget: () => QuotaWindowPart) => {
488
+ // The line box's laid-out width, read when it mounts and on each
489
+ // resize. It is undefined (amounts keep their cents) until layout.
490
+ const [width, setWidth] = createSignal<number>()
491
+ let line: BoxRenderable | undefined
492
+ return (
493
+ <box
494
+ width='100%'
495
+ ref={(element: BoxRenderable) => {
496
+ line = element
497
+ setWidth(element.width || undefined)
498
+ }}
499
+ onSizeChange={() => setWidth(line?.width || undefined)}
500
+ >
501
+ <text
502
+ fg={toneColor(
503
+ props.theme,
504
+ props.quota?.extraUsage?.severity === 'critical'
505
+ ? 'err'
506
+ : props.quota?.extraUsage?.severity === 'warning' ||
507
+ props.quota?.extraUsage?.exhausted
508
+ ? 'warn'
509
+ : 'muted',
510
+ )}
511
+ >
512
+ {creditLine(
513
+ budget().figure,
514
+ props.quota?.extraUsage,
515
+ width(),
516
+ )}
517
+ </text>
518
+ </box>
519
+ )
520
+ }}
521
+ </Show>
522
+ <Show when={parts().age}>
523
+ <text fg={props.theme.textMuted}>{parts().age}</text>
404
524
  </Show>
405
525
  <Show when={props.quota?.fallbackAdvised === true}>
406
526
  <text fg={props.theme.textMuted}>{'→ fallback advised'}</text>
@@ -410,8 +530,6 @@ function AccountBlock(props: {
410
530
  )
411
531
  }
412
532
 
413
- export { formatQuotaMoney }
414
-
415
533
  // --- Quota dialog content ---------------------------------------------------
416
534
 
417
535
  // Renders the same rich visualization as the sidebar (account blocks with
@@ -437,7 +555,9 @@ function QuotaDialogContent(props: {
437
555
  setTimeout(refresh, 0)
438
556
  const theme = () => props.api.theme.current
439
557
  const enabledFallbacks = () =>
440
- (state().fallbacks ?? []).filter((f) => f.enabled)
558
+ (state().fallbacks ?? []).filter(
559
+ (f) => f.enabled || (f.custodyState && f.custodyState !== 'off'),
560
+ )
441
561
  return (
442
562
  <box flexDirection='column' padding={2} width='100%' alignItems='center'>
443
563
  <box flexDirection='column' width={58}>
@@ -454,6 +574,8 @@ function QuotaDialogContent(props: {
454
574
  active={state().activeId === 'main'}
455
575
  pacingEnabled={prefs().sections.pacing}
456
576
  tierLabel={state().main?.tierLabel}
577
+ stateLine={state().main?.stateLine}
578
+ quotaDated={state().main?.quotaDated}
457
579
  />
458
580
  <Show when={prefs().sections.fallbackAccounts}>
459
581
  <For each={enabledFallbacks()}>
@@ -467,6 +589,9 @@ function QuotaDialogContent(props: {
467
589
  pacingEnabled={prefs().sections.pacing}
468
590
  tierLabel={fb.tierLabel}
469
591
  needsReauth={fb.needsReauth}
592
+ vaultReauth={fb.vaultReauth}
593
+ stateLine={fb.stateLine}
594
+ quotaDated={fb.quotaDated}
470
595
  marginTop={1}
471
596
  />
472
597
  )}
@@ -594,9 +719,13 @@ function QuotaSidebar(props: {
594
719
 
595
720
  const theme = () => props.api.theme.current
596
721
  const enabledFallbacks = () =>
597
- (state().fallbacks ?? []).filter((f) => f.enabled)
722
+ (state().fallbacks ?? []).filter(
723
+ (f) => f.enabled || (f.custodyState && f.custodyState !== 'off'),
724
+ )
598
725
  const hasData = () =>
599
- state().main?.quota != null || enabledFallbacks().length > 0
726
+ state().main?.quota != null ||
727
+ state().main?.stateLine != null ||
728
+ enabledFallbacks().length > 0
600
729
 
601
730
  const headerLabel = () => {
602
731
  const name = prefs().header.label
@@ -607,6 +736,7 @@ function QuotaSidebar(props: {
607
736
  getCollapsedQuotaSummary(activeAccount().quota)
608
737
  const activePacingDeficit = () => {
609
738
  if (!prefs().sections.pacing) return false
739
+ if (activeAccount().quotaDated) return null
610
740
  const quota = activeAccount().quota
611
741
  if (!quota) return false
612
742
  const now = Date.now()
@@ -724,18 +854,25 @@ function QuotaSidebar(props: {
724
854
  </Show>
725
855
  </box>
726
856
 
727
- {/* Collapsed: active account 5h + 7d quota, plus fast-mode when on */}
857
+ {/* Collapsed accounts keep placement order; only a fresh active row is emphasized. */}
728
858
  <Show when={collapsed() && hasData()}>
729
- <CollapsedRow theme={theme()} label={activeAccount().name}>
730
- <Show
731
- when={activeQuotaSummary().text != null}
732
- fallback={<text fg={theme().textMuted}>{'\u2014'}</text>}
733
- >
734
- <text fg={toneColor(theme(), activeQuotaTone())}>
735
- <b>{activeQuotaSummary().text}</b>
736
- </text>
737
- </Show>
738
- </CollapsedRow>
859
+ {/* Index keys rows by position: each state update builds new account
860
+ objects, so For would rebuild every row and its measured width.
861
+ Passing the accessor's value as a prop keeps the row reactive. */}
862
+ <Index
863
+ each={getCollapsedAccounts(
864
+ state(),
865
+ prefs().sections.fallbackAccounts,
866
+ )}
867
+ >
868
+ {(account) => (
869
+ <CollapsedAccountRow
870
+ theme={theme()}
871
+ account={account()}
872
+ tone={activeQuotaTone()}
873
+ />
874
+ )}
875
+ </Index>
739
876
  <Show when={state().fastMode}>
740
877
  <CollapsedRow theme={theme()} label='Mode'>
741
878
  <text fg={toneColor(theme(), 'accent')}>
@@ -776,6 +913,8 @@ function QuotaSidebar(props: {
776
913
  active={state().activeId === 'main'}
777
914
  pacingEnabled={prefs().sections.pacing}
778
915
  tierLabel={state().main?.tierLabel}
916
+ stateLine={state().main?.stateLine}
917
+ quotaDated={state().main?.quotaDated}
779
918
  />
780
919
  <Show when={prefs().sections.fallbackAccounts}>
781
920
  <For each={enabledFallbacks()}>
@@ -790,6 +929,8 @@ function QuotaSidebar(props: {
790
929
  tierLabel={fb.tierLabel}
791
930
  needsReauth={fb.needsReauth}
792
931
  vaultReauth={fb.vaultReauth}
932
+ stateLine={fb.stateLine}
933
+ quotaDated={fb.quotaDated}
793
934
  marginTop={1}
794
935
  />
795
936
  )}