@1930dev/opencode-usage 0.3.2 → 0.3.4

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.
Files changed (4) hide show
  1. package/README.md +11 -5
  2. package/dist/cli.js +149 -20
  3. package/dist/tui.js +148 -19
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -123,7 +123,12 @@ Normalized percentage of budget consumed per provider, from these sources (in pr
123
123
  - `github-copilot`: premium requests entitlement (7000/mo)
124
124
  - `opencode-go` (Zen): rolling 5h / weekly / monthly % (binding window)
125
125
  - `openrouter`: credits used / total credits
126
- - `zai`: coding plan quota
126
+ - `orcarouter`: spend since top-up; a per-key credit cap turns into a %
127
+ - `zai`: coding plan quota when the key has an active GLM plan; otherwise the
128
+ wallet balance (fallback)
129
+ - `snowflake-cortex`: tokens in the last 30d from the account usage views
130
+ (SQL API); the percentage comes from a `snowflake-cortex` line in
131
+ `budgets.json`
127
132
 
128
133
  2. **Documented limits** — published quotas, used when the provider reports none.
129
134
  Each one is read against the period it resets on: a daily limit against today,
@@ -138,10 +143,11 @@ Normalized percentage of budget consumed per provider, from these sources (in pr
138
143
  records tokens and requests; a credit and a neuron are the provider's own
139
144
  unit, derived from the model and the request by a rule it does not record.
140
145
  Give them a line in `budgets.json` to get a percentage in USD instead.
141
- - `cloudflare-workers-ai`: 100k neurons/day (free tier)
142
- - `nvidia`: 1000 credits/month (free tier)
143
- - `orcarouter`: undocumented
144
- - `snowflake-cortex`: 100 credits/month (paid)
146
+ - `cloudflare-workers-ai`: 10k neurons/day (free tier, resets 00:00 UTC)
147
+ - `nvidia`: credit allowance retired — the trial is rate-limited per model
148
+ - `opencode`: no spend API for API keys
149
+ - `zai`: a key without a coding plan shows its wallet balance (live), and the
150
+ percentage comes from a `zai` line in `budgets.json`
145
151
 
146
152
  3. **budgets.json** — your monthly USD per provider, read against what the
147
153
  provider cost so far this calendar month:
package/dist/cli.js CHANGED
@@ -284,12 +284,39 @@ function parseZai(data) {
284
284
  raw: data
285
285
  };
286
286
  }
287
+ function parseZaiWallet(data) {
288
+ const failed = data.success === false || data.code !== undefined && data.code !== 0 && data.code !== 200;
289
+ if (failed) {
290
+ return { provider: "zai", ok: false, detail: data.msg ?? "wallet endpoint error", windows: [] };
291
+ }
292
+ const balance = data.data?.availableBalance ?? data.data?.rechargeAmount ?? 0;
293
+ const spend = data.data?.totalSpendAmount ?? 0;
294
+ const windows = [
295
+ { label: "balance", percentUsed: 0, detail: `$${balance.toFixed(2)}` },
296
+ { label: "spend", percentUsed: 0, detail: `$${spend.toFixed(2)}` }
297
+ ];
298
+ const today = data.data?.todaySpendAmount;
299
+ if (typeof today === "number") {
300
+ windows.push({ label: "today", percentUsed: 0, detail: `$${today.toFixed(2)}` });
301
+ }
302
+ return {
303
+ provider: "zai",
304
+ ok: true,
305
+ detail: `balance $${balance.toFixed(2)}`,
306
+ windows,
307
+ raw: data
308
+ };
309
+ }
310
+ var QUOTA_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
311
+ var WALLET_URL = "https://api.z.ai/api/biz/account/query-customer-account-report";
287
312
  async function fetchZai(key) {
288
313
  try {
289
- const data = await getJson("https://api.z.ai/api/monitor/usage/quota/limit", {
290
- Authorization: `Bearer ${key}`,
291
- Accept: "application/json"
292
- });
314
+ const auth = { Authorization: `Bearer ${key}`, Accept: "application/json" };
315
+ const data = await getJson(QUOTA_URL, auth);
316
+ if (data.code !== undefined && data.code !== 0) {
317
+ const wallet = await getJson(WALLET_URL, auth);
318
+ return parseZaiWallet(wallet);
319
+ }
293
320
  return parseZai(data);
294
321
  } catch (err) {
295
322
  return { provider: "zai", ok: false, detail: err.message, windows: [] };
@@ -328,6 +355,88 @@ async function fetchAmd(key) {
328
355
  }
329
356
  }
330
357
 
358
+ // packages/core/src/quota/orcarouter.ts
359
+ var CENTS_PER_USD = 100;
360
+ var NO_CAP = 1e6;
361
+ function parseOrcaRouter(usage, subscription) {
362
+ const spent = (usage.total_usage ?? 0) / CENTS_PER_USD;
363
+ const hard = subscription.hard_limit_usd ?? 0;
364
+ const soft = subscription.soft_limit_usd ?? 0;
365
+ const cap = hard > 0 && hard < NO_CAP ? hard : soft > 0 && soft < NO_CAP ? soft : 0;
366
+ const capped = cap > 0;
367
+ const pct = capped ? spent / cap * 100 : 0;
368
+ const hint = capped ? "" : " (no cap set)";
369
+ const detail = `$${spent.toFixed(2)} spent${hint}`;
370
+ return {
371
+ provider: "orcarouter",
372
+ ok: true,
373
+ detail,
374
+ windows: [{ label: "spend", percentUsed: pct, detail: `$${spent.toFixed(2)}${hint}` }],
375
+ budget: capped ? { percentUsed: pct, label: `$${cap}` } : undefined,
376
+ raw: { usage, subscription }
377
+ };
378
+ }
379
+ async function fetchOrcaRouter(key) {
380
+ try {
381
+ const headers = { Authorization: `Bearer ${key}`, Accept: "application/json" };
382
+ const usage = await getJson("https://api.orcarouter.ai/v1/dashboard/billing/usage", headers);
383
+ const subscription = await getJson("https://api.orcarouter.ai/v1/dashboard/billing/subscription", headers);
384
+ return parseOrcaRouter(usage, subscription);
385
+ } catch (err) {
386
+ return { provider: "orcarouter", ok: false, detail: err.message, windows: [] };
387
+ }
388
+ }
389
+
390
+ // packages/core/src/quota/snowflake.ts
391
+ var SQL_API = (account) => `https://${account}.snowflakecomputing.com/api/v2/statements`;
392
+ var STATEMENT = `SELECT
393
+ SUM(CASE WHEN START_TIME >= DATE_TRUNC('day', CURRENT_TIMESTAMP())
394
+ AND START_TIME < DATEADD('day', 1, DATE_TRUNC('day', CURRENT_TIMESTAMP()))
395
+ THEN TOKENS ELSE 0 END) AS today_tokens,
396
+ SUM(TOKENS) AS month_tokens
397
+ FROM SNOWFLAKE.ACCOUNT_USAGE.CORTEX_REST_API_USAGE_HISTORY
398
+ WHERE START_TIME >= DATEADD('day', -30, CURRENT_TIMESTAMP())`;
399
+ var fmtTokens = (n) => n.toLocaleString("en-US");
400
+ function parseSnowflake(row) {
401
+ const today = Number(row?.[0] ?? 0);
402
+ const month = Number(row?.[1] ?? 0);
403
+ return {
404
+ provider: "snowflake-cortex",
405
+ ok: true,
406
+ detail: `${fmtTokens(month)} tokens/30d`,
407
+ windows: [
408
+ { label: "tokens/mo", percentUsed: 0, detail: `${fmtTokens(month)}` },
409
+ { label: "today", percentUsed: 0, detail: `${fmtTokens(today)}` }
410
+ ],
411
+ raw: { row }
412
+ };
413
+ }
414
+ async function fetchSnowflake(key, metadata) {
415
+ const account = String(metadata?.account ?? "").toUpperCase();
416
+ if (!account) {
417
+ return { provider: "snowflake-cortex", ok: false, detail: "metadata.account is required", windows: [] };
418
+ }
419
+ try {
420
+ const res = await fetch(SQL_API(account), {
421
+ method: "POST",
422
+ headers: {
423
+ Authorization: `Bearer ${key}`,
424
+ "Content-Type": "application/json",
425
+ Accept: "application/json"
426
+ },
427
+ body: JSON.stringify({ statement: STATEMENT, timeout: 30 })
428
+ });
429
+ const body = await res.json().catch(() => null);
430
+ if (!res.ok) {
431
+ return { provider: "snowflake-cortex", ok: false, detail: body?.message ?? `HTTP ${res.status}`, windows: [] };
432
+ }
433
+ const rows = Array.isArray(body?.data) ? body.data : [];
434
+ return parseSnowflake(rows[0]);
435
+ } catch (err) {
436
+ return { provider: "snowflake-cortex", ok: false, detail: err.message, windows: [] };
437
+ }
438
+ }
439
+
331
440
  // packages/core/src/providers.ts
332
441
  var TTL_MS = 15 * 60 * 1000;
333
442
  async function readCache() {
@@ -350,7 +459,9 @@ var FETCHERS = {
350
459
  openrouter: fetchOpenRouter,
351
460
  "github-copilot": fetchCopilot,
352
461
  zai: fetchZai,
353
- amd: fetchAmd
462
+ amd: fetchAmd,
463
+ orcarouter: fetchOrcaRouter,
464
+ "snowflake-cortex": fetchSnowflake
354
465
  };
355
466
  async function providerStatuses(opts) {
356
467
  const auth = await readAuth();
@@ -371,7 +482,7 @@ async function providerStatuses(opts) {
371
482
  const secret = authSecret(auth[p]);
372
483
  if (!fetcher || !secret)
373
484
  return null;
374
- return fetcher(secret);
485
+ return fetcher(secret, auth[p].metadata);
375
486
  }));
376
487
  for (const q of live) {
377
488
  if (q)
@@ -394,7 +505,7 @@ async function providerStatuses(opts) {
394
505
  }));
395
506
  }
396
507
  // packages/core/src/report.ts
397
- function fmtTokens(n) {
508
+ function fmtTokens2(n) {
398
509
  if (n >= 1e9)
399
510
  return `${(n / 1e9).toFixed(1)}B`;
400
511
  if (n >= 1e6)
@@ -436,8 +547,8 @@ function usageTable(rows, totals, sinceLabel, groupBy, pct) {
436
547
  const row = [
437
548
  r.group,
438
549
  String(r.messages),
439
- fmtTokens(r.tokensInput),
440
- fmtTokens(r.tokensOutput),
550
+ fmtTokens2(r.tokensInput),
551
+ fmtTokens2(r.tokensOutput),
441
552
  fmtCost(r.cost)
442
553
  ];
443
554
  if (pct) {
@@ -454,7 +565,7 @@ function usageTable(rows, totals, sinceLabel, groupBy, pct) {
454
565
  [sources: ${[...pct.values()].map((p) => `${p.source}${p.label ? `(${p.label})` : ""}`).filter((v, i, a) => a.indexOf(v) === i).join(", ")}]` : "";
455
566
  return `${t}
456
567
 
457
- TOTAL since ${sinceLabel}: ${totals.messages} msgs, ${fmtTokens(totals.tokensInput)} in / ${fmtTokens(totals.tokensOutput)} out, ${fmtCost(totals.cost)} est.${sourceNote}`;
568
+ TOTAL since ${sinceLabel}: ${totals.messages} msgs, ${fmtTokens2(totals.tokensInput)} in / ${fmtTokens2(totals.tokensOutput)} out, ${fmtCost(totals.cost)} est.${sourceNote}`;
458
569
  }
459
570
  function providersTable(statuses, local) {
460
571
  const headers = ["PROVIDER", "QUOTA", "USAGE 7D (LOCAL)", "LAST USED"];
@@ -638,10 +749,10 @@ var PROVIDER_LIMITS = [
638
749
  provider: "cloudflare-workers-ai",
639
750
  unit: "neurons",
640
751
  period: "day",
641
- limit: 1e5,
752
+ limit: 1e4,
642
753
  tier: "free",
643
- source: "https://developers.cloudflare.com/workers-ai/platform/limits/",
644
- note: "A neuron is Cloudflare's own unit, per model. Not measurable from tokens."
754
+ source: "https://developers.cloudflare.com/workers-ai/platform/pricing/",
755
+ note: "10,000 neurons/day free, resets at 00:00 UTC. A neuron is Cloudflare's own unit, per model. Live usage needs an API token with the Analytics read scope; the Workers AI key does not carry one."
645
756
  },
646
757
  {
647
758
  provider: "digitalocean",
@@ -674,10 +785,19 @@ var PROVIDER_LIMITS = [
674
785
  provider: "nvidia",
675
786
  unit: "credits",
676
787
  period: "month",
677
- limit: 1000,
788
+ limit: 0,
678
789
  tier: "free",
679
- source: "https://build.nvidia.com/",
680
- note: "NIM free tier ~1000 credits/month. A credit varies by model."
790
+ source: "https://forums.developer.nvidia.com/t/request-more-4-000-credits-option-on-build-nvidia-com/344567",
791
+ note: "The credit allowance was retired; build.nvidia.com now rate-limits the trial per model (about 40 RPM, unpublished, shown in the UI header). No usage endpoint exists."
792
+ },
793
+ {
794
+ provider: "opencode",
795
+ unit: "tokens",
796
+ period: "month",
797
+ limit: 0,
798
+ tier: "unknown",
799
+ source: "https://opencode.ai/docs/go/",
800
+ note: "The opencode gateway exposes no spend API to its API key; the dashboard balance needs a browser session. Go usage is measured through the opencode-go provider instead."
681
801
  },
682
802
  {
683
803
  provider: "orcarouter",
@@ -685,8 +805,8 @@ var PROVIDER_LIMITS = [
685
805
  period: "day",
686
806
  limit: 0,
687
807
  tier: "unknown",
688
- source: "unknown",
689
- note: "Proxy service. No public limits documented."
808
+ source: "https://docs.orcarouter.ai/operations/billing-and-usage",
809
+ note: "Pay-per-token at upstream rates with no markup. A per-key credit cap turns into a live budget; without one there is nothing to exhaust. Free models are rate-limited with unpublished numbers."
690
810
  },
691
811
  {
692
812
  provider: "snowflake-cortex",
@@ -694,8 +814,17 @@ var PROVIDER_LIMITS = [
694
814
  period: "month",
695
815
  limit: 100,
696
816
  tier: "paid",
697
- source: "https://docs.snowflake.com/en/user-guide/snowflake-cortex",
698
- note: "Credits follow warehouse time, not tokens. Not measurable from usage."
817
+ source: "https://docs.snowflake.com/en/sql-reference/account-usage/cortex_ai_functions_usage_history",
818
+ note: "Billed per token (Service Consumption Table). Live tokens come from ACCOUNT_USAGE.CORTEX_REST_API_USAGE_HISTORY through the SQL API with the stored JWT; the percentage needs a snowflake-cortex line in budgets.json."
819
+ },
820
+ {
821
+ provider: "zai",
822
+ unit: "tokens",
823
+ period: "day",
824
+ limit: 0,
825
+ tier: "unknown",
826
+ source: "https://openusage.sh/docs/providers/zai/",
827
+ note: "The quota monitor answers only keys with an active GLM coding plan; this key reports none, so the endpoint returns an error. With a plan, the monitor exposes a rolling 5h window plus weekly and monthly limits."
699
828
  }
700
829
  ];
701
830
  var MEASURED_UNITS = new Set(["tokens", "requests"]);
package/dist/tui.js CHANGED
@@ -274,12 +274,39 @@ function parseZai(data) {
274
274
  raw: data
275
275
  };
276
276
  }
277
+ function parseZaiWallet(data) {
278
+ const failed = data.success === false || data.code !== undefined && data.code !== 0 && data.code !== 200;
279
+ if (failed) {
280
+ return { provider: "zai", ok: false, detail: data.msg ?? "wallet endpoint error", windows: [] };
281
+ }
282
+ const balance = data.data?.availableBalance ?? data.data?.rechargeAmount ?? 0;
283
+ const spend = data.data?.totalSpendAmount ?? 0;
284
+ const windows = [
285
+ { label: "balance", percentUsed: 0, detail: `$${balance.toFixed(2)}` },
286
+ { label: "spend", percentUsed: 0, detail: `$${spend.toFixed(2)}` }
287
+ ];
288
+ const today = data.data?.todaySpendAmount;
289
+ if (typeof today === "number") {
290
+ windows.push({ label: "today", percentUsed: 0, detail: `$${today.toFixed(2)}` });
291
+ }
292
+ return {
293
+ provider: "zai",
294
+ ok: true,
295
+ detail: `balance $${balance.toFixed(2)}`,
296
+ windows,
297
+ raw: data
298
+ };
299
+ }
300
+ var QUOTA_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
301
+ var WALLET_URL = "https://api.z.ai/api/biz/account/query-customer-account-report";
277
302
  async function fetchZai(key) {
278
303
  try {
279
- const data = await getJson("https://api.z.ai/api/monitor/usage/quota/limit", {
280
- Authorization: `Bearer ${key}`,
281
- Accept: "application/json"
282
- });
304
+ const auth = { Authorization: `Bearer ${key}`, Accept: "application/json" };
305
+ const data = await getJson(QUOTA_URL, auth);
306
+ if (data.code !== undefined && data.code !== 0) {
307
+ const wallet = await getJson(WALLET_URL, auth);
308
+ return parseZaiWallet(wallet);
309
+ }
283
310
  return parseZai(data);
284
311
  } catch (err) {
285
312
  return { provider: "zai", ok: false, detail: err.message, windows: [] };
@@ -318,6 +345,88 @@ async function fetchAmd(key) {
318
345
  }
319
346
  }
320
347
 
348
+ // packages/core/src/quota/orcarouter.ts
349
+ var CENTS_PER_USD = 100;
350
+ var NO_CAP = 1e6;
351
+ function parseOrcaRouter(usage, subscription) {
352
+ const spent = (usage.total_usage ?? 0) / CENTS_PER_USD;
353
+ const hard = subscription.hard_limit_usd ?? 0;
354
+ const soft = subscription.soft_limit_usd ?? 0;
355
+ const cap = hard > 0 && hard < NO_CAP ? hard : soft > 0 && soft < NO_CAP ? soft : 0;
356
+ const capped = cap > 0;
357
+ const pct = capped ? spent / cap * 100 : 0;
358
+ const hint = capped ? "" : " (no cap set)";
359
+ const detail = `$${spent.toFixed(2)} spent${hint}`;
360
+ return {
361
+ provider: "orcarouter",
362
+ ok: true,
363
+ detail,
364
+ windows: [{ label: "spend", percentUsed: pct, detail: `$${spent.toFixed(2)}${hint}` }],
365
+ budget: capped ? { percentUsed: pct, label: `$${cap}` } : undefined,
366
+ raw: { usage, subscription }
367
+ };
368
+ }
369
+ async function fetchOrcaRouter(key) {
370
+ try {
371
+ const headers = { Authorization: `Bearer ${key}`, Accept: "application/json" };
372
+ const usage = await getJson("https://api.orcarouter.ai/v1/dashboard/billing/usage", headers);
373
+ const subscription = await getJson("https://api.orcarouter.ai/v1/dashboard/billing/subscription", headers);
374
+ return parseOrcaRouter(usage, subscription);
375
+ } catch (err) {
376
+ return { provider: "orcarouter", ok: false, detail: err.message, windows: [] };
377
+ }
378
+ }
379
+
380
+ // packages/core/src/quota/snowflake.ts
381
+ var SQL_API = (account) => `https://${account}.snowflakecomputing.com/api/v2/statements`;
382
+ var STATEMENT = `SELECT
383
+ SUM(CASE WHEN START_TIME >= DATE_TRUNC('day', CURRENT_TIMESTAMP())
384
+ AND START_TIME < DATEADD('day', 1, DATE_TRUNC('day', CURRENT_TIMESTAMP()))
385
+ THEN TOKENS ELSE 0 END) AS today_tokens,
386
+ SUM(TOKENS) AS month_tokens
387
+ FROM SNOWFLAKE.ACCOUNT_USAGE.CORTEX_REST_API_USAGE_HISTORY
388
+ WHERE START_TIME >= DATEADD('day', -30, CURRENT_TIMESTAMP())`;
389
+ var fmtTokens = (n) => n.toLocaleString("en-US");
390
+ function parseSnowflake(row) {
391
+ const today = Number(row?.[0] ?? 0);
392
+ const month = Number(row?.[1] ?? 0);
393
+ return {
394
+ provider: "snowflake-cortex",
395
+ ok: true,
396
+ detail: `${fmtTokens(month)} tokens/30d`,
397
+ windows: [
398
+ { label: "tokens/mo", percentUsed: 0, detail: `${fmtTokens(month)}` },
399
+ { label: "today", percentUsed: 0, detail: `${fmtTokens(today)}` }
400
+ ],
401
+ raw: { row }
402
+ };
403
+ }
404
+ async function fetchSnowflake(key, metadata) {
405
+ const account = String(metadata?.account ?? "").toUpperCase();
406
+ if (!account) {
407
+ return { provider: "snowflake-cortex", ok: false, detail: "metadata.account is required", windows: [] };
408
+ }
409
+ try {
410
+ const res = await fetch(SQL_API(account), {
411
+ method: "POST",
412
+ headers: {
413
+ Authorization: `Bearer ${key}`,
414
+ "Content-Type": "application/json",
415
+ Accept: "application/json"
416
+ },
417
+ body: JSON.stringify({ statement: STATEMENT, timeout: 30 })
418
+ });
419
+ const body = await res.json().catch(() => null);
420
+ if (!res.ok) {
421
+ return { provider: "snowflake-cortex", ok: false, detail: body?.message ?? `HTTP ${res.status}`, windows: [] };
422
+ }
423
+ const rows = Array.isArray(body?.data) ? body.data : [];
424
+ return parseSnowflake(rows[0]);
425
+ } catch (err) {
426
+ return { provider: "snowflake-cortex", ok: false, detail: err.message, windows: [] };
427
+ }
428
+ }
429
+
321
430
  // packages/core/src/providers.ts
322
431
  var TTL_MS = 15 * 60 * 1000;
323
432
  async function readCache() {
@@ -340,7 +449,9 @@ var FETCHERS = {
340
449
  openrouter: fetchOpenRouter,
341
450
  "github-copilot": fetchCopilot,
342
451
  zai: fetchZai,
343
- amd: fetchAmd
452
+ amd: fetchAmd,
453
+ orcarouter: fetchOrcaRouter,
454
+ "snowflake-cortex": fetchSnowflake
344
455
  };
345
456
  async function providerStatuses(opts) {
346
457
  const auth = await readAuth();
@@ -361,7 +472,7 @@ async function providerStatuses(opts) {
361
472
  const secret = authSecret(auth[p]);
362
473
  if (!fetcher || !secret)
363
474
  return null;
364
- return fetcher(secret);
475
+ return fetcher(secret, auth[p].metadata);
365
476
  }));
366
477
  for (const q of live) {
367
478
  if (q)
@@ -403,10 +514,10 @@ var PROVIDER_LIMITS = [
403
514
  provider: "cloudflare-workers-ai",
404
515
  unit: "neurons",
405
516
  period: "day",
406
- limit: 1e5,
517
+ limit: 1e4,
407
518
  tier: "free",
408
- source: "https://developers.cloudflare.com/workers-ai/platform/limits/",
409
- note: "A neuron is Cloudflare's own unit, per model. Not measurable from tokens."
519
+ source: "https://developers.cloudflare.com/workers-ai/platform/pricing/",
520
+ note: "10,000 neurons/day free, resets at 00:00 UTC. A neuron is Cloudflare's own unit, per model. Live usage needs an API token with the Analytics read scope; the Workers AI key does not carry one."
410
521
  },
411
522
  {
412
523
  provider: "digitalocean",
@@ -439,10 +550,19 @@ var PROVIDER_LIMITS = [
439
550
  provider: "nvidia",
440
551
  unit: "credits",
441
552
  period: "month",
442
- limit: 1000,
553
+ limit: 0,
443
554
  tier: "free",
444
- source: "https://build.nvidia.com/",
445
- note: "NIM free tier ~1000 credits/month. A credit varies by model."
555
+ source: "https://forums.developer.nvidia.com/t/request-more-4-000-credits-option-on-build-nvidia-com/344567",
556
+ note: "The credit allowance was retired; build.nvidia.com now rate-limits the trial per model (about 40 RPM, unpublished, shown in the UI header). No usage endpoint exists."
557
+ },
558
+ {
559
+ provider: "opencode",
560
+ unit: "tokens",
561
+ period: "month",
562
+ limit: 0,
563
+ tier: "unknown",
564
+ source: "https://opencode.ai/docs/go/",
565
+ note: "The opencode gateway exposes no spend API to its API key; the dashboard balance needs a browser session. Go usage is measured through the opencode-go provider instead."
446
566
  },
447
567
  {
448
568
  provider: "orcarouter",
@@ -450,8 +570,8 @@ var PROVIDER_LIMITS = [
450
570
  period: "day",
451
571
  limit: 0,
452
572
  tier: "unknown",
453
- source: "unknown",
454
- note: "Proxy service. No public limits documented."
573
+ source: "https://docs.orcarouter.ai/operations/billing-and-usage",
574
+ note: "Pay-per-token at upstream rates with no markup. A per-key credit cap turns into a live budget; without one there is nothing to exhaust. Free models are rate-limited with unpublished numbers."
455
575
  },
456
576
  {
457
577
  provider: "snowflake-cortex",
@@ -459,8 +579,17 @@ var PROVIDER_LIMITS = [
459
579
  period: "month",
460
580
  limit: 100,
461
581
  tier: "paid",
462
- source: "https://docs.snowflake.com/en/user-guide/snowflake-cortex",
463
- note: "Credits follow warehouse time, not tokens. Not measurable from usage."
582
+ source: "https://docs.snowflake.com/en/sql-reference/account-usage/cortex_ai_functions_usage_history",
583
+ note: "Billed per token (Service Consumption Table). Live tokens come from ACCOUNT_USAGE.CORTEX_REST_API_USAGE_HISTORY through the SQL API with the stored JWT; the percentage needs a snowflake-cortex line in budgets.json."
584
+ },
585
+ {
586
+ provider: "zai",
587
+ unit: "tokens",
588
+ period: "day",
589
+ limit: 0,
590
+ tier: "unknown",
591
+ source: "https://openusage.sh/docs/providers/zai/",
592
+ note: "The quota monitor answers only keys with an active GLM coding plan; this key reports none, so the endpoint returns an error. With a plan, the monitor exposes a rolling 5h window plus weekly and monthly limits."
464
593
  }
465
594
  ];
466
595
  var MEASURED_UNITS = new Set(["tokens", "requests"]);
@@ -606,7 +735,7 @@ function columnsFor(inner) {
606
735
  const budget = Math.max(BUDGET_MIN, inner - used);
607
736
  return { cols: [...base, { title: "BUDGET", width: budget, align: "left" }], bar: wide ? WIDE_BAR : COMPACT_BAR };
608
737
  }
609
- function fmtTokens(n) {
738
+ function fmtTokens2(n) {
610
739
  if (n >= 1e6)
611
740
  return `${(n / 1e6).toFixed(1)}M`;
612
741
  if (n >= 1000)
@@ -772,8 +901,8 @@ function leadCells(cols, r) {
772
901
  return row(cols, [
773
902
  r.provider,
774
903
  String(r.messages),
775
- fmtTokens(r.tokensInput),
776
- fmtTokens(r.tokensOutput),
904
+ fmtTokens2(r.tokensInput),
905
+ fmtTokens2(r.tokensOutput),
777
906
  `$${r.cost.toFixed(2)}`
778
907
  ]).trimEnd().padEnd(upToBudget) + " ";
779
908
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1930dev/opencode-usage",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Usage tracking, budget percentages and model ranking for opencode — CLI plus a /usage TUI plugin",
5
5
  "type": "module",
6
6
  "bin": {