tokentracker-cli 0.84.6 → 0.84.8

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 (118) hide show
  1. package/README.de.md +10 -8
  2. package/README.ja.md +9 -7
  3. package/README.ko.md +9 -7
  4. package/README.md +12 -10
  5. package/README.zh-CN.md +9 -7
  6. package/dashboard/dist/assets/{AchievementBadge-CKIvD3lG.js → AchievementBadge-DAAenS6A.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-CZWCUiS5.js → AchievementsPage-DJ9GMKxL.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-BSk_fR8P.js → AchievementsSection-DtpLrh2Q.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-CjHkcmuq.js → BadgeDetailModal-CkE1DwdN.js} +1 -1
  10. package/dashboard/dist/assets/Card-Cs2vVMHG.js +1 -0
  11. package/dashboard/dist/assets/{ClawdAnimated-Cl6gmF7u.js → ClawdAnimated-BCSNeob2.js} +1 -1
  12. package/dashboard/dist/assets/{CommandPalette-B0rcodcF.js → CommandPalette-CFDTjp76.js} +1 -1
  13. package/dashboard/dist/assets/{CommunityStatsModal-BtlTJbf4.js → CommunityStatsModal-D2oJpI9i.js} +1 -1
  14. package/dashboard/dist/assets/{ConfirmModal-tJCWVZQH.js → ConfirmModal-D0a_xdwF.js} +1 -1
  15. package/dashboard/dist/assets/{DashboardPage-Dtxq8dzI.js → DashboardPage-CMCqvpkA.js} +1 -1
  16. package/dashboard/dist/assets/{DevicePage-9USuEsRu.js → DevicePage-c9Fk13gJ.js} +1 -1
  17. package/dashboard/dist/assets/{DialogClose-DxHdqMcl.js → DialogClose-jsfDvF2n.js} +1 -1
  18. package/dashboard/dist/assets/DialogDescription-Dwxj5h5w.js +1 -0
  19. package/dashboard/dist/assets/{DialogTitle-C1ZeQgf2.js → DialogTitle-Dt835L7T.js} +1 -1
  20. package/dashboard/dist/assets/{FadeIn-Cx4jLl9G.js → FadeIn-DJssGrj8.js} +1 -1
  21. package/dashboard/dist/assets/{HeaderGithubStar-BOyuGkoA.js → HeaderGithubStar-B9MwwA-P.js} +1 -1
  22. package/dashboard/dist/assets/{HoverTooltip-BDhqMR3L.js → HoverTooltip-DPLMJkai.js} +1 -1
  23. package/dashboard/dist/assets/{IpCheckPage-DTvA9Wmx.js → IpCheckPage-BXKjxEHe.js} +1 -1
  24. package/dashboard/dist/assets/LandingPage-BJNMQTec.js +2 -0
  25. package/dashboard/dist/assets/{LeaderboardAvatar-3ibNg6If.js → LeaderboardAvatar-BNwbnOa3.js} +1 -1
  26. package/dashboard/dist/assets/LeaderboardPage-BOxejyMy.js +2 -0
  27. package/dashboard/dist/assets/{LeaderboardProfileModal-DeGbJtvi.js → LeaderboardProfileModal-BEqUZCOD.js} +4 -4
  28. package/dashboard/dist/assets/{LeaderboardProfilePage-DEPzO6hW.js → LeaderboardProfilePage-CUDV5iyh.js} +1 -1
  29. package/dashboard/dist/assets/LimitsPage-ejlJN1vi.js +4 -0
  30. package/dashboard/dist/assets/{LocalOnlyNotice-C76LAT_3.js → LocalOnlyNotice-B3BSeCHd.js} +1 -1
  31. package/dashboard/dist/assets/{LoginCard-DEyCJT67.js → LoginCard-2mAb696j.js} +1 -1
  32. package/dashboard/dist/assets/{LoginModal-CueMQgAZ.js → LoginModal-cacKhc6U.js} +1 -1
  33. package/dashboard/dist/assets/{LoginPage-CwqBb5Fd.js → LoginPage-rnBlie8p.js} +1 -1
  34. package/dashboard/dist/assets/PetPage-D3DhuS6O.js +1 -0
  35. package/dashboard/dist/assets/{PopoverPopup-Dt9VY2VQ.js → PopoverPopup-CxATX7M9.js} +1 -1
  36. package/dashboard/dist/assets/{ProviderIcon-CHx4ohfx.js → ProviderIcon-B5b9LkmM.js} +1 -1
  37. package/dashboard/dist/assets/{ResetPasswordPage-DB6R7fjf.js → ResetPasswordPage-BGKmQ_JW.js} +1 -1
  38. package/dashboard/dist/assets/{SegmentedControl-BINOpXIY.js → SegmentedControl-BMeYCm2b.js} +1 -1
  39. package/dashboard/dist/assets/{Select-DcYXcsKv.js → Select-CYHXq8im.js} +1 -1
  40. package/dashboard/dist/assets/{SelectItemText-BDv2Kb3x.js → SelectItemText-DvI4i06h.js} +1 -1
  41. package/dashboard/dist/assets/ServiceStatusPage-dv26-VvI.js +1 -0
  42. package/dashboard/dist/assets/{SessionsPage-cL_w0ytB.js → SessionsPage-DBQruqkO.js} +2 -2
  43. package/dashboard/dist/assets/SettingsPage-BWRbQ3Iq.js +1 -0
  44. package/dashboard/dist/assets/SkillsPage-BonhKgAt.js +1 -0
  45. package/dashboard/dist/assets/{TokenGalaxy-BHq4YSv2.js → TokenGalaxy-BBUD1kpU.js} +1 -1
  46. package/dashboard/dist/assets/{ToolbarRootContext-akXaaKb6.js → ToolbarRootContext-DPMm8uYz.js} +1 -1
  47. package/dashboard/dist/assets/{TrendMonitor-DPhFXzQc.js → TrendMonitor-CZVE6gID.js} +1 -1
  48. package/dashboard/dist/assets/{WidgetsPage-Nf0UEz7s.js → WidgetsPage-Bz9cieeZ.js} +1 -1
  49. package/dashboard/dist/assets/{WrappedPage-D4zOSsqD.js → WrappedPage-CruMolZd.js} +1 -1
  50. package/dashboard/dist/assets/__vite-browser-external-C3VH6nS4.js +1483 -0
  51. package/dashboard/dist/assets/__vite-browser-external-hXV_ZYTG.css +1 -0
  52. package/dashboard/dist/assets/{agent-logos-Bja3VCAa.js → agent-logos-HIeYwIqN.js} +1 -1
  53. package/dashboard/dist/assets/{arrow-up-right-CzcCNJJ2.js → arrow-up-right-n7Bi4Tvd.js} +1 -1
  54. package/dashboard/dist/assets/{check-DUHSZCYn.js → check-C5Q44G5k.js} +1 -1
  55. package/dashboard/dist/assets/{chevron-right-jLvhaNUy.js → chevron-right-BsnptUU0.js} +1 -1
  56. package/dashboard/dist/assets/{copy-ChyH-ynx.js → copy-BGuL9uFa.js} +1 -1
  57. package/dashboard/dist/assets/{download-CgwvXrUe.js → download-BljgPwR2.js} +1 -1
  58. package/dashboard/dist/assets/external-link-B4Oz8hUe.js +1 -0
  59. package/dashboard/dist/assets/{flame-ysf1F8uB.js → flame-ChLG_NZE.js} +1 -1
  60. package/dashboard/dist/assets/{format-tokens-DAa2cubl.js → format-tokens-DjuNk13Z.js} +1 -1
  61. package/dashboard/dist/assets/{icons-DccoWQq9.js → icons-CkO42M-u.js} +1 -1
  62. package/dashboard/dist/assets/{index-CAycNPQf.js → index-CEsvYBWY.js} +1 -1
  63. package/dashboard/dist/assets/{index-CxExExXh.js → index-CGxawuWx.js} +1 -1
  64. package/dashboard/dist/assets/{index-kkQrDRat.js → index-D87oE_rV.js} +1 -1
  65. package/dashboard/dist/assets/{info-DWQQIq9M.js → info-et8xtJ4v.js} +1 -1
  66. package/dashboard/dist/assets/limits-providers-vO_86yRy.js +1 -0
  67. package/dashboard/dist/assets/{link-2-CKeVVq4P.js → link-2-Dge2rBkr.js} +1 -1
  68. package/dashboard/dist/assets/{loader-circle-Dw0tu6IN.js → loader-circle-Cs3wpqJe.js} +1 -1
  69. package/dashboard/dist/assets/{maximize-2-BMU0YzEF.js → maximize-2-zn71uZX3.js} +1 -1
  70. package/dashboard/dist/assets/{provider-display-Cf9I-VLS.js → provider-display-CTt6mzLe.js} +1 -1
  71. package/dashboard/dist/assets/{react-BhbcISKy.js → react-BbHDQe6Q.js} +1 -1
  72. package/dashboard/dist/assets/{search-BoY8EW1z.js → search-3CMhatWb.js} +1 -1
  73. package/dashboard/dist/assets/{skills-api-KVH21GJw.js → skills-api-Cm1aiqWL.js} +1 -1
  74. package/dashboard/dist/assets/{store-BL7rSajG.js → store-DclXV77t.js} +1 -1
  75. package/dashboard/dist/assets/{terminal-CeMuITdm.js → terminal-BC7uFcA7.js} +1 -1
  76. package/dashboard/dist/assets/{trash-2-jVUCKqrR.js → trash-2-BT8V531F.js} +1 -1
  77. package/dashboard/dist/assets/{use-community-stats-uWK0fE6l.js → use-community-stats-DJr59SGu.js} +1 -1
  78. package/dashboard/dist/assets/{use-limits-display-prefs-DWsF4ShM.js → use-limits-display-prefs-DU8OOFVb.js} +1 -1
  79. package/dashboard/dist/assets/{use-native-settings-SEbqWDvc.js → use-native-settings-BvSwI4BL.js} +1 -1
  80. package/dashboard/dist/assets/{use-ordered-list-D4UhrPvw.js → use-ordered-list-Cq5AyV2x.js} +1 -1
  81. package/dashboard/dist/assets/use-reduced-motion-D1_q3aRK.js +1 -0
  82. package/dashboard/dist/assets/{use-session-efficiency-pref-DbysyUTJ.js → use-session-efficiency-pref-WS8NhXuW.js} +1 -1
  83. package/dashboard/dist/assets/{use-transform-DaEssGaZ.js → use-transform-CLYsi8xc.js} +1 -1
  84. package/dashboard/dist/assets/{use-usage-limits-CmmQ34Ov.js → use-usage-limits-CGoQQDG-.js} +1 -1
  85. package/dashboard/dist/assets/{useCurrency-DvBEn4rx.js → useCurrency-D0GtEoJD.js} +1 -1
  86. package/dashboard/dist/assets/{useScrollLock-QozsdFng.js → useScrollLock-TIUBSeIM.js} +1 -1
  87. package/dashboard/dist/assets/useTokenFormat-B0TBL9La.js +1 -0
  88. package/dashboard/dist/assets/{zap-Dp_aJ1q6.js → zap-D89769CL.js} +1 -1
  89. package/dashboard/dist/brand-logos/qoder.svg +5 -0
  90. package/dashboard/dist/index.html +18 -15
  91. package/dashboard/dist/ip-check.html +2 -2
  92. package/dashboard/dist/leaderboard.html +2 -2
  93. package/dashboard/dist/llms.txt +3 -2
  94. package/dashboard/dist/share.html +2 -2
  95. package/package.json +2 -2
  96. package/src/commands/init.js +1 -0
  97. package/src/commands/status.js +17 -0
  98. package/src/commands/sync.js +146 -126
  99. package/src/lib/local-api.js +4 -1
  100. package/src/lib/pet-packages.js +44 -2
  101. package/src/lib/pricing/seed-snapshot.json +1 -1
  102. package/src/lib/provider-status.js +104 -0
  103. package/src/lib/qoder-limits.js +782 -0
  104. package/src/lib/rollout.js +385 -0
  105. package/src/lib/usage-limits.js +31 -2
  106. package/dashboard/dist/assets/Card-D6hdzwvI.js +0 -1
  107. package/dashboard/dist/assets/DialogDescription-DgUSe6A1.js +0 -1
  108. package/dashboard/dist/assets/LandingPage-k1271lWV.js +0 -2
  109. package/dashboard/dist/assets/LeaderboardPage-Byww5UpT.js +0 -2
  110. package/dashboard/dist/assets/LimitsPage-DPtqL0PS.js +0 -4
  111. package/dashboard/dist/assets/PetPage--xQAjW4_.js +0 -1
  112. package/dashboard/dist/assets/SettingsPage-CHGA0bQS.js +0 -1
  113. package/dashboard/dist/assets/SkillsPage-I29SqkXO.js +0 -1
  114. package/dashboard/dist/assets/__vite-browser-external-UqlSqI-1.js +0 -1465
  115. package/dashboard/dist/assets/__vite-browser-external-WKilYZnq.css +0 -1
  116. package/dashboard/dist/assets/limits-providers-B_18X9Rp.js +0 -1
  117. package/dashboard/dist/assets/use-reduced-motion-C3UrOh55.js +0 -1
  118. package/dashboard/dist/assets/useTokenFormat-BTLrL7Mi.js +0 -1
@@ -1,5 +1,6 @@
1
1
  const fs = require("node:fs/promises");
2
2
  const fssync = require("node:fs");
3
+ const os = require("node:os");
3
4
  const path = require("node:path");
4
5
  const readline = require("node:readline");
5
6
 
@@ -2904,6 +2905,15 @@ function normalizeOpencodeState(raw) {
2904
2905
  };
2905
2906
  }
2906
2907
 
2908
+ function normalizeQoderState(raw) {
2909
+ const state = raw && typeof raw === "object" ? raw : {};
2910
+ const messages = state.messages && typeof state.messages === "object" ? state.messages : {};
2911
+ return {
2912
+ messages,
2913
+ updatedAt: typeof state.updatedAt === "string" ? state.updatedAt : null,
2914
+ };
2915
+ }
2916
+
2907
2917
  function normalizeMessageKeyPart(value) {
2908
2918
  if (typeof value !== "string") return "";
2909
2919
  return value.trim();
@@ -2997,6 +3007,33 @@ function addTotals(target, delta) {
2997
3007
  target.conversation_count += delta.conversation_count || 0;
2998
3008
  }
2999
3009
 
3010
+ function subtractTotals(target, totals) {
3011
+ target.input_tokens = Math.max(0, target.input_tokens - (totals.input_tokens || 0));
3012
+ target.cached_input_tokens = Math.max(
3013
+ 0,
3014
+ target.cached_input_tokens - (totals.cached_input_tokens || 0),
3015
+ );
3016
+ target.cache_creation_input_tokens = Math.max(
3017
+ 0,
3018
+ target.cache_creation_input_tokens - (totals.cache_creation_input_tokens || 0),
3019
+ );
3020
+ target.output_tokens = Math.max(0, target.output_tokens - (totals.output_tokens || 0));
3021
+ target.reasoning_output_tokens = Math.max(
3022
+ 0,
3023
+ target.reasoning_output_tokens - (totals.reasoning_output_tokens || 0),
3024
+ );
3025
+ target.total_tokens = Math.max(0, target.total_tokens - (totals.total_tokens || 0));
3026
+ target.billable_total_tokens = Math.max(
3027
+ 0,
3028
+ target.billable_total_tokens -
3029
+ (totals.billable_total_tokens ?? totals.total_tokens ?? 0),
3030
+ );
3031
+ target.conversation_count = Math.max(
3032
+ 0,
3033
+ target.conversation_count - (totals.conversation_count || 0),
3034
+ );
3035
+ }
3036
+
3000
3037
  function totalsKey(totals) {
3001
3038
  return [
3002
3039
  totals.input_tokens || 0,
@@ -4040,6 +4077,349 @@ async function parseOpencodeDbIncremental({
4040
4077
  return { messagesProcessed, eventsAggregated, bucketsQueued, projectBucketsQueued };
4041
4078
  }
4042
4079
 
4080
+ const QODER_USAGE_SQL = `
4081
+ SELECT
4082
+ cm.rowid AS row_id,
4083
+ cm.id,
4084
+ cm.session_id,
4085
+ cm.request_id,
4086
+ cm.token_info,
4087
+ cm.model_info,
4088
+ cm.gmt_create,
4089
+ cr.extra AS record_extra,
4090
+ cs.preferred_model_info,
4091
+ cs.project_uri,
4092
+ cs.project_name
4093
+ FROM chat_message AS cm
4094
+ LEFT JOIN chat_record AS cr ON cr.request_id = cm.request_id
4095
+ LEFT JOIN chat_session AS cs ON cs.session_id = cm.session_id
4096
+ WHERE cm.role = 'assistant'
4097
+ AND cm.token_info IS NOT NULL
4098
+ AND trim(cm.token_info) NOT IN ('', '{}')
4099
+ ORDER BY cm.gmt_create, cm.rowid
4100
+ `;
4101
+
4102
+ function resolveQoderDbPath({
4103
+ home = os.homedir(),
4104
+ env = process.env,
4105
+ platform = process.platform,
4106
+ } = {}) {
4107
+ if (typeof env.QODER_DB_PATH === "string" && env.QODER_DB_PATH.trim()) {
4108
+ return path.resolve(env.QODER_DB_PATH.trim());
4109
+ }
4110
+ let root;
4111
+ if (typeof env.QODER_HOME === "string" && env.QODER_HOME.trim()) {
4112
+ root = path.resolve(env.QODER_HOME.trim());
4113
+ } else if (platform === "darwin") {
4114
+ root = path.join(home, "Library", "Application Support", "Qoder");
4115
+ } else if (platform === "win32") {
4116
+ root = path.join(
4117
+ env.APPDATA || path.join(home, "AppData", "Roaming"),
4118
+ "Qoder",
4119
+ );
4120
+ } else {
4121
+ root = path.join(home, ".config", "Qoder");
4122
+ }
4123
+ return path.join(root, "SharedClientCache", "cache", "db", "local.db");
4124
+ }
4125
+
4126
+ function resolveQoderDbPaths({
4127
+ home = os.homedir(),
4128
+ env = process.env,
4129
+ platform = process.platform,
4130
+ deps = {},
4131
+ } = {}) {
4132
+ const nativeValue = resolveQoderDbPath({ home, env, platform });
4133
+ if (platform !== "win32" || env.QODER_DB_PATH || env.QODER_HOME) {
4134
+ return { native: nativeValue, wsl: null };
4135
+ }
4136
+ const existsSync = deps.existsSync || fssync.existsSync;
4137
+ const native = wsl.shouldProbeNative(env) && existsSync(nativeValue) ? nativeValue : null;
4138
+ const discoverWslHome = deps.discoverWslHome || wsl.discoverWslHome;
4139
+ const wslRoot = wsl.shouldProbeWsl(env)
4140
+ ? discoverWslHome(".config/Qoder", { ...deps, env })
4141
+ : null;
4142
+ const wslValue = wslRoot
4143
+ ? path.join(wslRoot, "SharedClientCache", "cache", "db", "local.db")
4144
+ : null;
4145
+ const wslDb = wslValue && existsSync(wslValue) ? wslValue : null;
4146
+ return wsl.resolveAllWin32Paths({
4147
+ nativeValue: native,
4148
+ wslValue: wslDb,
4149
+ env,
4150
+ platform: "win32",
4151
+ });
4152
+ }
4153
+
4154
+ async function readQoderDbMessages(dbPath, sqliteOptions = {}) {
4155
+ if (!dbPath || !fssync.existsSync(dbPath)) return [];
4156
+ return readSqliteJsonRowsAsync(dbPath, QODER_USAGE_SQL, {
4157
+ label: "Qoder",
4158
+ ...sqliteOptions,
4159
+ });
4160
+ }
4161
+
4162
+ function parseJsonObject(value) {
4163
+ if (!value) return null;
4164
+ if (value && typeof value === "object" && !Buffer.isBuffer(value)) return value;
4165
+ try {
4166
+ const parsed = JSON.parse(String(value));
4167
+ return parsed && typeof parsed === "object" ? parsed : null;
4168
+ } catch (_error) {
4169
+ return null;
4170
+ }
4171
+ }
4172
+
4173
+ function normalizeQoderTokens(tokenInfo) {
4174
+ const tokens = parseJsonObject(tokenInfo);
4175
+ if (!tokens) return null;
4176
+ const prompt = Number(tokens.prompt_tokens);
4177
+ const cached = Number(tokens.cached_tokens || 0);
4178
+ const completion = Number(tokens.completion_tokens);
4179
+ if (
4180
+ !Number.isFinite(prompt) ||
4181
+ !Number.isFinite(cached) ||
4182
+ !Number.isFinite(completion) ||
4183
+ prompt < 0 ||
4184
+ cached < 0 ||
4185
+ completion < 0
4186
+ ) {
4187
+ return null;
4188
+ }
4189
+ // Qoder's prompt_tokens already includes cached_tokens. Keep cached input in
4190
+ // its own column and report only the remainder as ordinary input, otherwise
4191
+ // cached context is counted twice in dashboards and cost calculations.
4192
+ const input = Math.max(0, Math.trunc(prompt) - Math.trunc(cached));
4193
+ const cachedInput = Math.min(Math.trunc(prompt), Math.trunc(cached));
4194
+ const output = Math.trunc(completion);
4195
+ return {
4196
+ input_tokens: input,
4197
+ cached_input_tokens: cachedInput,
4198
+ cache_creation_input_tokens: 0,
4199
+ output_tokens: output,
4200
+ reasoning_output_tokens: 0,
4201
+ total_tokens: Math.trunc(prompt) + output,
4202
+ billable_total_tokens: Math.trunc(prompt) + output,
4203
+ };
4204
+ }
4205
+
4206
+ function qoderModelFromRow(row) {
4207
+ const direct = parseJsonObject(row?.model_info);
4208
+ const recordExtra = parseJsonObject(row?.record_extra);
4209
+ const preferred = parseJsonObject(row?.preferred_model_info);
4210
+ return (
4211
+ normalizeModelInput(direct?.model_key || direct?.modelKey) ||
4212
+ normalizeModelInput(recordExtra?.modelConfig?.key || recordExtra?.model_config?.key) ||
4213
+ normalizeModelInput(
4214
+ preferred?.model_key ||
4215
+ preferred?.modelKey ||
4216
+ preferred?.preferred_model ||
4217
+ preferred?.preferredModel,
4218
+ ) ||
4219
+ "qoder-agent"
4220
+ );
4221
+ }
4222
+
4223
+ function qoderMessageKey(row) {
4224
+ const id = normalizeMessageKeyPart(row?.id);
4225
+ const sessionId = normalizeMessageKeyPart(row?.session_id);
4226
+ if (sessionId && id) return `${sessionId}|${id}`;
4227
+ if (id) return id;
4228
+ const rowId = row?.row_id;
4229
+ return rowId === null || rowId === undefined ? null : `row:${rowId}`;
4230
+ }
4231
+
4232
+ function qoderProjectPath(row) {
4233
+ const raw = typeof row?.project_uri === "string" ? row.project_uri.trim() : "";
4234
+ if (!raw) return null;
4235
+ if (!raw.startsWith("file://")) return raw;
4236
+ try {
4237
+ return decodeURIComponent(new URL(raw).pathname);
4238
+ } catch (_error) {
4239
+ return null;
4240
+ }
4241
+ }
4242
+
4243
+ async function parseQoderDbIncremental({
4244
+ dbMessages,
4245
+ cursors,
4246
+ queuePath,
4247
+ projectQueuePath,
4248
+ onProgress,
4249
+ publicRepoResolver,
4250
+ } = {}) {
4251
+ await ensureDir(path.dirname(queuePath));
4252
+ const rows = Array.isArray(dbMessages) ? dbMessages : [];
4253
+ const hourlyState = normalizeHourlyState(cursors?.hourly);
4254
+ const qoderState = normalizeQoderState(cursors?.qoder);
4255
+ const touchedBuckets = new Set();
4256
+ const projectEnabled = typeof projectQueuePath === "string" && projectQueuePath.length > 0;
4257
+ const projectState = projectEnabled ? normalizeProjectState(cursors?.projectHourly) : null;
4258
+ const projectTouchedBuckets = projectEnabled ? new Set() : null;
4259
+ const projectMetaCache = projectEnabled ? new Map() : null;
4260
+ const publicRepoCache = projectEnabled ? new Map() : null;
4261
+ const requestOwners = new Map();
4262
+ for (const row of rows) {
4263
+ const messageKey = qoderMessageKey(row);
4264
+ if (
4265
+ !messageKey ||
4266
+ !normalizeQoderTokens(row?.token_info) ||
4267
+ !coerceEpochMs(row?.gmt_create)
4268
+ ) {
4269
+ continue;
4270
+ }
4271
+ const requestKey =
4272
+ normalizeMessageKeyPart(row?.request_id) ||
4273
+ normalizeMessageKeyPart(row?.session_id) ||
4274
+ messageKey;
4275
+ if (!requestOwners.has(requestKey)) requestOwners.set(requestKey, messageKey);
4276
+ }
4277
+ const cb = typeof onProgress === "function" ? onProgress : null;
4278
+ let messagesProcessed = 0;
4279
+ let eventsAggregated = 0;
4280
+
4281
+ for (let index = 0; index < rows.length; index++) {
4282
+ const row = rows[index];
4283
+ const messageKey = qoderMessageKey(row);
4284
+ const currentBase = normalizeQoderTokens(row?.token_info);
4285
+ const timestampMs = coerceEpochMs(row?.gmt_create);
4286
+ const bucketStart = timestampMs
4287
+ ? toUtcHalfHourStart(new Date(timestampMs).toISOString())
4288
+ : null;
4289
+ if (!messageKey || !currentBase || !bucketStart) {
4290
+ messagesProcessed += 1;
4291
+ continue;
4292
+ }
4293
+
4294
+ const requestKey =
4295
+ normalizeMessageKeyPart(row?.request_id) ||
4296
+ normalizeMessageKeyPart(row?.session_id) ||
4297
+ messageKey;
4298
+ const previous = qoderState.messages[messageKey];
4299
+ // Recompute request ownership from the complete ordered DB snapshot on
4300
+ // every pass. Qoder can attach token_info to an earlier assistant row only
4301
+ // after a later row was already counted; retaining the old per-message
4302
+ // owner in that case inflates one request to two conversations.
4303
+ const conversationCount = requestOwners.get(requestKey) === messageKey ? 1 : 0;
4304
+
4305
+ const currentTotals = {
4306
+ ...currentBase,
4307
+ conversation_count: conversationCount,
4308
+ };
4309
+ const model = qoderModelFromRow(row);
4310
+ let projectKey = null;
4311
+ let projectRef = null;
4312
+ if (projectEnabled) {
4313
+ const projectPath = qoderProjectPath(row);
4314
+ if (projectPath) {
4315
+ const context = await resolveProjectContextForPath({
4316
+ startDir: projectPath,
4317
+ projectMetaCache,
4318
+ publicRepoCache,
4319
+ publicRepoResolver,
4320
+ projectState,
4321
+ });
4322
+ projectKey = context?.projectKey || null;
4323
+ projectRef = context?.projectRef || null;
4324
+ }
4325
+ }
4326
+
4327
+ const previousTotals =
4328
+ previous?.totals && typeof previous.totals === "object" ? previous.totals : null;
4329
+ const unchanged =
4330
+ previousTotals &&
4331
+ totalsKey(previousTotals) === totalsKey(currentTotals) &&
4332
+ previous.bucketStart === bucketStart &&
4333
+ previous.model === model &&
4334
+ (previous.projectKey || null) === projectKey;
4335
+ if (!unchanged) {
4336
+ if (previousTotals && previous.bucketStart && previous.model) {
4337
+ const oldBucket = getHourlyBucket(
4338
+ hourlyState,
4339
+ "qoder",
4340
+ previous.model,
4341
+ previous.bucketStart,
4342
+ );
4343
+ subtractTotals(oldBucket.totals, previousTotals);
4344
+ touchedBuckets.add(bucketKey("qoder", previous.model, previous.bucketStart));
4345
+ if (projectEnabled && previous.projectKey) {
4346
+ const oldProjectBucket = getProjectBucket(
4347
+ projectState,
4348
+ previous.projectKey,
4349
+ "qoder",
4350
+ previous.bucketStart,
4351
+ previous.projectRef || null,
4352
+ );
4353
+ subtractTotals(oldProjectBucket.totals, previousTotals);
4354
+ projectTouchedBuckets.add(
4355
+ projectBucketKey(previous.projectKey, "qoder", previous.bucketStart),
4356
+ );
4357
+ }
4358
+ }
4359
+
4360
+ const bucket = getHourlyBucket(hourlyState, "qoder", model, bucketStart);
4361
+ addTotals(bucket.totals, currentTotals);
4362
+ touchedBuckets.add(bucketKey("qoder", model, bucketStart));
4363
+ if (projectEnabled && projectKey) {
4364
+ const projectBucket = getProjectBucket(
4365
+ projectState,
4366
+ projectKey,
4367
+ "qoder",
4368
+ bucketStart,
4369
+ projectRef,
4370
+ );
4371
+ addTotals(projectBucket.totals, currentTotals);
4372
+ projectTouchedBuckets.add(projectBucketKey(projectKey, "qoder", bucketStart));
4373
+ }
4374
+ qoderState.messages[messageKey] = {
4375
+ totals: currentTotals,
4376
+ conversationCount,
4377
+ requestKey,
4378
+ bucketStart,
4379
+ model,
4380
+ projectKey,
4381
+ projectRef,
4382
+ updatedAt: new Date().toISOString(),
4383
+ };
4384
+ eventsAggregated += 1;
4385
+ }
4386
+
4387
+ messagesProcessed += 1;
4388
+ if (cb) {
4389
+ cb({
4390
+ index: index + 1,
4391
+ total: rows.length,
4392
+ messagesProcessed,
4393
+ eventsAggregated,
4394
+ bucketsQueued: touchedBuckets.size,
4395
+ });
4396
+ }
4397
+ }
4398
+
4399
+ const bucketsQueued = await enqueueTouchedBuckets({
4400
+ queuePath,
4401
+ hourlyState,
4402
+ touchedBuckets,
4403
+ });
4404
+ const projectBucketsQueued = projectEnabled
4405
+ ? await enqueueTouchedProjectBuckets({
4406
+ projectQueuePath,
4407
+ projectState,
4408
+ projectTouchedBuckets,
4409
+ })
4410
+ : 0;
4411
+ const updatedAt = new Date().toISOString();
4412
+ hourlyState.updatedAt = updatedAt;
4413
+ qoderState.updatedAt = updatedAt;
4414
+ cursors.hourly = hourlyState;
4415
+ cursors.qoder = qoderState;
4416
+ if (projectState) {
4417
+ projectState.updatedAt = updatedAt;
4418
+ cursors.projectHourly = projectState;
4419
+ }
4420
+ return { messagesProcessed, eventsAggregated, bucketsQueued, projectBucketsQueued };
4421
+ }
4422
+
4043
4423
  // Resolve project context from DB message (no file path available)
4044
4424
  async function resolveProjectContextForDb({
4045
4425
  msg,
@@ -13854,6 +14234,9 @@ module.exports = {
13854
14234
  readOpencodeDbMessages,
13855
14235
  readMimoDbMessages,
13856
14236
  readZcodeDbMessages,
14237
+ resolveQoderDbPath,
14238
+ resolveQoderDbPaths,
14239
+ readQoderDbMessages,
13857
14240
  resolveKiroDbPath,
13858
14241
  resolveKiroJsonlPath,
13859
14242
  resolveHermesPath,
@@ -13878,6 +14261,7 @@ module.exports = {
13878
14261
  parseGeminiIncremental,
13879
14262
  parseOpencodeIncremental,
13880
14263
  parseOpencodeDbIncremental,
14264
+ parseQoderDbIncremental,
13881
14265
  openclawCursorKey,
13882
14266
  parseOpenclawIncremental,
13883
14267
  parseCursorApiIncremental,
@@ -13965,6 +14349,7 @@ module.exports = {
13965
14349
  // Exposed for regression tests covering cache-token accounting.
13966
14350
  normalizeGeminiTokens,
13967
14351
  normalizeOpencodeTokens,
14352
+ normalizeQoderTokens,
13968
14353
  sameGeminiTotals,
13969
14354
  diffGeminiTotals,
13970
14355
  // Exposed so the queue-repair migration can mutate cursors state in the
@@ -27,6 +27,8 @@ const {
27
27
  const { fetchGrokLimits } = require("./grok-limits");
28
28
  const { fetchZcodeLimits } = require("./zcode-limits");
29
29
  const { fetchOpencodeGoLimits } = require("./opencode-go-limits");
30
+ const { fetchQoderLimits } = require("./qoder-limits");
31
+ const { fetchProviderServiceStatus } = require("./provider-status");
30
32
  const { readSqliteJsonRows, readSqliteJsonRowsAsync } = require("./sqlite-reader");
31
33
 
32
34
  const execFileAsync = promisify(cp.execFile);
@@ -2909,7 +2911,7 @@ function toTitleCase(s) {
2909
2911
  // leading brand word and Title Case the rest.
2910
2912
  function normalizePlanLabel(raw, brand) {
2911
2913
  if (raw == null) return null;
2912
- let s = String(raw).trim();
2914
+ let s = String(raw).replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
2913
2915
  if (!s) return null;
2914
2916
  const lower = s.toLowerCase();
2915
2917
  if (["free", "none", "unknown"].includes(lower)) return null;
@@ -3052,7 +3054,7 @@ async function fetchUsageLimitsUncached({
3052
3054
  : null;
3053
3055
 
3054
3056
  const providerFetch = withFetchTimeout(fetchImpl, providerTimeoutMs);
3055
- const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok, zcode, opencodeGo] = await Promise.all([
3057
+ const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok, zcode, opencodeGo, qoder, claudeServiceStatus] = await Promise.all([
3056
3058
  claudeToken && !freshClaudeCache && !claudeRetryAtMs
3057
3059
  ? withProviderTimeout(fetchClaudeUsageLimits(claudeToken, { fetchImpl: providerFetch, maxAttempts: 1 }), "Claude", providerTimeoutMs).then(
3058
3060
  (value) => ({ status: "fulfilled", value }),
@@ -3088,6 +3090,22 @@ async function fetchUsageLimitsUncached({
3088
3090
  // OPENCODE_GO_AUTH_COOKIE is set. See src/lib/opencode-go-limits.js.
3089
3091
  withProviderTimeout(fetchOpencodeGoLimits({ home, env, fetchImpl: providerFetch }), "OpenCode Go", providerTimeoutMs)
3090
3092
  .catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
3093
+ withProviderTimeout(
3094
+ fetchQoderLimits({
3095
+ home,
3096
+ env,
3097
+ platform,
3098
+ fetchImpl: providerFetch,
3099
+ }),
3100
+ "Qoder",
3101
+ providerTimeoutMs,
3102
+ ).catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
3103
+ // Public status-page probe (fail-soft, own 5-min cache in provider-status.js).
3104
+ // Only probed for configured accounts — without a token the Claude section
3105
+ // never renders, so the reading would have nowhere to go.
3106
+ claudeToken
3107
+ ? fetchProviderServiceStatus("claude", { fetchImpl, nowMs })
3108
+ : Promise.resolve(null),
3091
3109
  ]);
3092
3110
 
3093
3111
  let claude;
@@ -3154,6 +3172,15 @@ async function fetchUsageLimitsUncached({
3154
3172
  }
3155
3173
  }
3156
3174
 
3175
+ // Attach the service-status reading AFTER assembly so it rides on every path
3176
+ // (live, fresh-cache, stale-cache, error) but never gets persisted by
3177
+ // writeClaudeLimitsCache above — a disk cache must not resurrect an incident
3178
+ // banner hours later. Only active incidents ship; "none" is omitted so the
3179
+ // client renders nothing in the happy path.
3180
+ if (claude.configured && claudeServiceStatus && claudeServiceStatus.indicator !== "none") {
3181
+ claude.service_status = claudeServiceStatus;
3182
+ }
3183
+
3157
3184
  let codex;
3158
3185
  if (!codexToken) {
3159
3186
  codex = { configured: false };
@@ -3212,6 +3239,7 @@ async function fetchUsageLimitsUncached({
3212
3239
  grok: withPlanLabel(grok, null, "Grok"),
3213
3240
  zcode: withPlanLabel(zcode, zcode.plan_label, "ZCode"),
3214
3241
  opencodeGo: withPlanLabel(opencodeGo, opencodeGo?.plan_label, "OpenCode Go"),
3242
+ qoder: withPlanLabel(qoder, qoder?.plan_label, "Qoder"),
3215
3243
  };
3216
3244
 
3217
3245
  for (const [providerName, provider] of Object.entries(data)) {
@@ -3270,4 +3298,5 @@ module.exports = {
3270
3298
  fetchGrokLimits,
3271
3299
  fetchZcodeLimits,
3272
3300
  fetchOpencodeGoLimits,
3301
+ fetchQoderLimits,
3273
3302
  };
@@ -1 +0,0 @@
1
- import{j as r,d as e}from"./__vite-browser-external-UqlSqI-1.js";function n({children:i,title:a,subtitle:o,className:s="",bodyClassName:d=""}){return r.jsxs("div",{className:e("rounded-xl border border-oai-gray-200 dark:border-oai-gray-800 bg-white dark:bg-oai-gray-900 transition-colors duration-200",s),children:[(a||o)&&r.jsxs("div",{className:"px-5 py-4 border-b border-oai-gray-200 dark:border-oai-gray-800 transition-colors duration-200",children:[a&&r.jsx("h3",{className:"text-sm font-medium text-oai-gray-500 dark:text-oai-gray-300 uppercase tracking-wide transition-colors duration-200",children:a}),o&&r.jsx("p",{className:"text-sm text-oai-gray-500 dark:text-oai-gray-300 mt-1 transition-colors duration-200",children:o})]}),r.jsx("div",{className:`p-5 ${d}`,children:i})]})}export{n as C};
@@ -1 +0,0 @@
1
- import{r as n,z as a}from"./__vite-browser-external-UqlSqI-1.js";import{u as p}from"./DialogTitle-C1ZeQgf2.js";import{u as c}from"./useScrollLock-QozsdFng.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,{store:i}=p(),o=c(s);return i.useSyncedValueWithCleanup("descriptionElementId",o),a("p",e,{ref:r,props:[{id:o},t]})});export{x as D};