@cortexkit/opencode-anthropic-auth 1.12.1 → 1.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-anthropic-auth",
3
- "version": "1.12.1",
3
+ "version": "1.13.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -58,7 +58,7 @@
58
58
  "@opencode-ai/plugin": "*"
59
59
  },
60
60
  "dependencies": {
61
- "@cortexkit/anthropic-auth-core": "1.12.1",
61
+ "@cortexkit/anthropic-auth-core": "1.13.0",
62
62
  "@opentui/core": ">=0.4.2",
63
63
  "@opentui/solid": ">=0.4.2",
64
64
  "jsonc-parser": "^3.3.1",
@@ -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,67 @@ 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
+ if (scoped.length) quota.scoped = scoped
125
+ }
126
+
127
+ return Object.keys(quota).length ? quota : null
128
+ }
129
+
68
130
  export function normalizeSidebarState(raw: unknown): SidebarState {
69
131
  if (!isRecord(raw)) return { ...DEFAULT_SIDEBAR_STATE }
70
132
 
71
133
  const main: SidebarState['main'] = { quota: null }
72
134
  if (isRecord(raw.main)) {
73
135
  const m = raw.main
74
- main.quota = isRecord(m.quota) ? (m.quota as AccountQuota) : null
136
+ main.quota = normalizeAccountQuota(m.quota)
75
137
  if (typeof m.quotaBackedOff === 'boolean')
76
138
  main.quotaBackedOff = m.quotaBackedOff
77
139
  if (typeof m.quotaBackoffUntil === 'number')
@@ -89,7 +151,7 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
89
151
  .map((entry) => ({
90
152
  id: entry.id as string,
91
153
  label: typeof entry.label === 'string' ? entry.label : undefined,
92
- quota: isRecord(entry.quota) ? (entry.quota as AccountQuota) : null,
154
+ quota: normalizeAccountQuota(entry.quota),
93
155
  enabled: typeof entry.enabled === 'boolean' ? entry.enabled : false,
94
156
  needsReauth:
95
157
  typeof entry.needsReauth === 'boolean' ? entry.needsReauth : false,
@@ -194,21 +256,46 @@ export function resolveActiveAccount(state: SidebarState): {
194
256
  return { id: 'main', name: 'main', quota: state.main?.quota ?? null }
195
257
  }
196
258
 
259
+ export function formatScopedQuotaLabel(title: string) {
260
+ const label = title.replace(/\s+only$/i, '').trim()
261
+ return /^fable$/i.test(label) ? 'Fa' : label
262
+ }
263
+
197
264
  export function getCollapsedQuotaSummary(quota: AccountQuota | null): {
198
265
  fiveHourUsedPercent: number | null
199
266
  sevenDayUsedPercent: number | null
267
+ scopedUsedPercents: number[]
200
268
  text: string | null
201
269
  } {
202
270
  const fiveHourUsedPercent = quota?.five_hour?.usedPercent ?? null
203
271
  const sevenDayUsedPercent = quota?.seven_day?.usedPercent ?? null
204
- if (fiveHourUsedPercent == null && sevenDayUsedPercent == null) {
205
- return { fiveHourUsedPercent, sevenDayUsedPercent, text: null }
272
+ const scoped = quota?.scoped ?? []
273
+ const scopedUsedPercents = scoped.map((window) => window.usedPercent)
274
+ if (
275
+ fiveHourUsedPercent == null &&
276
+ sevenDayUsedPercent == null &&
277
+ scoped.length === 0
278
+ ) {
279
+ return {
280
+ fiveHourUsedPercent,
281
+ sevenDayUsedPercent,
282
+ scopedUsedPercents,
283
+ text: null,
284
+ }
206
285
  }
207
286
 
208
287
  return {
209
288
  fiveHourUsedPercent,
210
289
  sevenDayUsedPercent,
211
- text: `5h: ${fiveHourUsedPercent == null ? '—' : `${Math.round(fiveHourUsedPercent)}%`} 7d: ${sevenDayUsedPercent == null ? '—' : `${Math.round(sevenDayUsedPercent)}%`}`,
290
+ scopedUsedPercents,
291
+ text: [
292
+ `5h: ${fiveHourUsedPercent == null ? '—' : `${Math.round(fiveHourUsedPercent)}%`}`,
293
+ `7d: ${sevenDayUsedPercent == null ? '—' : `${Math.round(sevenDayUsedPercent)}%`}`,
294
+ ...scoped.map(
295
+ (window) =>
296
+ `${formatScopedQuotaLabel(window.title)}: ${Math.round(window.usedPercent)}%`,
297
+ ),
298
+ ].join(' '),
212
299
  }
213
300
  }
214
301
 
@@ -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