tokentracker-cli 0.88.6 → 0.88.7

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 (94) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-CgVttPJy.js → AchievementBadge-m7oaBLpH.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-2MeyWMmO.js → AchievementsPage-BHHh4VjM.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-hZGNcWfs.js → AchievementsSection-CGLNe_1p.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-DBMexTtI.js → BadgeDetailModal-v1DGF6EM.js} +1 -1
  5. package/dashboard/dist/assets/{Card-Ca6NJlW2.js → Card-C16SqIR8.js} +1 -1
  6. package/dashboard/dist/assets/{ClawdAnimated-D5GOjZtF.js → ClawdAnimated-Bj3ptLJv.js} +1 -1
  7. package/dashboard/dist/assets/{CommandPalette-CPLhHuIS.js → CommandPalette-BaegDLAM.js} +1 -1
  8. package/dashboard/dist/assets/{CommunityStatsModal-BFyZtlgh.js → CommunityStatsModal-CThxVFG0.js} +1 -1
  9. package/dashboard/dist/assets/{ConfirmModal-DzpUyVbg.js → ConfirmModal-C0sP3Ktk.js} +1 -1
  10. package/dashboard/dist/assets/{Controls-CIPVemCR.js → Controls-C8usK6RW.js} +1 -1
  11. package/dashboard/dist/assets/{DashboardPage-h2LfrNwp.js → DashboardPage-DbzbT_cY.js} +1 -1
  12. package/dashboard/dist/assets/{DevicePage-CORcVYoS.js → DevicePage-Cu4qqRcB.js} +1 -1
  13. package/dashboard/dist/assets/{DialogClose-BMizP3FN.js → DialogClose-DVo3jJb6.js} +1 -1
  14. package/dashboard/dist/assets/DialogDescription-zf3KGPP3.js +1 -0
  15. package/dashboard/dist/assets/{DialogTitle-CAlyWmQy.js → DialogTitle-C1UOMgTZ.js} +1 -1
  16. package/dashboard/dist/assets/{FadeIn-R8uHDC1n.js → FadeIn-CuQlQrvO.js} +1 -1
  17. package/dashboard/dist/assets/{HeaderGithubStar-D-H9BYBq.js → HeaderGithubStar-BTt2612H.js} +1 -1
  18. package/dashboard/dist/assets/{HoverTooltip-C6D0Qf3J.js → HoverTooltip-CHdLolcf.js} +1 -1
  19. package/dashboard/dist/assets/{IpCheckPage-BDKN_XOv.js → IpCheckPage-BFz8hr_3.js} +1 -1
  20. package/dashboard/dist/assets/{LandingPage-CVS7Gi16.js → LandingPage-CTyNSAYl.js} +2 -2
  21. package/dashboard/dist/assets/{LeaderboardAvatar-By9ZRxCx.js → LeaderboardAvatar-CBdOXZWk.js} +1 -1
  22. package/dashboard/dist/assets/{LeaderboardPage-B2FWLDbM.js → LeaderboardPage-DStx51Bj.js} +2 -2
  23. package/dashboard/dist/assets/{LeaderboardProfileModal-bKfAVxsg.js → LeaderboardProfileModal-CtxgJR07.js} +3 -3
  24. package/dashboard/dist/assets/{LeaderboardProfilePage-NSZpmCOS.js → LeaderboardProfilePage-DrpsHTdZ.js} +1 -1
  25. package/dashboard/dist/assets/{LimitsPage-DEeKWZ3u.js → LimitsPage-m_itA-7d.js} +1 -1
  26. package/dashboard/dist/assets/{LocalOnlyNotice-CJRoGtCr.js → LocalOnlyNotice-A62wFwYT.js} +1 -1
  27. package/dashboard/dist/assets/{LoginCard-DsotnrVc.js → LoginCard-DFJugNa-.js} +1 -1
  28. package/dashboard/dist/assets/{LoginModal-CBzSJfD-.js → LoginModal-DF4BOwS6.js} +1 -1
  29. package/dashboard/dist/assets/{LoginPage-Dr7dP5R6.js → LoginPage-BxFhfitr.js} +1 -1
  30. package/dashboard/dist/assets/{PetPage-D_FQtbbK.js → PetPage-l4fkRsf1.js} +1 -1
  31. package/dashboard/dist/assets/{PopoverPopup-BBIGg4i-.js → PopoverPopup-y-OAHy4v.js} +1 -1
  32. package/dashboard/dist/assets/{ProviderIcon-C6NT8xCP.js → ProviderIcon-DwoPHz27.js} +1 -1
  33. package/dashboard/dist/assets/{ResetPasswordPage-BbDZdmyR.js → ResetPasswordPage-CCDj94rl.js} +1 -1
  34. package/dashboard/dist/assets/{SegmentedControl-C0YbTO7C.js → SegmentedControl-lEKPzZlV.js} +1 -1
  35. package/dashboard/dist/assets/{Select-CO90ysTb.js → Select-DoyUBoxn.js} +1 -1
  36. package/dashboard/dist/assets/{SelectItemText-CI5GNKhv.js → SelectItemText-K8Vzn1Dd.js} +1 -1
  37. package/dashboard/dist/assets/{ServiceStatusPage-CV71dVHd.js → ServiceStatusPage-Cqyo7OtW.js} +1 -1
  38. package/dashboard/dist/assets/{SessionsPage-B5jvyh-X.js → SessionsPage-BELC-8aJ.js} +1 -1
  39. package/dashboard/dist/assets/{SettingsPage-DokT0qhX.js → SettingsPage-C6Og4tMS.js} +1 -1
  40. package/dashboard/dist/assets/{SkillsPage-BYAAiPpI.js → SkillsPage-BKKgpXc_.js} +1 -1
  41. package/dashboard/dist/assets/{TokenGalaxy-m_5kxlC_.js → TokenGalaxy-DoSCQSpY.js} +1 -1
  42. package/dashboard/dist/assets/{ToolbarRootContext-TYR3c0G7.js → ToolbarRootContext-GoQP9LXi.js} +1 -1
  43. package/dashboard/dist/assets/{TrendMonitor-DjVbRbLd.js → TrendMonitor-BHE1KkJ7.js} +1 -1
  44. package/dashboard/dist/assets/{WidgetsPage-DwImvB2Y.js → WidgetsPage-CHBzNb19.js} +1 -1
  45. package/dashboard/dist/assets/{WrappedPage-CEcqtP_W.js → WrappedPage-CW_kqVoY.js} +1 -1
  46. package/dashboard/dist/assets/{__vite-browser-external-BOpcJ7Bn.js → __vite-browser-external-BgeLzJ0q.js} +2 -2
  47. package/dashboard/dist/assets/{agent-logos-Cwc0bFNx.js → agent-logos-BWeWRlms.js} +1 -1
  48. package/dashboard/dist/assets/{arrow-up-right-C0nGIX35.js → arrow-up-right-5GGSSEKW.js} +1 -1
  49. package/dashboard/dist/assets/{check-Cpk8oXcn.js → check-g6Fq4TSB.js} +1 -1
  50. package/dashboard/dist/assets/{chevron-right-DsKtZU0a.js → chevron-right-DjPuavEP.js} +1 -1
  51. package/dashboard/dist/assets/{copy-COeB2IQx.js → copy-BAMl60zT.js} +1 -1
  52. package/dashboard/dist/assets/{download-Dvw5E6xA.js → download-BQPhL4h5.js} +1 -1
  53. package/dashboard/dist/assets/{external-link-COe8flh4.js → external-link-CU3XKMbZ.js} +1 -1
  54. package/dashboard/dist/assets/{flame-Dza_-WNE.js → flame-B85YY_rB.js} +1 -1
  55. package/dashboard/dist/assets/{format-tokens-CKDbxlqC.js → format-tokens-DDxlnhNV.js} +1 -1
  56. package/dashboard/dist/assets/{icons-Dj3nlViF.js → icons-D1e1TWTe.js} +1 -1
  57. package/dashboard/dist/assets/{index-CBgWOlov.js → index-BZ2hkHKk.js} +1 -1
  58. package/dashboard/dist/assets/{index-DvR1xY_C.js → index-CWMkVmon.js} +1 -1
  59. package/dashboard/dist/assets/{index-DCpYik68.js → index-sQl-P7u5.js} +1 -1
  60. package/dashboard/dist/assets/{info-B9M6Luq-.js → info-DlSYpCFm.js} +1 -1
  61. package/dashboard/dist/assets/{limits-providers-CAZ_eIVT.js → limits-providers-DS5EIbJK.js} +1 -1
  62. package/dashboard/dist/assets/{link-2-PL-NJZyN.js → link-2-DbEVoNk-.js} +1 -1
  63. package/dashboard/dist/assets/{loader-circle-CQ-akiLL.js → loader-circle-CqmzGIX-.js} +1 -1
  64. package/dashboard/dist/assets/{maximize-2-9EZoXvlF.js → maximize-2-Ce8X8kHQ.js} +1 -1
  65. package/dashboard/dist/assets/{modifiers.esm-Bc1a9Bqm.js → modifiers.esm-BQNRpVcA.js} +1 -1
  66. package/dashboard/dist/assets/{monitor-smartphone-DR9MHu8R.js → monitor-smartphone-8drugZmh.js} +1 -1
  67. package/dashboard/dist/assets/{provider-display-DAH8KAyP.js → provider-display-BayTojFu.js} +1 -1
  68. package/dashboard/dist/assets/{react-gkeEFJtr.js → react-BfgySeLu.js} +1 -1
  69. package/dashboard/dist/assets/{search-B67Xwb-N.js → search-BkGUxlXI.js} +1 -1
  70. package/dashboard/dist/assets/{skills-api-aZca3hZV.js → skills-api-D80JxFc1.js} +1 -1
  71. package/dashboard/dist/assets/{sortable.esm-Crm57xTH.js → sortable.esm-SjRQ4Ehw.js} +1 -1
  72. package/dashboard/dist/assets/{store-DisKKZeI.js → store-B3WkWBgn.js} +1 -1
  73. package/dashboard/dist/assets/{terminal-DGueqM2Q.js → terminal-CIvEvMau.js} +1 -1
  74. package/dashboard/dist/assets/{trash-2-b5d9XdRV.js → trash-2-BZclE0mU.js} +1 -1
  75. package/dashboard/dist/assets/{use-community-stats-DOPcq80h.js → use-community-stats-ChaFzJoW.js} +1 -1
  76. package/dashboard/dist/assets/{use-limits-display-prefs-DGfPkvee.js → use-limits-display-prefs-2LALdqvT.js} +1 -1
  77. package/dashboard/dist/assets/{use-ordered-list-Dyul6pNM.js → use-ordered-list-10Nmxw1I.js} +1 -1
  78. package/dashboard/dist/assets/use-reduced-motion-J12KnTgP.js +1 -0
  79. package/dashboard/dist/assets/{use-session-efficiency-pref-BPzM7u7L.js → use-session-efficiency-pref-CqPK4jX1.js} +1 -1
  80. package/dashboard/dist/assets/{use-transform-C5yhWdHM.js → use-transform-s6og1tg9.js} +1 -1
  81. package/dashboard/dist/assets/{use-usage-limits-C6S69l9t.js → use-usage-limits-jyRJ_kl9.js} +1 -1
  82. package/dashboard/dist/assets/{useCurrency-BfJWbenA.js → useCurrency-hZfdhfqK.js} +1 -1
  83. package/dashboard/dist/assets/{useScrollLock-Btq-Jtjh.js → useScrollLock-DFF4vpw2.js} +1 -1
  84. package/dashboard/dist/assets/{useTokenFormat-DaXiI8qf.js → useTokenFormat-DaVdoRvh.js} +1 -1
  85. package/dashboard/dist/assets/{zap-DMOIXlpH.js → zap-B1XMdyCV.js} +1 -1
  86. package/dashboard/dist/index.html +1 -1
  87. package/dashboard/dist/ip-check.html +1 -1
  88. package/dashboard/dist/leaderboard.html +1 -1
  89. package/dashboard/dist/share.html +1 -1
  90. package/package.json +1 -1
  91. package/src/lib/opencode-go-limits.js +122 -13
  92. package/src/lib/pricing/seed-snapshot.json +1 -1
  93. package/dashboard/dist/assets/DialogDescription-B_xemJjT.js +0 -1
  94. package/dashboard/dist/assets/use-reduced-motion-UUonF8mY.js +0 -1
@@ -5,23 +5,27 @@ const { readSqliteJsonRowsAsync } = require("./sqlite-reader");
5
5
 
6
6
  // OpenCode Go usage limits.
7
7
  //
8
- // Two data sources are available:
8
+ // Three data sources are available, in priority order:
9
9
  //
10
- // 1. Web scrape of the workspace dashboard
11
- // (https://opencode.ai/workspace/<id>/go) for the server's authoritative
12
- // rolling (5h) / weekly / monthly usagePercent and subscription state.
13
- // The cookie is sent as `Cookie: auth=<OPENCODE_GO_AUTH_COOKIE>` per
14
- // slkiser/opencode-quota#41.
10
+ // 1. The official OpenCode Go usage API
11
+ // (GET https://opencode.ai/zen/go/v1/usage) with
12
+ // `Authorization: Bearer <OPENCODE_GO_API_KEY>`. It returns the server's
13
+ // authoritative rolling (5h), weekly, and monthly usage windows.
15
14
  //
16
- // 2. Local opencode.db cost aggregation, explicitly enabled with
15
+ // 2. Web scrape of the workspace dashboard
16
+ // (https://opencode.ai/workspace/<id>/go) for existing users configured
17
+ // with OPENCODE_GO_AUTH_COOKIE. This is retained as a compatibility path.
18
+ //
19
+ // 3. Local opencode.db cost aggregation, explicitly enabled with
17
20
  // TOKENTRACKER_OPENCODE_GO_LOCAL_ESTIMATE=1. The `opencode` CLI records
18
21
  // every Go turn's USD `cost` in SQLite, so cost ÷ Go's dollar caps is a
19
22
  // useful local estimate. It cannot prove that the account still has an
20
23
  // active Go subscription, because the database contains historical turns
21
24
  // but no current entitlement state.
22
25
  //
23
- // The returned shape is shared by both sources. `source` is `'web'` for
24
- // authoritative dashboard data or `'local-estimate'` for the opt-in estimate.
26
+ // The returned shape is shared by all sources. `source` is `'api'` for the
27
+ // official endpoint, `'web'` for the compatibility scrape, or `'local-estimate'`
28
+ // for the opt-in estimate.
25
29
 
26
30
  const SCRAPED_NUMBER_PATTERN = "([0-9]+(?:\\.[0-9]+)?)";
27
31
 
@@ -38,11 +42,12 @@ function windowFieldRegex(windowKey, field) {
38
42
  return new RegExp(`${windowKey}[^}]*?${field}"?\\s*[:=]+\\s*${SCRAPED_NUMBER_PATTERN}`);
39
43
  }
40
44
 
45
+ const GO_USAGE_API_URL = "https://opencode.ai/zen/go/v1/usage";
41
46
  const DASHBOARD_URL_PREFIX = "https://opencode.ai/workspace/";
42
47
  const DASHBOARD_URL_SUFFIX = "/go";
43
48
  const USER_AGENT =
44
49
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Gecko/20100101 Firefox/148.0";
45
- const DEFAULT_SCRAPE_TIMEOUT_MS = 10_000;
50
+ const DEFAULT_REQUEST_TIMEOUT_MS = 10_000;
46
51
  const LOCAL_ESTIMATE_ENV = "TOKENTRACKER_OPENCODE_GO_LOCAL_ESTIMATE";
47
52
 
48
53
  function isTruthyEnvValue(value) {
@@ -78,6 +83,13 @@ function readConfig(env = process.env) {
78
83
  return { workspaceId, authCookie };
79
84
  }
80
85
 
86
+ function readApiKey(env = process.env) {
87
+ if (!env || typeof env !== "object") return "";
88
+ return typeof env.OPENCODE_GO_API_KEY === "string"
89
+ ? env.OPENCODE_GO_API_KEY.trim()
90
+ : "";
91
+ }
92
+
81
93
  function clampPercent(value) {
82
94
  if (value === null || value === undefined || value === "") return null;
83
95
  let n = Number(value);
@@ -486,6 +498,84 @@ function localGoResult(local) {
486
498
  };
487
499
  }
488
500
 
501
+ // --- Official usage API (exact server-side usage, API-key-gated) -------------
502
+
503
+ async function fetchOpencodeGoApiLimits({ apiKey, fetchImpl, nowMs, timeoutMs }) {
504
+ let response;
505
+ try {
506
+ response = await withTimeout(fetchImpl, timeoutMs)(GO_USAGE_API_URL, {
507
+ method: "GET",
508
+ headers: {
509
+ Authorization: `Bearer ${apiKey}`,
510
+ Accept: "application/json",
511
+ "User-Agent": USER_AGENT,
512
+ },
513
+ });
514
+ } catch (err) {
515
+ return { configured: true, error: sanitizeMessage(err?.message || err) };
516
+ }
517
+
518
+ if (response.status === 401) {
519
+ return {
520
+ configured: true,
521
+ error: "OpenCode Go API key is missing, invalid, expired, or not entitled to an OpenCode Go subscription.",
522
+ auth_error: true,
523
+ };
524
+ }
525
+ if (response.status === 403) {
526
+ return {
527
+ configured: true,
528
+ error: "OpenCode Go subscription required for this API key.",
529
+ auth_error: true,
530
+ };
531
+ }
532
+ if (!response.ok) {
533
+ return { configured: true, error: `OpenCode Go usage API error ${response.status}` };
534
+ }
535
+
536
+ let payload;
537
+ try {
538
+ payload = JSON.parse(await response.text());
539
+ } catch (err) {
540
+ return {
541
+ configured: true,
542
+ error: `Could not parse OpenCode Go usage API response: ${sanitizeMessage(err?.message || err)}`,
543
+ };
544
+ }
545
+
546
+ const rolling = buildWindow({
547
+ usagePercent: payload?.rollingUsage?.usagePercent,
548
+ resetInSec: payload?.rollingUsage?.resetInSec,
549
+ nowMs,
550
+ });
551
+ const weekly = buildWindow({
552
+ usagePercent: payload?.weeklyUsage?.usagePercent,
553
+ resetInSec: payload?.weeklyUsage?.resetInSec,
554
+ nowMs,
555
+ });
556
+ const monthly = buildWindow({
557
+ usagePercent: payload?.monthlyUsage?.usagePercent,
558
+ resetInSec: payload?.monthlyUsage?.resetInSec,
559
+ nowMs,
560
+ });
561
+
562
+ if (!rolling && !weekly && !monthly) {
563
+ return {
564
+ configured: true,
565
+ error: "OpenCode Go usage API response did not include any known usage windows.",
566
+ };
567
+ }
568
+
569
+ return {
570
+ configured: true,
571
+ error: null,
572
+ subscription_status: "active",
573
+ primary_window: rolling,
574
+ secondary_window: weekly,
575
+ tertiary_window: monthly,
576
+ };
577
+ }
578
+
489
579
  // --- Web scrape (exact server-side usage, cookie-gated) ---------------------
490
580
 
491
581
  async function scrapeOpencodeGoWeb({ cfg, fetchImpl, nowMs, timeoutMs }) {
@@ -571,14 +661,31 @@ async function fetchOpencodeGoLimits({
571
661
  env = process.env,
572
662
  fetchImpl = fetch,
573
663
  nowMs = Date.now(),
574
- timeoutMs = DEFAULT_SCRAPE_TIMEOUT_MS,
664
+ timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS,
575
665
  sqliteOptions = {},
576
666
  } = {}) {
667
+ const apiKey = readApiKey(env);
577
668
  const cfg = readConfig(env);
578
669
  const allowLocalEstimate = localEstimateEnabled(env);
579
670
 
580
- // A configured cookie gives us the only authoritative subscription state.
581
- // Never replace an explicit inactive page with historical local usage.
671
+ // Prefer the documented API whenever an API key is explicitly configured.
672
+ // An authentication failure is actionable and must not be hidden by silently
673
+ // falling back to a possibly stale cookie. For a transient API failure, an
674
+ // existing cookie remains a compatibility fallback.
675
+ if (apiKey) {
676
+ const api = await fetchOpencodeGoApiLimits({ apiKey, fetchImpl, nowMs, timeoutMs });
677
+ if (!api?.error && (api.primary_window || api.secondary_window || api.tertiary_window)) {
678
+ return { ...api, source: "api" };
679
+ }
680
+ if (api?.auth_error) {
681
+ const { auth_error, ...result } = api;
682
+ return result;
683
+ }
684
+ if (!cfg) return api || { configured: true, error: "OpenCode Go unavailable" };
685
+ }
686
+
687
+ // Keep the cookie-backed dashboard path for existing configurations and as a
688
+ // fallback when the official API is temporarily unavailable.
582
689
  if (cfg) {
583
690
  const web = await scrapeOpencodeGoWeb({ cfg, fetchImpl, nowMs, timeoutMs });
584
691
  if (web?.subscription_status === "inactive") return web;
@@ -602,7 +709,9 @@ async function fetchOpencodeGoLimits({
602
709
 
603
710
  module.exports = {
604
711
  fetchOpencodeGoLimits,
712
+ fetchOpencodeGoApiLimits,
605
713
  readConfig,
714
+ readApiKey,
606
715
  localEstimateEnabled,
607
716
  detectSubscriptionStatus,
608
717
  extractWindows,