opencode-usage 0.5.0 → 0.5.1

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
@@ -11,6 +11,7 @@ CLI tool for tracking [OpenCode](https://github.com/sst/opencode) AI coding assi
11
11
  - JSON output for scripting and automation
12
12
  - Model pricing for accurate cost estimation
13
13
  - Terminal table output
14
+ - **Commander web dashboard** with quota status, account management, and ping
14
15
 
15
16
  ## Installation
16
17
 
@@ -63,6 +64,25 @@ opencode-usage -w -d 1
63
64
  opencode-usage --provider anthropic --since 7d --json
64
65
  ```
65
66
 
67
+ ### Commander Web Dashboard
68
+
69
+ ```bash
70
+ # Launch the web dashboard
71
+ opencode-usage --commander
72
+
73
+ # Custom port
74
+ opencode-usage --commander --commander-port 5000
75
+ ```
76
+
77
+ The Commander provides a single-page web UI with:
78
+
79
+ - **Quota Status** - Per-provider account usage with progress bars, thresholds, and stale detection (Anthropic, Codex, Antigravity)
80
+ - **Usage Breakdown** - Daily token usage table with cost estimates and provider drill-down
81
+ - **Account Management** - Add, switch, remove, and re-authenticate accounts
82
+ - **Ping** - Verify account connectivity with live PONG/FAIL indicators
83
+ - **Dark mode** toggle
84
+ - Auto-refresh every 5 minutes
85
+
66
86
  ## Output
67
87
 
68
88
  ```
package/dist/index.js CHANGED
@@ -30146,6 +30146,56 @@ import { join as join10, dirname as dirname2 } from "path";
30146
30146
  import { readFileSync as readFileSync2 } from "fs";
30147
30147
  import { fileURLToPath as fileURLToPath2 } from "url";
30148
30148
 
30149
+ // src/commander/services/usage-service.ts
30150
+ function serializeProviderStats(ps) {
30151
+ return {
30152
+ input: ps.input,
30153
+ output: ps.output,
30154
+ cacheWrite: ps.cacheWrite,
30155
+ cacheRead: ps.cacheRead,
30156
+ reasoning: ps.reasoning,
30157
+ cost: ps.cost,
30158
+ models: [...ps.models]
30159
+ };
30160
+ }
30161
+ function serializeDailyStats(stats) {
30162
+ const providerStats = {};
30163
+ for (const [id, ps] of stats.providerStats) {
30164
+ providerStats[id] = serializeProviderStats(ps);
30165
+ }
30166
+ return {
30167
+ date: stats.date,
30168
+ input: stats.input,
30169
+ output: stats.output,
30170
+ cacheWrite: stats.cacheWrite,
30171
+ cacheRead: stats.cacheRead,
30172
+ reasoning: stats.reasoning,
30173
+ cost: stats.cost,
30174
+ models: [...stats.models],
30175
+ providers: [...stats.providers],
30176
+ providerStats
30177
+ };
30178
+ }
30179
+ async function getUsageData(opts = {}) {
30180
+ const storagePath = getOpenCodeStoragePath();
30181
+ const messages = await loadMessages(storagePath, opts.provider);
30182
+ let stats = aggregateByDate(messages);
30183
+ if (opts.days !== undefined) {
30184
+ stats = filterByDays(stats, opts.days);
30185
+ }
30186
+ if (opts.since !== undefined || opts.until !== undefined) {
30187
+ stats = filterByDateRange(stats, opts.since, opts.until);
30188
+ }
30189
+ if (opts.monthly) {
30190
+ stats = aggregateByMonth(stats);
30191
+ }
30192
+ const result = [];
30193
+ for (const [, entry] of stats) {
30194
+ result.push(serializeDailyStats(entry));
30195
+ }
30196
+ return result;
30197
+ }
30198
+
30149
30199
  // src/commander/services/quota-service.ts
30150
30200
  async function getQuotaData() {
30151
30201
  const [anthropic, antigravity, codex] = await Promise.all([
@@ -30466,7 +30516,9 @@ var registered2 = false;
30466
30516
 
30467
30517
  // src/commander/server.ts
30468
30518
  init_config_service();
30469
- function queryUsageInWorker(opts) {
30519
+ async function queryUsage(opts) {
30520
+ if (!canUseWorker)
30521
+ return getUsageData(opts);
30470
30522
  return new Promise((resolve3, reject) => {
30471
30523
  const worker = new Worker(usageWorkerPath);
30472
30524
  worker.onmessage = (event) => {
@@ -30514,7 +30566,7 @@ async function runCommanderServer(args) {
30514
30566
  const since = url.searchParams.get("since") ?? undefined;
30515
30567
  const until = url.searchParams.get("until") ?? undefined;
30516
30568
  const monthly = url.searchParams.get("monthly") === "true";
30517
- const data = await queryUsageInWorker({
30569
+ const data = await queryUsage({
30518
30570
  provider,
30519
30571
  days,
30520
30572
  since,
@@ -30679,7 +30731,8 @@ async function runCommanderServer(args) {
30679
30731
  }
30680
30732
  }
30681
30733
  if (!url.pathname.startsWith("/api/")) {
30682
- const UI_DIST = join10(new URL(".", import.meta.url).pathname, "../commander-ui/dist");
30734
+ const base = new URL(".", import.meta.url).pathname;
30735
+ const UI_DIST = await Bun.file(join10(base, "commander-ui", "index.html")).exists() ? join10(base, "commander-ui") : join10(base, "..", "commander-ui", "dist");
30683
30736
  const filePath = url.pathname === "/" ? join10(UI_DIST, "index.html") : join10(UI_DIST, url.pathname);
30684
30737
  const file = Bun.file(filePath);
30685
30738
  if (await file.exists())
@@ -30708,6 +30761,7 @@ var PKG_VERSION = (() => {
30708
30761
  }
30709
30762
  })();
30710
30763
  var usageWorkerPath = join10(__dirname2, "services", "usage-worker.ts");
30764
+ var canUseWorker = await Bun.file(usageWorkerPath).exists();
30711
30765
  // src/index.ts
30712
30766
  function clearScreen() {
30713
30767
  process.stdout.write("\x1B[2J\x1B[H");
@@ -30818,4 +30872,4 @@ async function main2() {
30818
30872
  var WATCH_INTERVAL_MS = 5 * 60 * 1000;
30819
30873
  main2().catch(console.error);
30820
30874
 
30821
- //# debugId=FEF96600978E6E5764756E2164756E21
30875
+ //# debugId=AAD27BF82DBA8BEB64756E2164756E21