@leo000001/opencode-quota-sidebar 4.1.0 → 4.1.2

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/dist/cli.d.ts CHANGED
@@ -12,6 +12,12 @@ type CliServerCommand = {
12
12
  shell?: boolean;
13
13
  };
14
14
  type SpawnedCliServerProcess = ReturnType<typeof spawn>;
15
+ type CliPortResolver = (preferredPort: number, attemptedPorts: ReadonlySet<number>) => Promise<number>;
16
+ type StartedCliServer = {
17
+ url: string;
18
+ pid?: number;
19
+ close: () => void;
20
+ };
15
21
  export declare function releaseCliServerProcess(proc: {
16
22
  stdin?: {
17
23
  destroy: () => void;
@@ -45,12 +51,24 @@ export declare function terminateCliServerProcess(proc: {
45
51
  export declare function extractCliServerUrl(output: string): string | undefined;
46
52
  export declare function parseCliArgs(argv: string[]): CliCommand;
47
53
  export declare function cliBaseUrl(): string;
48
- export declare function cliServerCommandCandidates(platform?: NodeJS.Platform): CliServerCommand[];
54
+ export declare function cliServerCommandCandidates(platform?: NodeJS.Platform, port?: number): CliServerCommand[];
55
+ export declare function isCliServerPortConflictFailure(failure: unknown): boolean;
56
+ export declare function registerCliShutdownCleanup(cleanup: () => void, options?: {
57
+ targetProcess?: NodeJS.Process;
58
+ killProcess?: typeof process.kill;
59
+ }): () => void;
60
+ export declare function reserveCliServerPort(preferredPort?: number, attemptedPorts?: ReadonlySet<number>): Promise<number>;
49
61
  export declare function closeCliServerProcess(proc: SpawnedCliServerProcess, platform?: NodeJS.Platform, killProcess?: typeof process.kill, spawnProcess?: typeof spawn): void;
50
- export declare function tryStartCliOpencodeServer(candidate: CliServerCommand, spawnProcess?: typeof spawn, closeProcess?: typeof closeCliServerProcess): Promise<{
51
- url: string;
52
- close: () => void;
53
- }>;
62
+ export declare function tryStartCliOpencodeServer(candidate: CliServerCommand, spawnProcess?: typeof spawn, closeProcess?: typeof closeCliServerProcess): Promise<StartedCliServer>;
63
+ export declare function spawnCliServerWatchdog(targetPid: number | undefined, ttlMs?: number, spawnProcess?: typeof spawn, nodeExecPath?: string): import("child_process").ChildProcess | undefined;
64
+ export declare function startCliOpencodeServer(options?: {
65
+ platform?: NodeJS.Platform;
66
+ spawnProcess?: typeof spawn;
67
+ spawnWatchdogProcess?: typeof spawn;
68
+ closeProcess?: typeof closeCliServerProcess;
69
+ reservePort?: CliPortResolver;
70
+ tempServerTtlMs?: number;
71
+ }): Promise<StartedCliServer>;
54
72
  export declare function runCli(argv: string[]): Promise<string>;
55
73
  export declare function cliExitCodeForError(message: string): 0 | 1;
56
74
  export declare function cliShouldRunMain(argv1?: string, modulePath?: string, resolvePath?: (filePath: string) => string): boolean;
package/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { realpathSync } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { spawn } from 'node:child_process';
5
+ import { createServer } from 'node:net';
5
6
  import { fileURLToPath } from 'node:url';
6
7
  import { createOpencodeClient } from '@opencode-ai/sdk/client';
7
8
  import { cliCurrentLabel, renderCliDashboard, renderCliHistoryDashboard, } from './cli_render.js';
@@ -11,7 +12,7 @@ import { sinceFromLast } from './period.js';
11
12
  import { authFilePath, loadConfig, loadState,
12
13
  // reused for CLI-local config layering
13
14
  quotaConfigPaths, resolveOpencodeDataDir, stateFilePath, } from './storage.js';
14
- import { filterHistoryProvidersForDisplay, filterUsageProvidersForDisplay, listCurrentProviderIDs, } from './provider_catalog.js';
15
+ import { filterHistoryProvidersForDisplay, filterUsageProvidersForDisplay, listConnectedProviderIDs, listCurrentProviderIDs, } from './provider_catalog.js';
15
16
  import { createUsageService } from './usage_service.js';
16
17
  function emptyProviderUsage(usage) {
17
18
  return {
@@ -37,9 +38,20 @@ function strictFilterHistoryProviders(history, allowedProviderIDs) {
37
38
  }
38
39
  return filterHistoryProvidersForDisplay(history, allowedProviderIDs);
39
40
  }
41
+ function cliQuotaProviderIDs(usageProviderIDs, connectedProviderIDs) {
42
+ return [...new Set([...usageProviderIDs, ...connectedProviderIDs])];
43
+ }
40
44
  const DEFAULT_OPENCODE_BASE_URL = 'http://localhost:4096';
41
- const CLI_SERVER_TIMEOUT_MS = 10_000;
45
+ const DEFAULT_OPENCODE_HOST = '127.0.0.1';
46
+ const DEFAULT_OPENCODE_PORT = 4096;
47
+ const CLI_SERVER_TIMEOUT_MS = 60_000;
42
48
  const CLI_FORCE_EXIT_DELAY_MS = 100;
49
+ const CLI_SERVER_PORT_RETRY_LIMIT = 5;
50
+ const CLI_PORT_RESERVE_RETRY_LIMIT = 16;
51
+ const CLI_TEMP_SERVER_TTL_MS = 10 * 60_000;
52
+ const CLI_TEMP_SERVER_HINT = 'CLI note: if the temporary OpenCode server keeps failing, run `opencode serve` manually to inspect logs, or set `OPENCODE_BASE_URL`.';
53
+ const CLI_SHUTDOWN_HOOK_KEY = Symbol('quota-sidebar.cliShutdownHook');
54
+ const CLI_SHUTDOWN_CALLBACKS_KEY = Symbol('quota-sidebar.cliShutdownCallbacks');
43
55
  export function releaseCliServerProcess(proc) {
44
56
  // The CLI only needs the child pipes until the server prints its listen URL.
45
57
  // After that, unref/destroy them so the parent process can exit cleanly.
@@ -174,24 +186,158 @@ export function cliBaseUrl() {
174
186
  function isDefaultBaseUrl() {
175
187
  return !process.env.OPENCODE_BASE_URL?.trim();
176
188
  }
177
- export function cliServerCommandCandidates(platform = process.platform) {
178
- const directArgs = ['serve', '--hostname=127.0.0.1', '--port=4096'];
189
+ export function cliServerCommandCandidates(platform = process.platform, port = DEFAULT_OPENCODE_PORT) {
190
+ const directArgs = [
191
+ 'serve',
192
+ `--hostname=${DEFAULT_OPENCODE_HOST}`,
193
+ `--port=${port}`,
194
+ ];
195
+ const directCommand = `opencode serve --hostname=${DEFAULT_OPENCODE_HOST} --port=${port}`;
179
196
  if (platform === 'win32') {
180
197
  return [
181
198
  { command: 'opencode.cmd', args: directArgs },
182
199
  {
183
- command: 'opencode serve --hostname=127.0.0.1 --port=4096',
200
+ command: directCommand,
184
201
  args: [],
185
202
  shell: true,
186
203
  },
187
204
  {
188
205
  command: 'bash',
189
- args: ['-lc', 'opencode serve --hostname=127.0.0.1 --port=4096'],
206
+ args: ['-lc', directCommand],
190
207
  },
191
208
  ];
192
209
  }
193
210
  return [{ command: 'opencode', args: directArgs }];
194
211
  }
212
+ function cliServerFailureOutput(failure) {
213
+ if (typeof failure !== 'object' ||
214
+ failure === null ||
215
+ !('output' in failure)) {
216
+ return '';
217
+ }
218
+ const output = failure.output;
219
+ return typeof output === 'string' ? output : '';
220
+ }
221
+ function cliServerFailureError(failure) {
222
+ if (typeof failure === 'object' && failure !== null && 'error' in failure) {
223
+ const error = failure.error;
224
+ return error instanceof Error ? error : new Error(String(error));
225
+ }
226
+ return failure instanceof Error ? failure : new Error(String(failure));
227
+ }
228
+ function isRecoverableCliServerFailure(failure) {
229
+ return (typeof failure === 'object' &&
230
+ failure !== null &&
231
+ 'recoverable' in failure &&
232
+ failure.recoverable === true);
233
+ }
234
+ export function isCliServerPortConflictFailure(failure) {
235
+ const text = [
236
+ cliServerFailureError(failure).message,
237
+ cliServerFailureOutput(failure),
238
+ ]
239
+ .filter(Boolean)
240
+ .join('\n');
241
+ return /eaddrinuse|address already in use|port .*already in use|listen .*in use/i.test(text);
242
+ }
243
+ function describeCliError(error) {
244
+ return error instanceof Error ? error.message : String(error);
245
+ }
246
+ function formatCliTempServerFailure(prefix, error) {
247
+ return `${prefix}: ${describeCliError(error)}\n\n${CLI_TEMP_SERVER_HINT}`;
248
+ }
249
+ function runCliShutdownCallbacks(targetProcess) {
250
+ const callbacks = targetProcess[CLI_SHUTDOWN_CALLBACKS_KEY];
251
+ if (!callbacks)
252
+ return;
253
+ for (const callback of Array.from(callbacks)) {
254
+ try {
255
+ callback();
256
+ }
257
+ catch {
258
+ // Best-effort cleanup for CLI temp servers.
259
+ }
260
+ }
261
+ }
262
+ export function registerCliShutdownCleanup(cleanup, options) {
263
+ const targetProcess = options?.targetProcess ||
264
+ process;
265
+ const callbacks = targetProcess[CLI_SHUTDOWN_CALLBACKS_KEY] ||
266
+ (targetProcess[CLI_SHUTDOWN_CALLBACKS_KEY] = new Set());
267
+ callbacks.add(cleanup);
268
+ if (!targetProcess[CLI_SHUTDOWN_HOOK_KEY]) {
269
+ targetProcess[CLI_SHUTDOWN_HOOK_KEY] = true;
270
+ targetProcess.once('exit', () => {
271
+ runCliShutdownCallbacks(targetProcess);
272
+ });
273
+ targetProcess.once('uncaughtExceptionMonitor', () => {
274
+ runCliShutdownCallbacks(targetProcess);
275
+ });
276
+ for (const signal of ['SIGINT', 'SIGTERM']) {
277
+ targetProcess.once(signal, () => {
278
+ runCliShutdownCallbacks(targetProcess);
279
+ if (typeof targetProcess.pid === 'number' && targetProcess.pid > 0) {
280
+ ;
281
+ (options?.killProcess ?? process.kill)(targetProcess.pid, signal);
282
+ }
283
+ });
284
+ }
285
+ }
286
+ return () => {
287
+ callbacks.delete(cleanup);
288
+ };
289
+ }
290
+ function tryReserveCliServerPort(port) {
291
+ return new Promise((resolve, reject) => {
292
+ const server = createServer();
293
+ let settled = false;
294
+ const finish = (value, error) => {
295
+ if (settled)
296
+ return;
297
+ settled = true;
298
+ server.removeAllListeners();
299
+ if (error) {
300
+ reject(error);
301
+ return;
302
+ }
303
+ resolve(value);
304
+ };
305
+ server.once('error', (error) => {
306
+ const code = error.code;
307
+ if (code === 'EADDRINUSE' || code === 'EACCES') {
308
+ finish(undefined);
309
+ return;
310
+ }
311
+ finish(undefined, error);
312
+ });
313
+ server.listen({ host: DEFAULT_OPENCODE_HOST, port, exclusive: true }, () => {
314
+ const address = server.address();
315
+ const resolvedPort = typeof address === 'object' && address ? address.port : port;
316
+ server.close((error) => {
317
+ if (error) {
318
+ finish(undefined, error);
319
+ return;
320
+ }
321
+ finish(resolvedPort);
322
+ });
323
+ });
324
+ server.unref();
325
+ });
326
+ }
327
+ export async function reserveCliServerPort(preferredPort = DEFAULT_OPENCODE_PORT, attemptedPorts = new Set()) {
328
+ if (!attemptedPorts.has(preferredPort)) {
329
+ const preferred = await tryReserveCliServerPort(preferredPort);
330
+ if (preferred !== undefined)
331
+ return preferred;
332
+ }
333
+ for (let attempt = 0; attempt < CLI_PORT_RESERVE_RETRY_LIMIT; attempt++) {
334
+ const fallback = await tryReserveCliServerPort(0);
335
+ if (fallback === undefined || attemptedPorts.has(fallback))
336
+ continue;
337
+ return fallback;
338
+ }
339
+ throw new Error('Failed to reserve a local port for the temporary OpenCode server');
340
+ }
195
341
  function releaseCliServerPipes(proc, inspect, onError, onExit) {
196
342
  if (inspect) {
197
343
  proc.stdout?.removeListener('data', inspect);
@@ -307,35 +453,97 @@ export async function tryStartCliOpencodeServer(candidate, spawnProcess = spawn,
307
453
  });
308
454
  return {
309
455
  url,
456
+ pid: proc.pid,
310
457
  close: () => closeProcess(proc),
311
458
  };
312
459
  }
313
- async function startCliOpencodeServer() {
314
- const candidates = cliServerCommandCandidates();
460
+ export function spawnCliServerWatchdog(targetPid, ttlMs = CLI_TEMP_SERVER_TTL_MS, spawnProcess = spawn, nodeExecPath = process.execPath) {
461
+ if (typeof targetPid !== 'number' ||
462
+ targetPid <= 0 ||
463
+ !Number.isFinite(ttlMs) ||
464
+ ttlMs <= 0) {
465
+ return undefined;
466
+ }
467
+ const script = [
468
+ "const { spawn } = require('node:child_process')",
469
+ `const pid = ${JSON.stringify(targetPid)}`,
470
+ `const ttl = ${JSON.stringify(Math.floor(ttlMs))}`,
471
+ 'setTimeout(() => {',
472
+ " if (process.platform === 'win32') {",
473
+ " const killer = spawn('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true })",
474
+ ' killer.unref?.()',
475
+ ' process.exit(0)',
476
+ ' }',
477
+ ' try {',
478
+ " process.kill(-pid, 'SIGTERM')",
479
+ ' } catch (error) {',
480
+ " if (!error || (typeof error === 'object' && error !== null && error.code !== 'ESRCH')) {",
481
+ " try { process.kill(pid, 'SIGTERM') } catch {}",
482
+ ' }',
483
+ ' }',
484
+ ' process.exit(0)',
485
+ '}, ttl)',
486
+ ].join('\n');
487
+ const proc = spawnProcess(nodeExecPath, ['-e', script], {
488
+ stdio: 'ignore',
489
+ detached: true,
490
+ windowsHide: true,
491
+ });
492
+ proc.unref();
493
+ return proc;
494
+ }
495
+ export async function startCliOpencodeServer(options) {
496
+ const platform = options?.platform ?? process.platform;
497
+ const spawnProcess = options?.spawnProcess ?? spawn;
498
+ const spawnWatchdogProcess = options?.spawnWatchdogProcess ?? spawn;
499
+ const closeProcess = options?.closeProcess ?? closeCliServerProcess;
500
+ const resolvePort = options?.reservePort ?? reserveCliServerPort;
501
+ const tempServerTtlMs = options?.tempServerTtlMs ?? CLI_TEMP_SERVER_TTL_MS;
315
502
  let lastError;
316
- for (const candidate of candidates) {
317
- try {
318
- return await tryStartCliOpencodeServer(candidate);
319
- }
320
- catch (failure) {
321
- lastError = failure;
322
- const recoverable = typeof failure === 'object' &&
323
- failure !== null &&
324
- 'recoverable' in failure &&
325
- failure.recoverable === true;
326
- if (!recoverable) {
327
- const error = typeof failure === 'object' && failure !== null && 'error' in failure
328
- ? failure.error
329
- : failure;
330
- throw error instanceof Error ? error : new Error(String(error));
503
+ const attemptedPorts = new Set();
504
+ for (let attempt = 0; attempt < CLI_SERVER_PORT_RETRY_LIMIT; attempt++) {
505
+ const port = await resolvePort(DEFAULT_OPENCODE_PORT, attemptedPorts);
506
+ attemptedPorts.add(port);
507
+ for (const candidate of cliServerCommandCandidates(platform, port)) {
508
+ try {
509
+ const server = await tryStartCliOpencodeServer(candidate, spawnProcess, closeProcess);
510
+ let watchdogProc;
511
+ try {
512
+ watchdogProc = spawnCliServerWatchdog(server.pid, tempServerTtlMs, spawnWatchdogProcess);
513
+ }
514
+ catch (error) {
515
+ server.close();
516
+ throw new Error(`Failed to start temporary OpenCode server watchdog: ${describeCliError(error)}`);
517
+ }
518
+ const stopWatchdog = () => {
519
+ if (!watchdogProc)
520
+ return;
521
+ const proc = watchdogProc;
522
+ watchdogProc = undefined;
523
+ closeProcess(proc);
524
+ };
525
+ return {
526
+ ...server,
527
+ close: () => {
528
+ stopWatchdog();
529
+ server.close();
530
+ },
531
+ };
532
+ }
533
+ catch (failure) {
534
+ lastError = failure;
535
+ if (isCliServerPortConflictFailure(failure))
536
+ break;
537
+ if (!isRecoverableCliServerFailure(failure)) {
538
+ throw cliServerFailureError(failure);
539
+ }
331
540
  }
332
541
  }
542
+ if (!isCliServerPortConflictFailure(lastError))
543
+ break;
333
544
  }
334
- const error = typeof lastError === 'object' && lastError !== null && 'error' in lastError
335
- ? lastError.error
336
- : lastError;
337
- throw error instanceof Error
338
- ? error
545
+ throw lastError
546
+ ? cliServerFailureError(lastError)
339
547
  : new Error('Failed to start OpenCode server');
340
548
  }
341
549
  async function resolvePathInfo(directory) {
@@ -360,7 +568,27 @@ async function resolvePathInfo(directory) {
360
568
  if (!isDefaultBaseUrl()) {
361
569
  throw new Error(`Failed to connect to OpenCode API at ${cliBaseUrl()}: ${error instanceof Error ? error.message : String(error)}`);
362
570
  }
363
- const server = await startCliOpencodeServer();
571
+ let server;
572
+ try {
573
+ server = await startCliOpencodeServer();
574
+ }
575
+ catch (startError) {
576
+ throw new Error(formatCliTempServerFailure('Failed to auto-start a temporary OpenCode server', startError));
577
+ }
578
+ let closed = false;
579
+ const unregisterTempServerCleanup = registerCliShutdownCleanup(() => {
580
+ if (closed)
581
+ return;
582
+ closed = true;
583
+ server.close();
584
+ });
585
+ const closeTempServer = () => {
586
+ if (closed)
587
+ return;
588
+ closed = true;
589
+ unregisterTempServerCleanup();
590
+ server.close();
591
+ };
364
592
  try {
365
593
  const client = createOpencodeClient({
366
594
  directory,
@@ -375,12 +603,12 @@ async function resolvePathInfo(directory) {
375
603
  client,
376
604
  worktree: data.worktree || directory,
377
605
  directory: data.directory || directory,
378
- close: () => server.close(),
606
+ close: closeTempServer,
379
607
  };
380
608
  }
381
609
  catch (innerError) {
382
- server.close();
383
- throw innerError;
610
+ closeTempServer();
611
+ throw new Error(formatCliTempServerFailure(`Failed to query the temporary OpenCode server at ${server.url}`, innerError));
384
612
  }
385
613
  }
386
614
  }
@@ -431,15 +659,21 @@ export async function runCli(argv) {
431
659
  listDescendantSessionIDs: async () => [],
432
660
  },
433
661
  });
434
- const allowedProviderIDs = await listCurrentProviderIDs({
435
- client,
436
- directory,
437
- });
662
+ const [allowedProviderIDs, connectedProviderIDs] = await Promise.all([
663
+ listCurrentProviderIDs({
664
+ client,
665
+ directory,
666
+ }),
667
+ listConnectedProviderIDs({
668
+ client,
669
+ directory,
670
+ }),
671
+ ]);
438
672
  if (command.since || command.last !== undefined) {
439
673
  const resolvedSince = command.since || sinceFromLast(command.period, command.last);
440
674
  const historyRaw = await usageService.summarizeHistoryUsage(command.period, resolvedSince);
441
675
  const history = strictFilterHistoryProviders(historyRaw, allowedProviderIDs);
442
- const quotas = await quotaService.getQuotaSnapshots(Object.keys(history.total.providers));
676
+ const quotas = await quotaService.getQuotaSnapshots(cliQuotaProviderIDs(Object.keys(history.total.providers), connectedProviderIDs));
443
677
  return renderCliHistoryDashboard({
444
678
  result: history,
445
679
  quotas,
@@ -449,7 +683,7 @@ export async function runCli(argv) {
449
683
  }
450
684
  const usageRaw = await usageService.summarizeForTool(command.period, '', false);
451
685
  const usage = strictFilterUsageProviders(usageRaw, allowedProviderIDs);
452
- const quotas = await quotaService.getQuotaSnapshots(Object.keys(usage.providers));
686
+ const quotas = await quotaService.getQuotaSnapshots(cliQuotaProviderIDs(Object.keys(usage.providers), connectedProviderIDs));
453
687
  return renderCliDashboard({
454
688
  label: cliCurrentLabel(command.period),
455
689
  usage,
@@ -4,5 +4,9 @@ export declare function listCurrentProviderIDs(input: {
4
4
  client: unknown;
5
5
  directory: string;
6
6
  }): Promise<Set<string>>;
7
+ export declare function listConnectedProviderIDs(input: {
8
+ client: unknown;
9
+ directory: string;
10
+ }): Promise<Set<string>>;
7
11
  export declare function filterUsageProvidersForDisplay(usage: UsageSummary, allowedProviderIDs: ReadonlySet<string>): UsageSummary;
8
12
  export declare function filterHistoryProvidersForDisplay(result: HistoryUsageResult, allowedProviderIDs: ReadonlySet<string>): HistoryUsageResult;
@@ -1,10 +1,14 @@
1
1
  function isRecord(value) {
2
2
  return typeof value === 'object' && value !== null && !Array.isArray(value);
3
3
  }
4
- function providerListFromResponse(response) {
5
- const data = isRecord(response) && Object.prototype.hasOwnProperty.call(response, 'data')
4
+ function providerDataFromResponse(response) {
5
+ return isRecord(response) &&
6
+ Object.prototype.hasOwnProperty.call(response, 'data')
6
7
  ? response.data
7
8
  : undefined;
9
+ }
10
+ function providerListFromResponse(response) {
11
+ const data = providerDataFromResponse(response);
8
12
  const record = isRecord(data) ? data : undefined;
9
13
  return Array.isArray(record?.providers)
10
14
  ? record.providers
@@ -14,6 +18,13 @@ function providerListFromResponse(response) {
14
18
  ? data
15
19
  : undefined;
16
20
  }
21
+ function connectedProviderIDsFromResponse(response) {
22
+ const data = providerDataFromResponse(response);
23
+ const record = isRecord(data) ? data : undefined;
24
+ return Array.isArray(record?.connected)
25
+ ? record.connected.filter((item) => typeof item === 'string' && !!item)
26
+ : undefined;
27
+ }
17
28
  export async function listCurrentProviderIDs(input) {
18
29
  const client = input.client;
19
30
  const ids = new Set();
@@ -46,6 +57,34 @@ export async function listCurrentProviderIDs(input) {
46
57
  }
47
58
  return ids;
48
59
  }
60
+ export async function listConnectedProviderIDs(input) {
61
+ const client = input.client;
62
+ const ids = new Set();
63
+ const collect = (response) => {
64
+ const connected = connectedProviderIDsFromResponse(response);
65
+ if (!connected)
66
+ return false;
67
+ for (const providerID of connected)
68
+ ids.add(providerID);
69
+ return ids.size > 0;
70
+ };
71
+ if (client.config?.providers) {
72
+ const response = await client.config.providers({
73
+ query: { directory: input.directory },
74
+ throwOnError: true,
75
+ });
76
+ if (collect(response))
77
+ return ids;
78
+ }
79
+ if (client.provider?.list) {
80
+ const response = await client.provider.list({
81
+ query: { directory: input.directory },
82
+ throwOnError: true,
83
+ });
84
+ collect(response);
85
+ }
86
+ return ids;
87
+ }
49
88
  export function filterUsageProvidersForDisplay(usage, allowedProviderIDs) {
50
89
  if (allowedProviderIDs.size === 0)
51
90
  return usage;
package/dist/tui.tsx CHANGED
@@ -253,7 +253,6 @@ function QuotaGroupBlock(props: {
253
253
  group: SidebarQuotaGroup
254
254
  bullet: boolean
255
255
  }) {
256
- const detailColor = quotaToneColor(props.api, props.group.tone)
257
256
  const content = (
258
257
  <box gap={0}>
259
258
  <text>
@@ -261,11 +260,14 @@ function QuotaGroupBlock(props: {
261
260
  {props.group.shortLabel}
262
261
  </span>
263
262
  <Show when={props.group.detail}>
264
- <span style={{ fg: detailColor }}> {props.group.detail}</span>
263
+ <span style={{ fg: props.api.theme.current.textMuted }}>
264
+ {' '}
265
+ {props.group.detail}
266
+ </span>
265
267
  </Show>
266
268
  </text>
267
269
  <For each={props.group.continuationLines}>
268
- {(line) => <text fg={detailColor}>{line}</text>}
270
+ {(line) => <text fg={props.api.theme.current.textMuted}>{line}</text>}
269
271
  </For>
270
272
  </box>
271
273
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leo000001/opencode-quota-sidebar",
3
- "version": "4.1.0",
3
+ "version": "4.1.2",
4
4
  "description": "OpenCode plugin that shows quota and token usage in TUI sidebar panels and compact session titles",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",