@leo000001/opencode-quota-sidebar 4.0.0 → 4.0.3

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 CHANGED
@@ -325,6 +325,12 @@ Examples:
325
325
 
326
326
  The package exposes a standalone CLI dashboard. After installing globally or making the `bin` available:
327
327
 
328
+ ```bash
329
+ npm install -g @leo000001/opencode-quota-sidebar
330
+ ```
331
+
332
+ If you prefer not to install globally, you can also run it with `npx @leo000001/opencode-quota-sidebar <args>`.
333
+
328
334
  ```bash
329
335
  # Current period (single snapshot)
330
336
  opencode-quota day # today
package/README.zh-CN.md CHANGED
@@ -332,6 +332,12 @@ usage 聚合采用增量模式。插件会为每个 session 维护 cursor,优
332
332
 
333
333
  这个包同时提供一个独立 CLI dashboard。全局安装或让 `bin` 可执行后:
334
334
 
335
+ ```bash
336
+ npm install -g @leo000001/opencode-quota-sidebar
337
+ ```
338
+
339
+ 如果不想全局安装,也可以直接用 `npx @leo000001/opencode-quota-sidebar <args>` 运行。
340
+
335
341
  ```bash
336
342
  # 当前周期(单次快照)
337
343
  opencode-quota day # 今天
package/dist/cli.d.ts CHANGED
@@ -15,4 +15,5 @@ export declare function cliBaseUrl(): string;
15
15
  export declare function cliServerCommandCandidates(platform?: NodeJS.Platform): CliServerCommand[];
16
16
  export declare function runCli(argv: string[]): Promise<string>;
17
17
  export declare function cliExitCodeForError(message: string): 0 | 1;
18
+ export declare function cliShouldRunMain(argv1?: string, modulePath?: string, resolvePath?: (filePath: string) => string): boolean;
18
19
  export {};
package/dist/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { realpathSync } from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import { spawn } from 'node:child_process';
4
5
  import { fileURLToPath } from 'node:url';
@@ -335,6 +336,19 @@ export async function runCli(argv) {
335
336
  export function cliExitCodeForError(message) {
336
337
  return message === HELP_TEXT ? 0 : 1;
337
338
  }
339
+ function resolveCliPath(filePath) {
340
+ try {
341
+ return realpathSync.native(filePath);
342
+ }
343
+ catch {
344
+ return path.resolve(filePath);
345
+ }
346
+ }
347
+ export function cliShouldRunMain(argv1 = process.argv[1], modulePath = fileURLToPath(import.meta.url), resolvePath = resolveCliPath) {
348
+ if (!argv1)
349
+ return false;
350
+ return resolvePath(modulePath) === resolvePath(argv1);
351
+ }
338
352
  async function main() {
339
353
  try {
340
354
  const output = await runCli(process.argv.slice(2));
@@ -348,7 +362,6 @@ async function main() {
348
362
  process.exitCode = exitCode;
349
363
  }
350
364
  }
351
- if (process.argv[1] &&
352
- path.resolve(fileURLToPath(import.meta.url)) === path.resolve(process.argv[1])) {
365
+ if (cliShouldRunMain()) {
353
366
  void main();
354
367
  }
package/dist/format.js CHANGED
@@ -409,15 +409,22 @@ function usageDetailLines(usage, cacheMetrics, options) {
409
409
  `I${numberToken(usage.input)}`,
410
410
  `O${numberToken(usage.output)}`,
411
411
  ]);
412
- const secondary = [];
412
+ const secondaryGroups = [];
413
+ const coverageTokens = [];
414
+ if (cacheMetrics.cachedRatio !== undefined) {
415
+ coverageTokens.push(`Cd ${formatPercent(cacheMetrics.cachedRatio, 0)}`);
416
+ }
417
+ if (coverageTokens.length > 0)
418
+ secondaryGroups.push(coverageTokens);
419
+ const cacheTokens = [];
413
420
  const pushCacheRead = () => {
414
421
  if (usage.cacheRead > 0) {
415
- secondary.push(`CR${numberToken(usage.cacheRead)}`);
422
+ cacheTokens.push(`R${numberToken(usage.cacheRead)}`);
416
423
  }
417
424
  };
418
425
  const pushCacheWrite = () => {
419
426
  if (usage.cacheWrite > 0) {
420
- secondary.push(`CW${numberToken(usage.cacheWrite)}`);
427
+ cacheTokens.push(`W${numberToken(usage.cacheWrite)}`);
421
428
  }
422
429
  };
423
430
  if (options.cacheReadFirst) {
@@ -428,11 +435,22 @@ function usageDetailLines(usage, cacheMetrics, options) {
428
435
  pushCacheWrite();
429
436
  pushCacheRead();
430
437
  }
431
- if (cacheMetrics.cachedRatio !== undefined) {
432
- secondary.push(`Cd${formatPercent(cacheMetrics.cachedRatio, 0)}`);
438
+ if (cacheTokens.length > 0) {
439
+ if (coverageTokens.length > 0) {
440
+ const combined = [...coverageTokens, ...cacheTokens].join(' ');
441
+ if (fitsLine(combined, width)) {
442
+ secondaryGroups.length = 0;
443
+ secondaryGroups.push([...coverageTokens, ...cacheTokens]);
444
+ }
445
+ else {
446
+ secondaryGroups.push(cacheTokens);
447
+ }
448
+ }
449
+ else {
450
+ secondaryGroups.push(cacheTokens);
451
+ }
433
452
  }
434
- if (secondary.length > 0)
435
- groups.push(secondary);
453
+ groups.push(...secondaryGroups);
436
454
  if (options.showCost && usage.apiCost > 0) {
437
455
  groups.push([costToken(usage.apiCost)]);
438
456
  }
@@ -453,6 +471,65 @@ function usageDetailLines(usage, cacheMetrics, options) {
453
471
  }
454
472
  return packed;
455
473
  }
474
+ function packUsageGroups(groups, width) {
475
+ const packed = [];
476
+ for (const group of groups) {
477
+ let current = '';
478
+ for (const token of group) {
479
+ const candidate = current ? `${current} ${token}` : token;
480
+ if (!current || fitsLine(candidate, width)) {
481
+ current = candidate;
482
+ continue;
483
+ }
484
+ packed.push(current);
485
+ current = token;
486
+ }
487
+ if (current)
488
+ packed.push(current);
489
+ }
490
+ return packed;
491
+ }
492
+ function readableSidebarUsageLines(usage, cacheMetrics, width, showCost) {
493
+ const groups = [
494
+ [
495
+ `Req ${shortNumber(usage.assistantMessages, 1)}`,
496
+ `In ${panelNumber(usage.input)}`,
497
+ `Out ${panelNumber(usage.output)}`,
498
+ ],
499
+ ];
500
+ if (cacheMetrics.cachedRatio !== undefined) {
501
+ const cacheTokens = [
502
+ `Cached ${formatPercent(cacheMetrics.cachedRatio, 0)}`,
503
+ ];
504
+ if (usage.cacheRead > 0)
505
+ cacheTokens.push(`Read ${panelNumber(usage.cacheRead)}`);
506
+ if (usage.cacheWrite > 0)
507
+ cacheTokens.push(`Write ${panelNumber(usage.cacheWrite)}`);
508
+ groups.push(cacheTokens);
509
+ }
510
+ else {
511
+ const cacheTokens = [];
512
+ if (usage.cacheRead > 0)
513
+ cacheTokens.push(`Cache Read ${panelNumber(usage.cacheRead)}`);
514
+ if (usage.cacheWrite > 0)
515
+ cacheTokens.push(`Write ${panelNumber(usage.cacheWrite)}`);
516
+ if (cacheTokens.length > 0)
517
+ groups.push(cacheTokens);
518
+ }
519
+ const summaryTokens = [];
520
+ if (showCost && usage.apiCost > 0) {
521
+ summaryTokens.push(`Est ${formatApiCostValue(usage.apiCost)}`);
522
+ }
523
+ if (summaryTokens.length > 0)
524
+ groups.push(summaryTokens);
525
+ const allTokens = groups.flat();
526
+ if (allTokens.some((token) => !fitsLine(token, width)))
527
+ return undefined;
528
+ const packed = packUsageGroups(groups, width);
529
+ if (packed.length > 4)
530
+ return undefined;
531
+ return packed;
532
+ }
456
533
  function formatQuotaPercent(value, options) {
457
534
  const missing = options?.missing ?? '-';
458
535
  if (value === undefined)
@@ -611,9 +688,13 @@ export function renderSidebarContextLine(tokens, percent, width) {
611
688
  export function renderSidebarUsageLines(usage, config, options) {
612
689
  const width = Math.max(8, Math.floor(config.sidebar.width || 36));
613
690
  const cacheMetrics = getCacheCoverageMetrics(usage);
691
+ const showCost = options?.showCost ?? config.sidebar.showCost;
692
+ const readable = readableSidebarUsageLines(usage, cacheMetrics, width, showCost);
693
+ if (readable)
694
+ return readable.map((line) => fitLine(line, width));
614
695
  return usageDetailLines(usage, cacheMetrics, {
615
696
  width,
616
- showCost: options?.showCost ?? config.sidebar.showCost,
697
+ showCost,
617
698
  numberToken: panelNumber,
618
699
  costToken: (value) => `Est ${formatApiCostValue(value)}`,
619
700
  cacheReadFirst: true,
package/dist/title.js CHANGED
@@ -3,6 +3,7 @@ function sanitizeTitleFragment(value) {
3
3
  .replace(/[\x00-\x1F\x7F-\x9F]/g, ' ')
4
4
  .trimEnd();
5
5
  }
6
+ const compactUsageTokenPattern = '(?:R\\$?[\\d.,]+[kKmM]?|W\\$?[\\d.,]+[kKmM]?|I\\$?[\\d.,]+[kKmM]?|O\\$?[\\d.,]+[kKmM]?|CR\\$?[\\d.,]+[kKmM]?|CW\\$?[\\d.,]+[kKmM]?|R(?:ead)?\\s+\\$?[\\d.,]+[kKmM]?|W(?:rite)?\\s+\\$?[\\d.,]+[kKmM]?|Cd\\s*\\d[\\d.,]*%|CC\\d[\\d.,]*%|CRC\\d[\\d.,]*%|API\\$\\S+|Est\\$\\S+)';
6
7
  function isCoreDecoratedDetail(line) {
7
8
  if (!line)
8
9
  return false;
@@ -29,7 +30,7 @@ function isCoreDecoratedDetail(line) {
29
30
  return true;
30
31
  if (/^Est\$\S+$/.test(line))
31
32
  return true;
32
- if (/^(?:R\$?[\d.,]+[kKmM]?|I\$?[\d.,]+[kKmM]?|O\$?[\d.,]+[kKmM]?|CR\$?[\d.,]+[kKmM]?|CW\$?[\d.,]+[kKmM]?|Cd\d[\d.,]*%|CC\d[\d.,]*%|CRC\d[\d.,]*%|API\$\S+|Est\$\S+)(?:\s+(?:R\$?[\d.,]+[kKmM]?|I\$?[\d.,]+[kKmM]?|O\$?[\d.,]+[kKmM]?|CR\$?[\d.,]+[kKmM]?|CW\$?[\d.,]+[kKmM]?|Cd\d[\d.,]*%|CC\d[\d.,]*%|CRC\d[\d.,]*%|API\$\S+|Est\$\S+))*$/.test(line)) {
33
+ if (new RegExp(`^${compactUsageTokenPattern}(?:\\s+${compactUsageTokenPattern})*$`).test(line)) {
33
34
  return true;
34
35
  }
35
36
  // Single-line compact mode compatibility.
@@ -125,7 +126,7 @@ function isSingleLineDecoratedPrefix(line) {
125
126
  return true;
126
127
  if (/^Est\$\S+(?:~|$)/.test(line))
127
128
  return true;
128
- if (/^(?:R\$?[\d.,]+[kKmM]?|I\$?[\d.,]+[kKmM]?|O\$?[\d.,]+[kKmM]?|CR\$?[\d.,]+[kKmM]?|CW\$?[\d.,]+[kKmM]?|Cd\d[\d.,]*%|CC\d[\d.,]*%|CRC\d[\d.,]*%|API\$\S+|Est\$\S+)(?:\s+(?:R\$?[\d.,]+[kKmM]?|I\$?[\d.,]+[kKmM]?|O\$?[\d.,]+[kKmM]?|CR\$?[\d.,]+[kKmM]?|CW\$?[\d.,]+[kKmM]?|Cd\d[\d.,]*%|CC\d[\d.,]*%|CRC\d[\d.,]*%|API\$\S+|Est\$\S+))*?(?:~|$)/.test(line)) {
129
+ if (new RegExp(`^${compactUsageTokenPattern}(?:\\s+${compactUsageTokenPattern})*?(?:~|$)`).test(line)) {
129
130
  return true;
130
131
  }
131
132
  return false;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@leo000001/opencode-quota-sidebar",
3
- "version": "4.0.0",
3
+ "version": "4.0.3",
4
4
  "description": "OpenCode plugin that shows quota and token usage in TUI sidebar panels and compact session titles",
5
5
  "type": "module",
6
- "main": "dist/index.js",
6
+ "main": "./dist/index.js",
7
7
  "bin": {
8
8
  "opencode-quota": "./dist/cli.js"
9
9
  },
@@ -13,6 +13,10 @@
13
13
  "types": "./dist/index.d.ts",
14
14
  "default": "./dist/index.js"
15
15
  },
16
+ "./server": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ },
16
20
  "./tui": {
17
21
  "types": "./dist/tui.d.ts",
18
22
  "default": "./dist/tui.tsx"