@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.
- package/README.md +35 -845
- package/dist/cli-failure.d.ts +38 -7
- package/dist/cli.js +50 -102
- package/dist/custody-mode.d.ts +1 -0
- package/dist/fable-fallback.d.ts +6 -2
- package/dist/index-0kxy8k2c.js +2 -0
- package/dist/index-1nwcnxp8.js +42 -0
- package/dist/index-53nnxq39.js +2 -0
- package/dist/{index-2arfb1y3.js → index-6chd6jnr.js} +1 -1
- package/dist/{index-1tbpt45x.js → index-ah5s0bew.js} +1 -1
- package/dist/index-b1dvkp15.js +2 -0
- package/dist/{index-mge0rw54.js → index-f2ne65w4.js} +1 -1
- package/dist/{index-rqync0pc.js → index-hazrk5c3.js} +1 -1
- package/dist/{index-1jkyhgyr.js → index-jexctf5w.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/quota-preflight.d.ts +1 -0
- package/dist/request-class.d.ts +10 -0
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.js +1 -1
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/setup/command.d.ts +5 -14
- package/dist/setup/native-activation.d.ts +21 -8
- package/dist/setup/native-enrollment.d.ts +21 -0
- package/dist/setup/native-local.d.ts +31 -1
- package/dist/setup/native-login.d.ts +12 -1
- package/dist/setup/process-fence.d.ts +0 -6
- package/dist/setup/types.d.ts +9 -0
- package/dist/sidebar-state.d.ts +20 -2
- package/dist/sidebar-state.js +1 -1
- package/dist/transform.d.ts +2 -8
- package/dist/tui-preferences.js +1 -1
- package/dist/tui.d.ts +0 -2
- package/dist/v2/activation.d.ts +2 -0
- package/dist/v2/adapter.d.ts +11 -1
- package/dist/v2/cachekeep.d.ts +19 -0
- package/dist/v2/menu.d.ts +2 -1
- package/dist/v2/server.js +4 -4
- package/dist/v2/setup.d.ts +2 -0
- package/dist/v2/source.d.ts +1 -0
- package/dist/v2/tui.js +1 -1
- package/package.json +10 -6
- package/src/sidebar-state.ts +105 -30
- package/src/tui-compiled/sidebar-state.ts +105 -30
- package/src/tui-compiled/tui.tsx +395 -241
- package/src/tui.tsx +199 -58
- package/dist/index-227rsmfv.js +0 -42
- package/dist/index-74r84r5f.js +0 -1
- package/dist/index-b3mxpp4w.js +0 -2
- package/dist/index-fn0zqnnw.js +0 -3
- package/dist/index-j0wsgy54.js +0 -449
- package/dist/index-jc8p0tkv.js +0 -2
- package/dist/setup/activation.d.ts +0 -7
- package/dist/setup/command.test.d.ts +0 -1
- package/dist/setup/detect.d.ts +0 -8
- package/dist/setup/host-executable.d.ts +0 -3
- package/dist/setup/jsonc.d.ts +0 -9
- package/dist/setup/opencode-config.d.ts +0 -18
- package/dist/setup/pi.d.ts +0 -18
package/src/sidebar-state.ts
CHANGED
|
@@ -2,6 +2,7 @@ export interface QuotaWindow {
|
|
|
2
2
|
usedPercent: number
|
|
3
3
|
remainingPercent: number
|
|
4
4
|
resetsAt?: string
|
|
5
|
+
checkedAt?: number
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export interface ScopedQuotaWindow extends QuotaWindow {
|
|
@@ -12,6 +13,7 @@ export interface ScopedQuotaWindow extends QuotaWindow {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface AccountQuota {
|
|
16
|
+
checkedAt?: number
|
|
15
17
|
five_hour?: QuotaWindow
|
|
16
18
|
seven_day?: QuotaWindow
|
|
17
19
|
scoped?: ScopedQuotaWindow[]
|
|
@@ -31,10 +33,12 @@ export interface SidebarAccountState {
|
|
|
31
33
|
label: string | undefined
|
|
32
34
|
quota: AccountQuota | null
|
|
33
35
|
enabled: boolean
|
|
36
|
+
stateLine?: string
|
|
37
|
+
quotaDated?: boolean
|
|
34
38
|
// True when the account's refresh token is permanently dead (400
|
|
35
39
|
// invalid_grant) and it needs a re-login — distinct from a transient backoff.
|
|
36
40
|
needsReauth: boolean
|
|
37
|
-
// True when the vault
|
|
41
|
+
// True when the vault credential needs a new sign-in.
|
|
38
42
|
vaultReauth?: boolean
|
|
39
43
|
// A gate alone does not prove the sidecar can serve a request.
|
|
40
44
|
vaultServed?: boolean
|
|
@@ -77,7 +81,10 @@ export interface PrimeSidebarAccountState {
|
|
|
77
81
|
export interface SidebarState {
|
|
78
82
|
main: {
|
|
79
83
|
quota: AccountQuota | null
|
|
84
|
+
enabled?: boolean
|
|
80
85
|
tierLabel?: string
|
|
86
|
+
stateLine?: string
|
|
87
|
+
quotaDated?: boolean
|
|
81
88
|
quotaBackedOff?: boolean
|
|
82
89
|
quotaBackoffUntil?: number
|
|
83
90
|
refreshBackedOff?: boolean
|
|
@@ -120,7 +127,12 @@ import {
|
|
|
120
127
|
import { tmpdir } from 'node:os'
|
|
121
128
|
import { dirname, join } from 'node:path'
|
|
122
129
|
import type { PrimeUsageCounters } from '@cortexkit/anthropic-auth-core'
|
|
123
|
-
import {
|
|
130
|
+
import {
|
|
131
|
+
formatQuota,
|
|
132
|
+
isQuotaStale,
|
|
133
|
+
logger,
|
|
134
|
+
quotaDisplayProjection,
|
|
135
|
+
} from '@cortexkit/anthropic-auth-core'
|
|
124
136
|
|
|
125
137
|
const STATE_FILE_ENV = 'OPENCODE_ANTHROPIC_AUTH_SIDEBAR_STATE_FILE'
|
|
126
138
|
const DEFAULT_STATE_DIR = join(tmpdir(), 'opencode-anthropic-auth')
|
|
@@ -229,12 +241,14 @@ function normalizeQuotaWindow(value: unknown): QuotaWindow | undefined {
|
|
|
229
241
|
usedPercent,
|
|
230
242
|
remainingPercent,
|
|
231
243
|
resetsAt: typeof value.resetsAt === 'string' ? value.resetsAt : undefined,
|
|
244
|
+
...(isFiniteNumber(value.checkedAt) && { checkedAt: value.checkedAt }),
|
|
232
245
|
}
|
|
233
246
|
}
|
|
234
247
|
|
|
235
248
|
function normalizeAccountQuota(value: unknown): AccountQuota | null {
|
|
236
249
|
if (!isRecord(value)) return null
|
|
237
250
|
const quota: AccountQuota = {}
|
|
251
|
+
if (isFiniteNumber(value.checkedAt)) quota.checkedAt = value.checkedAt
|
|
238
252
|
const fiveHour = normalizeQuotaWindow(value.five_hour)
|
|
239
253
|
const sevenDay = normalizeQuotaWindow(value.seven_day)
|
|
240
254
|
if (fiveHour) quota.five_hour = fiveHour
|
|
@@ -327,6 +341,9 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
327
341
|
if (isRecord(raw.main)) {
|
|
328
342
|
const m = raw.main
|
|
329
343
|
main.quota = normalizeAccountQuota(m.quota)
|
|
344
|
+
if (typeof m.enabled === 'boolean') main.enabled = m.enabled
|
|
345
|
+
if (typeof m.stateLine === 'string') main.stateLine = m.stateLine
|
|
346
|
+
if (m.quotaDated === true) main.quotaDated = true
|
|
330
347
|
if (typeof m.tierLabel === 'string' && m.tierLabel.trim()) {
|
|
331
348
|
main.tierLabel = m.tierLabel.trim()
|
|
332
349
|
}
|
|
@@ -349,6 +366,10 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
349
366
|
label: typeof entry.label === 'string' ? entry.label : undefined,
|
|
350
367
|
quota: normalizeAccountQuota(entry.quota),
|
|
351
368
|
enabled: typeof entry.enabled === 'boolean' ? entry.enabled : false,
|
|
369
|
+
...(typeof entry.stateLine === 'string' && {
|
|
370
|
+
stateLine: entry.stateLine,
|
|
371
|
+
}),
|
|
372
|
+
...(entry.quotaDated === true && { quotaDated: true }),
|
|
352
373
|
needsReauth:
|
|
353
374
|
typeof entry.needsReauth === 'boolean' ? entry.needsReauth : false,
|
|
354
375
|
...(entry.vaultReauth === true && { vaultReauth: true }),
|
|
@@ -635,7 +656,9 @@ async function writeSidebarStateAtomic(
|
|
|
635
656
|
return 'lock-lost-before-rename'
|
|
636
657
|
}
|
|
637
658
|
await rename(tempFile, stateFile)
|
|
638
|
-
|
|
659
|
+
// Look the hooks up again: a test may start listening while this write is
|
|
660
|
+
// in flight, after reading the old file, and must still hear this rename.
|
|
661
|
+
await sidebarStateWriteHooksFor(stateFile)?.afterRename?.(stateFile)
|
|
639
662
|
if (!(await ownsLock())) return 'lock-lost-after-rename'
|
|
640
663
|
return 'written'
|
|
641
664
|
} catch (error) {
|
|
@@ -792,13 +815,13 @@ export function resolveActiveAccount(state: SidebarState): {
|
|
|
792
815
|
id: string
|
|
793
816
|
name: string
|
|
794
817
|
quota: AccountQuota | null
|
|
818
|
+
stateLine?: string
|
|
819
|
+
quotaDated?: boolean
|
|
795
820
|
} {
|
|
796
821
|
const activeId = state.activeId
|
|
797
822
|
if (activeId && activeId !== 'main') {
|
|
798
|
-
//
|
|
799
|
-
// accounts
|
|
800
|
-
// enabled. Kept so this pure helper stays correct for any caller and is
|
|
801
|
-
// exercised directly by the unit tests.
|
|
823
|
+
// A disabled fallback cannot remain the active route, even though vault
|
|
824
|
+
// accounts stay visible in the expanded inventory while turned off.
|
|
802
825
|
const fallback = state.fallbacks?.find(
|
|
803
826
|
(account) => account.enabled && account.id === activeId,
|
|
804
827
|
)
|
|
@@ -807,15 +830,22 @@ export function resolveActiveAccount(state: SidebarState): {
|
|
|
807
830
|
id: fallback.id,
|
|
808
831
|
name: fallback.label ?? fallback.id,
|
|
809
832
|
quota: fallback.quota,
|
|
833
|
+
...(fallback.stateLine && {
|
|
834
|
+
stateLine: fallback.stateLine,
|
|
835
|
+
quotaDated: fallback.quotaDated,
|
|
836
|
+
}),
|
|
810
837
|
}
|
|
811
838
|
}
|
|
812
839
|
}
|
|
813
|
-
return {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
840
|
+
return {
|
|
841
|
+
id: 'main',
|
|
842
|
+
name: 'main',
|
|
843
|
+
quota: state.main?.quota ?? null,
|
|
844
|
+
...(state.main?.stateLine && {
|
|
845
|
+
stateLine: state.main.stateLine,
|
|
846
|
+
quotaDated: state.main.quotaDated,
|
|
847
|
+
}),
|
|
848
|
+
}
|
|
819
849
|
}
|
|
820
850
|
|
|
821
851
|
/**
|
|
@@ -903,49 +933,94 @@ export function getFableRecoverySummary(
|
|
|
903
933
|
return `Opus 4.8 · ${recovery.remaining} left`
|
|
904
934
|
}
|
|
905
935
|
|
|
906
|
-
|
|
936
|
+
/** Keep full shared names when they fit; truncate only to preserve row columns. */
|
|
937
|
+
export function formatQuotaRowLabel(name: string, width = 10): string {
|
|
938
|
+
if (width <= 0) return ''
|
|
939
|
+
let label = name
|
|
940
|
+
if (Bun.stringWidth(label) > width) {
|
|
941
|
+
label = ''
|
|
942
|
+
for (const character of name) {
|
|
943
|
+
if (Bun.stringWidth(label + character) > width - 1) break
|
|
944
|
+
label += character
|
|
945
|
+
}
|
|
946
|
+
label += '…'
|
|
947
|
+
}
|
|
948
|
+
return label + ' '.repeat(Math.max(0, width - Bun.stringWidth(label)))
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
export function getCollapsedQuotaSummary(
|
|
952
|
+
quota: AccountQuota | null,
|
|
953
|
+
now = Date.now(),
|
|
954
|
+
): {
|
|
907
955
|
fiveHourUsedPercent: number | null
|
|
908
956
|
sevenDayUsedPercent: number | null
|
|
909
957
|
scopedUsedPercents: number[]
|
|
910
958
|
text: string | null
|
|
959
|
+
stale: boolean
|
|
911
960
|
} {
|
|
912
961
|
const fiveHourUsedPercent = quota?.five_hour?.usedPercent ?? null
|
|
913
962
|
const sevenDayUsedPercent = quota?.seven_day?.usedPercent ?? null
|
|
914
963
|
const scoped = quota?.scoped ?? []
|
|
915
964
|
const scopedUsedPercents = scoped.map((window) => window.usedPercent)
|
|
916
965
|
if (
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
966
|
+
!quota ||
|
|
967
|
+
(fiveHourUsedPercent == null &&
|
|
968
|
+
sevenDayUsedPercent == null &&
|
|
969
|
+
scoped.length === 0 &&
|
|
970
|
+
!quota.extraUsage)
|
|
920
971
|
) {
|
|
921
972
|
return {
|
|
922
973
|
fiveHourUsedPercent,
|
|
923
974
|
sevenDayUsedPercent,
|
|
924
975
|
scopedUsedPercents,
|
|
925
976
|
text: null,
|
|
977
|
+
stale: false,
|
|
926
978
|
}
|
|
927
979
|
}
|
|
928
980
|
|
|
929
|
-
const
|
|
930
|
-
(window) =>
|
|
931
|
-
`${formatScopedQuotaLabel(window.title)}: ${Math.round(window.usedPercent)}%`,
|
|
932
|
-
)
|
|
933
|
-
const primarySegments =
|
|
934
|
-
fiveHourUsedPercent == null && sevenDayUsedPercent == null
|
|
935
|
-
? []
|
|
936
|
-
: [
|
|
937
|
-
`5h: ${fiveHourUsedPercent == null ? '—' : `${Math.round(fiveHourUsedPercent)}%`}`,
|
|
938
|
-
`7d: ${sevenDayUsedPercent == null ? '—' : `${Math.round(sevenDayUsedPercent)}%`}`,
|
|
939
|
-
]
|
|
940
|
-
|
|
981
|
+
const projection = quotaDisplayProjection(quota, now)
|
|
941
982
|
return {
|
|
942
983
|
fiveHourUsedPercent,
|
|
943
984
|
sevenDayUsedPercent,
|
|
944
985
|
scopedUsedPercents,
|
|
945
|
-
|
|
986
|
+
// The narrow form leaves out the reading's age to stay on one line, so
|
|
987
|
+
// a stale reading is dimmed instead of labelled.
|
|
988
|
+
text: formatQuota(projection, { now, form: 'narrow' }),
|
|
989
|
+
stale: isQuotaStale(projection, { now }),
|
|
946
990
|
}
|
|
947
991
|
}
|
|
948
992
|
|
|
993
|
+
export function getCollapsedAccounts(
|
|
994
|
+
state: SidebarState,
|
|
995
|
+
showFallbacks = true,
|
|
996
|
+
) {
|
|
997
|
+
const activeId = resolveActiveAccount(state).id
|
|
998
|
+
return [
|
|
999
|
+
{
|
|
1000
|
+
id: 'main',
|
|
1001
|
+
name: 'main',
|
|
1002
|
+
quota: state.main?.quota ?? null,
|
|
1003
|
+
quotaDated: state.main?.quotaDated,
|
|
1004
|
+
active: activeId === 'main',
|
|
1005
|
+
},
|
|
1006
|
+
...(showFallbacks
|
|
1007
|
+
? (state.fallbacks ?? [])
|
|
1008
|
+
.filter(
|
|
1009
|
+
(account) =>
|
|
1010
|
+
account.enabled ||
|
|
1011
|
+
(account.custodyState && account.custodyState !== 'off'),
|
|
1012
|
+
)
|
|
1013
|
+
.map((account) => ({
|
|
1014
|
+
id: account.id,
|
|
1015
|
+
name: account.label ?? account.id,
|
|
1016
|
+
quota: account.quota,
|
|
1017
|
+
quotaDated: account.quotaDated,
|
|
1018
|
+
active: activeId === account.id,
|
|
1019
|
+
}))
|
|
1020
|
+
: []),
|
|
1021
|
+
]
|
|
1022
|
+
}
|
|
1023
|
+
|
|
949
1024
|
export const FIVE_HOUR_MS = 5 * 60 * 60 * 1000
|
|
950
1025
|
export const SEVEN_DAY_MS = 7 * 24 * 60 * 60 * 1000
|
|
951
1026
|
|
|
@@ -2,6 +2,7 @@ export interface QuotaWindow {
|
|
|
2
2
|
usedPercent: number
|
|
3
3
|
remainingPercent: number
|
|
4
4
|
resetsAt?: string
|
|
5
|
+
checkedAt?: number
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export interface ScopedQuotaWindow extends QuotaWindow {
|
|
@@ -12,6 +13,7 @@ export interface ScopedQuotaWindow extends QuotaWindow {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface AccountQuota {
|
|
16
|
+
checkedAt?: number
|
|
15
17
|
five_hour?: QuotaWindow
|
|
16
18
|
seven_day?: QuotaWindow
|
|
17
19
|
scoped?: ScopedQuotaWindow[]
|
|
@@ -31,10 +33,12 @@ export interface SidebarAccountState {
|
|
|
31
33
|
label: string | undefined
|
|
32
34
|
quota: AccountQuota | null
|
|
33
35
|
enabled: boolean
|
|
36
|
+
stateLine?: string
|
|
37
|
+
quotaDated?: boolean
|
|
34
38
|
// True when the account's refresh token is permanently dead (400
|
|
35
39
|
// invalid_grant) and it needs a re-login — distinct from a transient backoff.
|
|
36
40
|
needsReauth: boolean
|
|
37
|
-
// True when the vault
|
|
41
|
+
// True when the vault credential needs a new sign-in.
|
|
38
42
|
vaultReauth?: boolean
|
|
39
43
|
// A gate alone does not prove the sidecar can serve a request.
|
|
40
44
|
vaultServed?: boolean
|
|
@@ -77,7 +81,10 @@ export interface PrimeSidebarAccountState {
|
|
|
77
81
|
export interface SidebarState {
|
|
78
82
|
main: {
|
|
79
83
|
quota: AccountQuota | null
|
|
84
|
+
enabled?: boolean
|
|
80
85
|
tierLabel?: string
|
|
86
|
+
stateLine?: string
|
|
87
|
+
quotaDated?: boolean
|
|
81
88
|
quotaBackedOff?: boolean
|
|
82
89
|
quotaBackoffUntil?: number
|
|
83
90
|
refreshBackedOff?: boolean
|
|
@@ -120,7 +127,12 @@ import {
|
|
|
120
127
|
import { tmpdir } from 'node:os'
|
|
121
128
|
import { dirname, join } from 'node:path'
|
|
122
129
|
import type { PrimeUsageCounters } from '@cortexkit/anthropic-auth-core'
|
|
123
|
-
import {
|
|
130
|
+
import {
|
|
131
|
+
formatQuota,
|
|
132
|
+
isQuotaStale,
|
|
133
|
+
logger,
|
|
134
|
+
quotaDisplayProjection,
|
|
135
|
+
} from '@cortexkit/anthropic-auth-core'
|
|
124
136
|
|
|
125
137
|
const STATE_FILE_ENV = 'OPENCODE_ANTHROPIC_AUTH_SIDEBAR_STATE_FILE'
|
|
126
138
|
const DEFAULT_STATE_DIR = join(tmpdir(), 'opencode-anthropic-auth')
|
|
@@ -229,12 +241,14 @@ function normalizeQuotaWindow(value: unknown): QuotaWindow | undefined {
|
|
|
229
241
|
usedPercent,
|
|
230
242
|
remainingPercent,
|
|
231
243
|
resetsAt: typeof value.resetsAt === 'string' ? value.resetsAt : undefined,
|
|
244
|
+
...(isFiniteNumber(value.checkedAt) && { checkedAt: value.checkedAt }),
|
|
232
245
|
}
|
|
233
246
|
}
|
|
234
247
|
|
|
235
248
|
function normalizeAccountQuota(value: unknown): AccountQuota | null {
|
|
236
249
|
if (!isRecord(value)) return null
|
|
237
250
|
const quota: AccountQuota = {}
|
|
251
|
+
if (isFiniteNumber(value.checkedAt)) quota.checkedAt = value.checkedAt
|
|
238
252
|
const fiveHour = normalizeQuotaWindow(value.five_hour)
|
|
239
253
|
const sevenDay = normalizeQuotaWindow(value.seven_day)
|
|
240
254
|
if (fiveHour) quota.five_hour = fiveHour
|
|
@@ -327,6 +341,9 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
327
341
|
if (isRecord(raw.main)) {
|
|
328
342
|
const m = raw.main
|
|
329
343
|
main.quota = normalizeAccountQuota(m.quota)
|
|
344
|
+
if (typeof m.enabled === 'boolean') main.enabled = m.enabled
|
|
345
|
+
if (typeof m.stateLine === 'string') main.stateLine = m.stateLine
|
|
346
|
+
if (m.quotaDated === true) main.quotaDated = true
|
|
330
347
|
if (typeof m.tierLabel === 'string' && m.tierLabel.trim()) {
|
|
331
348
|
main.tierLabel = m.tierLabel.trim()
|
|
332
349
|
}
|
|
@@ -349,6 +366,10 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
349
366
|
label: typeof entry.label === 'string' ? entry.label : undefined,
|
|
350
367
|
quota: normalizeAccountQuota(entry.quota),
|
|
351
368
|
enabled: typeof entry.enabled === 'boolean' ? entry.enabled : false,
|
|
369
|
+
...(typeof entry.stateLine === 'string' && {
|
|
370
|
+
stateLine: entry.stateLine,
|
|
371
|
+
}),
|
|
372
|
+
...(entry.quotaDated === true && { quotaDated: true }),
|
|
352
373
|
needsReauth:
|
|
353
374
|
typeof entry.needsReauth === 'boolean' ? entry.needsReauth : false,
|
|
354
375
|
...(entry.vaultReauth === true && { vaultReauth: true }),
|
|
@@ -635,7 +656,9 @@ async function writeSidebarStateAtomic(
|
|
|
635
656
|
return 'lock-lost-before-rename'
|
|
636
657
|
}
|
|
637
658
|
await rename(tempFile, stateFile)
|
|
638
|
-
|
|
659
|
+
// Look the hooks up again: a test may start listening while this write is
|
|
660
|
+
// in flight, after reading the old file, and must still hear this rename.
|
|
661
|
+
await sidebarStateWriteHooksFor(stateFile)?.afterRename?.(stateFile)
|
|
639
662
|
if (!(await ownsLock())) return 'lock-lost-after-rename'
|
|
640
663
|
return 'written'
|
|
641
664
|
} catch (error) {
|
|
@@ -792,13 +815,13 @@ export function resolveActiveAccount(state: SidebarState): {
|
|
|
792
815
|
id: string
|
|
793
816
|
name: string
|
|
794
817
|
quota: AccountQuota | null
|
|
818
|
+
stateLine?: string
|
|
819
|
+
quotaDated?: boolean
|
|
795
820
|
} {
|
|
796
821
|
const activeId = state.activeId
|
|
797
822
|
if (activeId && activeId !== 'main') {
|
|
798
|
-
//
|
|
799
|
-
// accounts
|
|
800
|
-
// enabled. Kept so this pure helper stays correct for any caller and is
|
|
801
|
-
// exercised directly by the unit tests.
|
|
823
|
+
// A disabled fallback cannot remain the active route, even though vault
|
|
824
|
+
// accounts stay visible in the expanded inventory while turned off.
|
|
802
825
|
const fallback = state.fallbacks?.find(
|
|
803
826
|
(account) => account.enabled && account.id === activeId,
|
|
804
827
|
)
|
|
@@ -807,15 +830,22 @@ export function resolveActiveAccount(state: SidebarState): {
|
|
|
807
830
|
id: fallback.id,
|
|
808
831
|
name: fallback.label ?? fallback.id,
|
|
809
832
|
quota: fallback.quota,
|
|
833
|
+
...(fallback.stateLine && {
|
|
834
|
+
stateLine: fallback.stateLine,
|
|
835
|
+
quotaDated: fallback.quotaDated,
|
|
836
|
+
}),
|
|
810
837
|
}
|
|
811
838
|
}
|
|
812
839
|
}
|
|
813
|
-
return {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
840
|
+
return {
|
|
841
|
+
id: 'main',
|
|
842
|
+
name: 'main',
|
|
843
|
+
quota: state.main?.quota ?? null,
|
|
844
|
+
...(state.main?.stateLine && {
|
|
845
|
+
stateLine: state.main.stateLine,
|
|
846
|
+
quotaDated: state.main.quotaDated,
|
|
847
|
+
}),
|
|
848
|
+
}
|
|
819
849
|
}
|
|
820
850
|
|
|
821
851
|
/**
|
|
@@ -903,49 +933,94 @@ export function getFableRecoverySummary(
|
|
|
903
933
|
return `Opus 4.8 · ${recovery.remaining} left`
|
|
904
934
|
}
|
|
905
935
|
|
|
906
|
-
|
|
936
|
+
/** Keep full shared names when they fit; truncate only to preserve row columns. */
|
|
937
|
+
export function formatQuotaRowLabel(name: string, width = 10): string {
|
|
938
|
+
if (width <= 0) return ''
|
|
939
|
+
let label = name
|
|
940
|
+
if (Bun.stringWidth(label) > width) {
|
|
941
|
+
label = ''
|
|
942
|
+
for (const character of name) {
|
|
943
|
+
if (Bun.stringWidth(label + character) > width - 1) break
|
|
944
|
+
label += character
|
|
945
|
+
}
|
|
946
|
+
label += '…'
|
|
947
|
+
}
|
|
948
|
+
return label + ' '.repeat(Math.max(0, width - Bun.stringWidth(label)))
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
export function getCollapsedQuotaSummary(
|
|
952
|
+
quota: AccountQuota | null,
|
|
953
|
+
now = Date.now(),
|
|
954
|
+
): {
|
|
907
955
|
fiveHourUsedPercent: number | null
|
|
908
956
|
sevenDayUsedPercent: number | null
|
|
909
957
|
scopedUsedPercents: number[]
|
|
910
958
|
text: string | null
|
|
959
|
+
stale: boolean
|
|
911
960
|
} {
|
|
912
961
|
const fiveHourUsedPercent = quota?.five_hour?.usedPercent ?? null
|
|
913
962
|
const sevenDayUsedPercent = quota?.seven_day?.usedPercent ?? null
|
|
914
963
|
const scoped = quota?.scoped ?? []
|
|
915
964
|
const scopedUsedPercents = scoped.map((window) => window.usedPercent)
|
|
916
965
|
if (
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
966
|
+
!quota ||
|
|
967
|
+
(fiveHourUsedPercent == null &&
|
|
968
|
+
sevenDayUsedPercent == null &&
|
|
969
|
+
scoped.length === 0 &&
|
|
970
|
+
!quota.extraUsage)
|
|
920
971
|
) {
|
|
921
972
|
return {
|
|
922
973
|
fiveHourUsedPercent,
|
|
923
974
|
sevenDayUsedPercent,
|
|
924
975
|
scopedUsedPercents,
|
|
925
976
|
text: null,
|
|
977
|
+
stale: false,
|
|
926
978
|
}
|
|
927
979
|
}
|
|
928
980
|
|
|
929
|
-
const
|
|
930
|
-
(window) =>
|
|
931
|
-
`${formatScopedQuotaLabel(window.title)}: ${Math.round(window.usedPercent)}%`,
|
|
932
|
-
)
|
|
933
|
-
const primarySegments =
|
|
934
|
-
fiveHourUsedPercent == null && sevenDayUsedPercent == null
|
|
935
|
-
? []
|
|
936
|
-
: [
|
|
937
|
-
`5h: ${fiveHourUsedPercent == null ? '—' : `${Math.round(fiveHourUsedPercent)}%`}`,
|
|
938
|
-
`7d: ${sevenDayUsedPercent == null ? '—' : `${Math.round(sevenDayUsedPercent)}%`}`,
|
|
939
|
-
]
|
|
940
|
-
|
|
981
|
+
const projection = quotaDisplayProjection(quota, now)
|
|
941
982
|
return {
|
|
942
983
|
fiveHourUsedPercent,
|
|
943
984
|
sevenDayUsedPercent,
|
|
944
985
|
scopedUsedPercents,
|
|
945
|
-
|
|
986
|
+
// The narrow form leaves out the reading's age to stay on one line, so
|
|
987
|
+
// a stale reading is dimmed instead of labelled.
|
|
988
|
+
text: formatQuota(projection, { now, form: 'narrow' }),
|
|
989
|
+
stale: isQuotaStale(projection, { now }),
|
|
946
990
|
}
|
|
947
991
|
}
|
|
948
992
|
|
|
993
|
+
export function getCollapsedAccounts(
|
|
994
|
+
state: SidebarState,
|
|
995
|
+
showFallbacks = true,
|
|
996
|
+
) {
|
|
997
|
+
const activeId = resolveActiveAccount(state).id
|
|
998
|
+
return [
|
|
999
|
+
{
|
|
1000
|
+
id: 'main',
|
|
1001
|
+
name: 'main',
|
|
1002
|
+
quota: state.main?.quota ?? null,
|
|
1003
|
+
quotaDated: state.main?.quotaDated,
|
|
1004
|
+
active: activeId === 'main',
|
|
1005
|
+
},
|
|
1006
|
+
...(showFallbacks
|
|
1007
|
+
? (state.fallbacks ?? [])
|
|
1008
|
+
.filter(
|
|
1009
|
+
(account) =>
|
|
1010
|
+
account.enabled ||
|
|
1011
|
+
(account.custodyState && account.custodyState !== 'off'),
|
|
1012
|
+
)
|
|
1013
|
+
.map((account) => ({
|
|
1014
|
+
id: account.id,
|
|
1015
|
+
name: account.label ?? account.id,
|
|
1016
|
+
quota: account.quota,
|
|
1017
|
+
quotaDated: account.quotaDated,
|
|
1018
|
+
active: activeId === account.id,
|
|
1019
|
+
}))
|
|
1020
|
+
: []),
|
|
1021
|
+
]
|
|
1022
|
+
}
|
|
1023
|
+
|
|
949
1024
|
export const FIVE_HOUR_MS = 5 * 60 * 60 * 1000
|
|
950
1025
|
export const SEVEN_DAY_MS = 7 * 24 * 60 * 60 * 1000
|
|
951
1026
|
|