@cortexkit/opencode-anthropic-auth 1.12.2 → 1.14.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.
@@ -4,9 +4,17 @@ export interface QuotaWindow {
4
4
  resetsAt?: string
5
5
  }
6
6
 
7
+ export interface ScopedQuotaWindow extends QuotaWindow {
8
+ id: string
9
+ title: string
10
+ modelId?: string
11
+ modelName: string
12
+ }
13
+
7
14
  export interface AccountQuota {
8
15
  five_hour?: QuotaWindow
9
16
  seven_day?: QuotaWindow
17
+ scoped?: ScopedQuotaWindow[]
10
18
  }
11
19
 
12
20
  export interface SidebarAccountState {
@@ -65,13 +73,71 @@ function isRecord(v: unknown): v is Record<string, unknown> {
65
73
  return v !== null && typeof v === 'object' && !Array.isArray(v)
66
74
  }
67
75
 
76
+ function normalizeQuotaWindow(value: unknown): QuotaWindow | undefined {
77
+ if (!isRecord(value)) return undefined
78
+ const usedPercent = Number(value.usedPercent)
79
+ const remainingPercent = Number(value.remainingPercent)
80
+ if (!Number.isFinite(usedPercent) || !Number.isFinite(remainingPercent)) {
81
+ return undefined
82
+ }
83
+ return {
84
+ usedPercent,
85
+ remainingPercent,
86
+ resetsAt: typeof value.resetsAt === 'string' ? value.resetsAt : undefined,
87
+ }
88
+ }
89
+
90
+ function normalizeAccountQuota(value: unknown): AccountQuota | null {
91
+ if (!isRecord(value)) return null
92
+ const quota: AccountQuota = {}
93
+ const fiveHour = normalizeQuotaWindow(value.five_hour)
94
+ const sevenDay = normalizeQuotaWindow(value.seven_day)
95
+ if (fiveHour) quota.five_hour = fiveHour
96
+ if (sevenDay) quota.seven_day = sevenDay
97
+
98
+ if (Array.isArray(value.scoped)) {
99
+ const scoped = value.scoped
100
+ .map((entry): ScopedQuotaWindow | undefined => {
101
+ if (!isRecord(entry)) return undefined
102
+ const window = normalizeQuotaWindow(entry)
103
+ if (!window) return undefined
104
+ if (typeof entry.id !== 'string' || !entry.id.trim()) return undefined
105
+ if (typeof entry.title !== 'string' || !entry.title.trim()) {
106
+ return undefined
107
+ }
108
+ if (typeof entry.modelName !== 'string' || !entry.modelName.trim()) {
109
+ return undefined
110
+ }
111
+ const modelId =
112
+ typeof entry.modelId === 'string' && entry.modelId.trim()
113
+ ? entry.modelId.trim()
114
+ : undefined
115
+ return {
116
+ ...window,
117
+ id: entry.id.trim(),
118
+ title: entry.title.trim(),
119
+ ...(modelId && { modelId }),
120
+ modelName: entry.modelName.trim(),
121
+ }
122
+ })
123
+ .filter((entry): entry is ScopedQuotaWindow => entry != null)
124
+ // Preserve empty `[]` so a sidebar reader can distinguish "scoped owned
125
+ // by anthropic-auth, none visible" from "no quota data at all". The OUTER
126
+ // Array.isArray guard means pre-feature inputs without a `scoped` key are
127
+ // not affected — only inputs that already carried an array reach this line.
128
+ quota.scoped = scoped
129
+ }
130
+
131
+ return Object.keys(quota).length ? quota : null
132
+ }
133
+
68
134
  export function normalizeSidebarState(raw: unknown): SidebarState {
69
135
  if (!isRecord(raw)) return { ...DEFAULT_SIDEBAR_STATE }
70
136
 
71
137
  const main: SidebarState['main'] = { quota: null }
72
138
  if (isRecord(raw.main)) {
73
139
  const m = raw.main
74
- main.quota = isRecord(m.quota) ? (m.quota as AccountQuota) : null
140
+ main.quota = normalizeAccountQuota(m.quota)
75
141
  if (typeof m.quotaBackedOff === 'boolean')
76
142
  main.quotaBackedOff = m.quotaBackedOff
77
143
  if (typeof m.quotaBackoffUntil === 'number')
@@ -89,7 +155,7 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
89
155
  .map((entry) => ({
90
156
  id: entry.id as string,
91
157
  label: typeof entry.label === 'string' ? entry.label : undefined,
92
- quota: isRecord(entry.quota) ? (entry.quota as AccountQuota) : null,
158
+ quota: normalizeAccountQuota(entry.quota),
93
159
  enabled: typeof entry.enabled === 'boolean' ? entry.enabled : false,
94
160
  needsReauth:
95
161
  typeof entry.needsReauth === 'boolean' ? entry.needsReauth : false,
@@ -194,21 +260,51 @@ export function resolveActiveAccount(state: SidebarState): {
194
260
  return { id: 'main', name: 'main', quota: state.main?.quota ?? null }
195
261
  }
196
262
 
263
+ export function formatScopedQuotaLabel(title: string) {
264
+ const label = title.replace(/\s+only$/i, '').trim()
265
+ return /^fable$/i.test(label) ? 'Fa' : label
266
+ }
267
+
197
268
  export function getCollapsedQuotaSummary(quota: AccountQuota | null): {
198
269
  fiveHourUsedPercent: number | null
199
270
  sevenDayUsedPercent: number | null
271
+ scopedUsedPercents: number[]
200
272
  text: string | null
201
273
  } {
202
274
  const fiveHourUsedPercent = quota?.five_hour?.usedPercent ?? null
203
275
  const sevenDayUsedPercent = quota?.seven_day?.usedPercent ?? null
204
- if (fiveHourUsedPercent == null && sevenDayUsedPercent == null) {
205
- return { fiveHourUsedPercent, sevenDayUsedPercent, text: null }
276
+ const scoped = quota?.scoped ?? []
277
+ const scopedUsedPercents = scoped.map((window) => window.usedPercent)
278
+ if (
279
+ fiveHourUsedPercent == null &&
280
+ sevenDayUsedPercent == null &&
281
+ scoped.length === 0
282
+ ) {
283
+ return {
284
+ fiveHourUsedPercent,
285
+ sevenDayUsedPercent,
286
+ scopedUsedPercents,
287
+ text: null,
288
+ }
206
289
  }
207
290
 
291
+ const scopedSegments = scoped.map(
292
+ (window) =>
293
+ `${formatScopedQuotaLabel(window.title)}: ${Math.round(window.usedPercent)}%`,
294
+ )
295
+ const primarySegments =
296
+ fiveHourUsedPercent == null && sevenDayUsedPercent == null
297
+ ? []
298
+ : [
299
+ `5h: ${fiveHourUsedPercent == null ? '—' : `${Math.round(fiveHourUsedPercent)}%`}`,
300
+ `7d: ${sevenDayUsedPercent == null ? '—' : `${Math.round(sevenDayUsedPercent)}%`}`,
301
+ ]
302
+
208
303
  return {
209
304
  fiveHourUsedPercent,
210
305
  sevenDayUsedPercent,
211
- text: `5h: ${fiveHourUsedPercent == null ? '—' : `${Math.round(fiveHourUsedPercent)}%`} 7d: ${sevenDayUsedPercent == null ? '—' : `${Math.round(sevenDayUsedPercent)}%`}`,
306
+ scopedUsedPercents,
307
+ text: [...primarySegments, ...scopedSegments].join(' '),
212
308
  }
213
309
  }
214
310
 
@@ -7,6 +7,31 @@ type ApplyFn = (
7
7
  args: string,
8
8
  ) => Promise<{ text: string; knobs: Record<string, unknown> }>
9
9
 
10
+ type KillswitchDialogConfig = {
11
+ enabled?: boolean
12
+ main?: Record<string, number>
13
+ accounts?: Record<string, Record<string, number>>
14
+ }
15
+
16
+ export function buildKillswitchThresholdSeed(
17
+ config: KillswitchDialogConfig,
18
+ accountIds: string[],
19
+ ) {
20
+ const readT = (t: Record<string, number> | undefined) => {
21
+ const fh = t?.five_hour ?? t?.['5h'] ?? 5
22
+ const sd = t?.seven_day ?? t?.['1w'] ?? 10
23
+ const scoped = t?.scoped ?? 0
24
+ return { fh, sd, scoped }
25
+ }
26
+ const mainT = readT(config.main)
27
+ const seedParts = [`main:${mainT.fh},${mainT.sd},${mainT.scoped}`]
28
+ for (const id of accountIds) {
29
+ const t = readT(config.accounts?.[id] ?? config.main)
30
+ seedParts.push(`${id}:${t.fh},${t.sd},${t.scoped}`)
31
+ }
32
+ return seedParts.join(' ')
33
+ }
34
+
10
35
  function showText(api: TuiPluginApi, text: string) {
11
36
  api.ui.dialog.setSize('xlarge')
12
37
  api.ui.dialog.replace(() => (
@@ -150,25 +175,10 @@ export function openCommandDialog(
150
175
  }
151
176
 
152
177
  if (payload.command === 'claude-killswitch') {
153
- const config = (payload.knobs.config ?? {}) as {
154
- enabled?: boolean
155
- main?: Record<string, number>
156
- accounts?: Record<string, Record<string, number>>
157
- }
178
+ const config = (payload.knobs.config ?? {}) as KillswitchDialogConfig
158
179
  const accountIds = (payload.knobs.accountIds as string[]) ?? []
159
180
  const enabled = config.enabled === true
160
- const readT = (t: Record<string, number> | undefined) => {
161
- const fh = t?.five_hour ?? t?.['5h'] ?? 5
162
- const sd = t?.seven_day ?? t?.['1w'] ?? 10
163
- return { fh, sd }
164
- }
165
- const mainT = readT(config.main)
166
- const seedParts = [`main:${mainT.fh},${mainT.sd}`]
167
- for (const id of accountIds) {
168
- const t = readT(config.accounts?.[id] ?? config.main)
169
- seedParts.push(`${id}:${t.fh},${t.sd}`)
170
- }
171
- const seed = seedParts.join(' ')
181
+ const seed = buildKillswitchThresholdSeed(config, accountIds)
172
182
 
173
183
  const openEdit = () => {
174
184
  const DialogPrompt = api.ui.DialogPrompt
@@ -177,7 +187,7 @@ export function openCommandDialog(
177
187
  <DialogPrompt
178
188
  title='Killswitch thresholds'
179
189
  description={() => <text>{payload.text}</text>}
180
- placeholder='main:5,10 work-alt:5,10'
190
+ placeholder='main:5,10,0 work-alt:5,10,0'
181
191
  value={seed}
182
192
  onConfirm={(value: string) => {
183
193
  void apply('claude-killswitch', `set ${value.trim()}`).then((r) => {
@@ -206,7 +216,7 @@ export function openCommandDialog(
206
216
  {
207
217
  title: 'Edit thresholds…',
208
218
  value: 'edit',
209
- description: 'Set per-account 5h,1w cutoffs',
219
+ description: 'Set per-account 5h,1w,scoped cutoffs',
210
220
  },
211
221
  ]}
212
222
  onSelect={(option) => {
package/src/tui.tsx CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  computeQuotaPacing,
24
24
  DEFAULT_SIDEBAR_STATE,
25
25
  FIVE_HOUR_MS,
26
+ formatScopedQuotaLabel,
26
27
  getCollapsedQuotaSummary,
27
28
  getSidebarState,
28
29
  type QuotaPacing,
@@ -348,6 +349,17 @@ function AccountBlock(props: {
348
349
  window={props.quota?.seven_day}
349
350
  pacing={pacingFor(props.quota?.seven_day, SEVEN_DAY_MS)}
350
351
  />
352
+ <For each={props.quota?.scoped ?? []}>
353
+ {(window) => (
354
+ <QuotaRow
355
+ theme={props.theme}
356
+ appearance={props.appearance}
357
+ label={formatScopedQuotaLabel(window.title)}
358
+ window={window}
359
+ pacing={pacingFor(window, SEVEN_DAY_MS)}
360
+ />
361
+ )}
362
+ </For>
351
363
  </Show>
352
364
  </box>
353
365
  )
@@ -548,9 +560,21 @@ function QuotaSidebar(props: {
548
560
  const quota = activeAccount().quota
549
561
  if (!quota) return false
550
562
  const now = Date.now()
551
- const windows: Array<[typeof quota.five_hour, number]> = [
563
+ const windows: Array<
564
+ [
565
+ (
566
+ | { usedPercent: number; remainingPercent: number; resetsAt?: string }
567
+ | undefined
568
+ ),
569
+ number,
570
+ ]
571
+ > = [
552
572
  [quota.five_hour, FIVE_HOUR_MS],
553
573
  [quota.seven_day, SEVEN_DAY_MS],
574
+ ...(quota.scoped ?? []).map((window): [typeof window, number] => [
575
+ window,
576
+ SEVEN_DAY_MS,
577
+ ]),
554
578
  ]
555
579
  return windows.some(
556
580
  ([w, ms]) =>
@@ -562,6 +586,7 @@ function QuotaSidebar(props: {
562
586
  const values = [
563
587
  summary.fiveHourUsedPercent,
564
588
  summary.sevenDayUsedPercent,
589
+ ...summary.scopedUsedPercents,
565
590
  ].filter((value): value is number => value != null)
566
591
  // Pacing deficit is an advisory projection, not actual quota exhaustion,
567
592
  // so it can only BUMP the usage tone up to warn at most — never soften a