@ipv9/tokentracker-cli 0.39.38 → 0.39.39

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 (79) hide show
  1. package/README.md +41 -9
  2. package/dashboard/dist/assets/{Card-DD4s0V6T.js → Card-BbRYRVXf.js} +1 -1
  3. package/dashboard/dist/assets/DashboardPage-DYKHv0m3.js +60 -0
  4. package/dashboard/dist/assets/{FadeIn-CUSBK0jm.js → FadeIn-CjAacJ5t.js} +1 -1
  5. package/dashboard/dist/assets/{IpCheckPage-PHF4lPax.js → IpCheckPage-BGALK8Vy.js} +1 -1
  6. package/dashboard/dist/assets/{LimitsPage-dS9C9Vdt.js → LimitsPage-LkZGPYvg.js} +2 -2
  7. package/dashboard/dist/assets/{LocalOnlyNotice-DCmtS_j5.js → LocalOnlyNotice-Dxo2vFMQ.js} +1 -1
  8. package/dashboard/dist/assets/PopoverPopup-DP9fwdtl.js +1 -0
  9. package/dashboard/dist/assets/Select-DPE53NmM.js +1 -0
  10. package/dashboard/dist/assets/SelectItemText-D69v77is.js +12 -0
  11. package/dashboard/dist/assets/SettingsPage-Dxzqpi02.js +1 -0
  12. package/dashboard/dist/assets/SkillsPage-Bo7e39AB.js +1 -0
  13. package/dashboard/dist/assets/WidgetsPage-HHcIr_Eq.js +1 -0
  14. package/dashboard/dist/assets/WrappedPage-C-_jLXXU.js +1 -0
  15. package/dashboard/dist/assets/{arrow-up-right-CjaBQnwI.js → arrow-up-right-BJvJHUGj.js} +1 -1
  16. package/dashboard/dist/assets/{download-DLBqkSwp.js → download-CnzY_EgN.js} +1 -1
  17. package/dashboard/dist/assets/format-1w-q5n2f.js +1 -0
  18. package/dashboard/dist/assets/main-1kxAVM3m.css +1 -0
  19. package/dashboard/dist/assets/main-3gBEI6Jl.js +694 -0
  20. package/dashboard/dist/assets/mock-data-CJOMGcL9.js +1 -0
  21. package/dashboard/dist/assets/use-limits-display-prefs-D1nT9-mr.js +1 -0
  22. package/dashboard/dist/assets/use-native-settings-Dh6NzqjY.js +1 -0
  23. package/dashboard/dist/assets/use-usage-limits-BXR6lzGf.js +1 -0
  24. package/dashboard/dist/assets/useCurrency-Hhs5jbWw.js +1 -0
  25. package/dashboard/dist/index.html +15 -27
  26. package/dashboard/dist/llms.txt +0 -2
  27. package/dashboard/dist/sitemap.xml +0 -12
  28. package/package.json +7 -4
  29. package/src/cli.js +4 -13
  30. package/src/commands/doctor.js +1 -3
  31. package/src/commands/init.js +63 -53
  32. package/src/commands/serve.js +2 -50
  33. package/src/commands/status.js +1 -55
  34. package/src/commands/sync.js +134 -227
  35. package/src/lib/{browser-auth.js → browser-open.js} +3 -149
  36. package/src/lib/claude-categorizer.js +204 -32
  37. package/src/lib/codex-context-breakdown.js +83 -7
  38. package/src/lib/codex-rollout-parser.js +52 -10
  39. package/src/lib/diagnostics.js +1 -49
  40. package/src/lib/doctor.js +12 -87
  41. package/src/lib/file-identity.js +69 -0
  42. package/src/lib/init-flow.js +6 -3
  43. package/src/lib/local-api.js +6 -364
  44. package/src/lib/pricing/curated-overrides.json +1 -1
  45. package/src/lib/pricing/seed-snapshot.json +1 -1
  46. package/src/lib/rollout.js +392 -144
  47. package/src/lib/runtime-config.js +0 -32
  48. package/src/lib/sqlite-reader.js +83 -8
  49. package/dashboard/dist/assets/ActivityHeatmap-BrehqUIb.js +0 -42
  50. package/dashboard/dist/assets/DashboardPage-CyUShBRJ.js +0 -19
  51. package/dashboard/dist/assets/DevicePage-BA74Su1l.js +0 -1
  52. package/dashboard/dist/assets/DialogTitle-DoKsz5wi.js +0 -1
  53. package/dashboard/dist/assets/InsforgeUserHeaderControls-Bk_U2dHr.js +0 -1
  54. package/dashboard/dist/assets/LandingPage-Bbeu84Iu.js +0 -4356
  55. package/dashboard/dist/assets/LeaderboardAvatar-ByqeB6bM.js +0 -1
  56. package/dashboard/dist/assets/LeaderboardPage-BeWXF00S.js +0 -6
  57. package/dashboard/dist/assets/LeaderboardProfileModal-C4yvbZd_.js +0 -72
  58. package/dashboard/dist/assets/LeaderboardProfilePage-D3cbSh5o.js +0 -1
  59. package/dashboard/dist/assets/LoginPage-Ch3UQoWj.js +0 -1
  60. package/dashboard/dist/assets/PopoverPopup-DjG0gjlA.js +0 -1
  61. package/dashboard/dist/assets/Select-HzIjWJpE.js +0 -1
  62. package/dashboard/dist/assets/SelectItemText-f0tCwtvZ.js +0 -1
  63. package/dashboard/dist/assets/SettingsPage-DJOZAlZy.js +0 -1
  64. package/dashboard/dist/assets/SkillsPage-BQcyJIdx.js +0 -1
  65. package/dashboard/dist/assets/WidgetsPage-B_vxuV09.js +0 -1
  66. package/dashboard/dist/assets/WrappedPage-CZN9Y3Vz.js +0 -1
  67. package/dashboard/dist/assets/index-BeoRn2gJ.js +0 -2
  68. package/dashboard/dist/assets/info-BAIxlHIB.js +0 -1
  69. package/dashboard/dist/assets/main-DAcSUW2t.css +0 -1
  70. package/dashboard/dist/assets/main-pi07CfeC.js +0 -1004
  71. package/dashboard/dist/assets/use-limits-display-prefs-CMfASZsh.js +0 -1
  72. package/dashboard/dist/assets/use-native-settings-CfPik-Ao.js +0 -1
  73. package/dashboard/dist/assets/use-spring-wsmJQRqb.js +0 -1
  74. package/dashboard/dist/assets/use-usage-limits-Bt0Dp6kx.js +0 -1
  75. package/dashboard/dist/assets/useCurrency-CsaAMc3j.js +0 -1
  76. package/dashboard/dist/assets/useOpenInteractionType-CPzi5Z8M.js +0 -12
  77. package/dashboard/dist/share.html +0 -140
  78. package/src/commands/device-login.js +0 -161
  79. package/src/lib/upload-throttle.js +0 -148
@@ -12,7 +12,6 @@ const {
12
12
  isGeminiHookConfigured,
13
13
  } = require("./gemini-config");
14
14
  const { resolveOpencodeConfigDir, isOpencodePluginInstalled } = require("./opencode-config");
15
- const { normalizeState: normalizeUploadState } = require("./upload-throttle");
16
15
  const { probeOpenclawHookState } = require("./openclaw-hook");
17
16
  const { probeOpenclawSessionPluginState } = require("./openclaw-session-plugin");
18
17
  const { probeGrokHookState } = require("./grok-hook");
@@ -40,13 +39,10 @@ async function collectTrackerDiagnostics({
40
39
  const { trackerDir, binDir } = await resolveTrackerPaths({ home });
41
40
  const configPath = path.join(trackerDir, "config.json");
42
41
  const queuePath = path.join(trackerDir, "queue.jsonl");
43
- const queueStatePath = path.join(trackerDir, "queue.state.json");
44
42
  const cursorsPath = path.join(trackerDir, "cursors.json");
45
43
  const notifySignalPath = path.join(trackerDir, "notify.signal");
46
44
  const openclawSignalPath = path.join(trackerDir, "openclaw.signal");
47
45
  const throttlePath = path.join(trackerDir, "sync.throttle");
48
- const uploadThrottlePath = path.join(trackerDir, "upload.throttle.json");
49
- const autoRetryPath = path.join(trackerDir, "auto.retry.json");
50
46
  const codexConfigPath = path.join(codexHome, "config.toml");
51
47
  const codeConfigPath = path.join(codeHome, "config.toml");
52
48
  const claudeConfigPath = path.join(home, ".claude", "settings.json");
@@ -60,13 +56,8 @@ async function collectTrackerDiagnostics({
60
56
 
61
57
  const config = await readJson(configPath);
62
58
  const cursors = await readJson(cursorsPath);
63
- const queueState = (await readJson(queueStatePath)) || { offset: 0 };
64
- const uploadThrottle = normalizeUploadState(await readJson(uploadThrottlePath));
65
- const autoRetry = await readJson(autoRetryPath);
66
59
 
67
60
  const queueSize = await safeStatSize(queuePath);
68
- const offsetBytes = Number(queueState.offset || 0);
69
- const pendingBytes = Math.max(0, queueSize - offsetBytes);
70
61
 
71
62
  const lastNotify = (await safeReadText(notifySignalPath))?.trim() || null;
72
63
  const lastOpenclawSync = (await safeReadText(openclawSignalPath))?.trim() || null;
@@ -120,11 +111,6 @@ async function collectTrackerDiagnostics({
120
111
  const kiroCliDbPath = resolveKiroCliDbPathInline(process.env, home);
121
112
  const kiroCliPresent = require("node:fs").existsSync(kiroCliDbPath);
122
113
 
123
- const lastSuccessAt = uploadThrottle.lastSuccessMs
124
- ? new Date(uploadThrottle.lastSuccessMs).toISOString()
125
- : null;
126
- const autoRetryAt = parseEpochMsToIso(autoRetry?.retryAtMs);
127
-
128
114
  return {
129
115
  ok: true,
130
116
  version: 1,
@@ -155,11 +141,9 @@ async function collectTrackerDiagnostics({
155
141
  cli_approximation:
156
142
  "Kiro CLI does not persist explicit token counts (billing is credit-based on Bedrock). Tokens are approximated at 4 chars/token from user prompt chars and assistant response chars. Source rows that came through this path have model='kiro-cli-agent' when the underlying model is unknown (auto-routing); known Bedrock ARNs canonicalize to their short name (e.g. claude-sonnet-4).",
157
143
  merge_policy:
158
- "Kiro IDE and Kiro CLI both emit source='kiro' in queue.jsonl so token, cost, heatmap, and leaderboard aggregations merge transparently. Use this block to distinguish sub-path contributions.",
144
+ "Kiro IDE and Kiro CLI both emit source='kiro' in queue.jsonl so token, cost, and heatmap aggregations merge transparently. Use this block to distinguish sub-path contributions.",
159
145
  },
160
146
  config: {
161
- base_url: typeof config?.baseUrl === "string" ? config.baseUrl : null,
162
- device_token: config?.deviceToken ? "set" : "unset",
163
147
  device_id: maskId(config?.deviceId),
164
148
  installed_at: typeof config?.installedAt === "string" ? config.installedAt : null,
165
149
  },
@@ -172,9 +156,6 @@ async function collectTrackerDiagnostics({
172
156
  },
173
157
  queue: {
174
158
  size_bytes: queueSize,
175
- offset_bytes: offsetBytes,
176
- pending_bytes: pendingBytes,
177
- updated_at: typeof queueState.updatedAt === "string" ? queueState.updatedAt : null,
178
159
  },
179
160
  notify: {
180
161
  last_notify: lastNotify,
@@ -198,28 +179,6 @@ async function collectTrackerDiagnostics({
198
179
  grok_hook_handler_exists: Boolean(grokHookState?.handlerExists),
199
180
  grok_sessions_dir: redactValue(grokHookState?.sessionsDir, home),
200
181
  },
201
- upload: {
202
- last_success_at: lastSuccessAt,
203
- next_allowed_after: parseEpochMsToIso(uploadThrottle.nextAllowedAtMs || null),
204
- backoff_until: parseEpochMsToIso(uploadThrottle.backoffUntilMs || null),
205
- last_error: uploadThrottle.lastError
206
- ? {
207
- at: uploadThrottle.lastErrorAt || null,
208
- message: redactError(String(uploadThrottle.lastError), home),
209
- }
210
- : null,
211
- },
212
- auto_retry: autoRetryAt
213
- ? {
214
- next_retry_at: autoRetryAt,
215
- reason: typeof autoRetry?.reason === "string" ? autoRetry.reason : null,
216
- pending_bytes: Number.isFinite(Number(autoRetry?.pendingBytes))
217
- ? Math.max(0, Number(autoRetry.pendingBytes))
218
- : null,
219
- scheduled_at: typeof autoRetry?.scheduledAt === "string" ? autoRetry.scheduledAt : null,
220
- source: typeof autoRetry?.source === "string" ? autoRetry.source : null,
221
- }
222
- : null,
223
182
  };
224
183
  }
225
184
 
@@ -237,13 +196,6 @@ function redactValue(value, home) {
237
196
  return value.startsWith(homeNorm) ? `~${value.slice(homeNorm.length)}` : value;
238
197
  }
239
198
 
240
- function redactError(message, home) {
241
- if (typeof message !== "string") return message;
242
- if (typeof home !== "string" || home.length === 0) return message;
243
- const homeNorm = home.endsWith(path.sep) ? home.slice(0, -1) : home;
244
- return message.split(homeNorm).join("~");
245
- }
246
-
247
199
  async function safeStatSize(p) {
248
200
  try {
249
201
  const st = await fs.stat(p);
package/src/lib/doctor.js CHANGED
@@ -29,7 +29,8 @@ async function buildDoctorReport({
29
29
  checks.push(await checkCliEntrypoint(paths.cliPath));
30
30
  }
31
31
 
32
- checks.push(await checkNetwork({ baseUrl: runtime?.baseUrl || null, fetch }));
32
+ // No cloud reachability check: TokenTracker is local-only, so there is no
33
+ // remote endpoint whose availability could affect anything here.
33
34
 
34
35
  if (diagnostics) {
35
36
  checks.push(...buildDiagnosticsChecks(diagnostics));
@@ -142,10 +143,6 @@ async function commandExistsOnPath(command, env = process.env) {
142
143
 
143
144
  function buildRuntimeChecks(runtime = {}) {
144
145
  const checks = [];
145
- const baseUrl =
146
- typeof runtime.baseUrl === "string" && runtime.baseUrl.trim() ? runtime.baseUrl.trim() : null;
147
- const deviceToken =
148
- typeof runtime.deviceToken === "string" && runtime.deviceToken.trim() ? "set" : "unset";
149
146
  const dashboardUrl =
150
147
  typeof runtime.dashboardUrl === "string" && runtime.dashboardUrl.trim()
151
148
  ? runtime.dashboardUrl.trim()
@@ -154,29 +151,9 @@ function buildRuntimeChecks(runtime = {}) {
154
151
  ? Number(runtime.httpTimeoutMs)
155
152
  : null;
156
153
  const debug = Boolean(runtime.debug);
157
- const autoRetryNoSpawn = Boolean(runtime.autoRetryNoSpawn);
158
154
 
159
- checks.push({
160
- id: "runtime.base_url",
161
- status: baseUrl ? "ok" : "fail",
162
- detail: baseUrl ? "base_url set" : "base_url missing",
163
- critical: false,
164
- meta: {
165
- base_url: baseUrl,
166
- source: runtime?.sources?.baseUrl || null,
167
- },
168
- });
169
-
170
- checks.push({
171
- id: "runtime.device_token",
172
- status: deviceToken === "set" ? "ok" : "warn",
173
- detail: deviceToken === "set" ? "device token set" : "device token missing",
174
- critical: false,
175
- meta: {
176
- device_token: deviceToken,
177
- source: runtime?.sources?.deviceToken || null,
178
- },
179
- });
155
+ // base URL / device token checks removed: local-only, there is no remote
156
+ // endpoint to point at and no credential to hold.
180
157
 
181
158
  checks.push({
182
159
  id: "runtime.dashboard_url",
@@ -211,16 +188,11 @@ function buildRuntimeChecks(runtime = {}) {
211
188
  },
212
189
  });
213
190
 
214
- checks.push({
215
- id: "runtime.auto_retry_no_spawn",
216
- status: "ok",
217
- detail: autoRetryNoSpawn ? "auto retry spawn disabled" : "auto retry spawn enabled",
218
- critical: false,
219
- meta: {
220
- auto_retry_no_spawn: autoRetryNoSpawn,
221
- source: runtime?.sources?.autoRetryNoSpawn || null,
222
- },
223
- });
191
+ // runtime.auto_retry_no_spawn removed: scheduleAutoRetry and
192
+ // spawnAutoRetryProcess went with cloud upload, so this reported on a spawn
193
+ // path that cannot happen — and told anyone setting the env var that their
194
+ // opt-out had taken effect on nothing.
195
+
224
196
 
225
197
  return checks;
226
198
  }
@@ -346,48 +318,6 @@ async function checkCliEntrypoint(cliPath) {
346
318
  }
347
319
  }
348
320
 
349
- async function checkNetwork({ baseUrl, fetch }) {
350
- if (!baseUrl) {
351
- return {
352
- id: "network.base_url",
353
- status: "warn",
354
- detail: "base_url missing (skipped)",
355
- critical: false,
356
- meta: { base_url: null },
357
- };
358
- }
359
-
360
- const start = Date.now();
361
- try {
362
- if (typeof fetch !== "function") throw new Error("Missing fetch");
363
- const res = await fetch(baseUrl, { method: "GET" });
364
- const latency = Date.now() - start;
365
- return {
366
- id: "network.base_url",
367
- status: "ok",
368
- detail: `HTTP ${res.status} (reachable)`,
369
- critical: false,
370
- meta: {
371
- status_code: res.status,
372
- latency_ms: latency,
373
- base_url: baseUrl,
374
- },
375
- };
376
- } catch (err) {
377
- const latency = Date.now() - start;
378
- return {
379
- id: "network.base_url",
380
- status: "fail",
381
- detail: "Network error",
382
- critical: false,
383
- meta: {
384
- error: err?.message || String(err),
385
- latency_ms: latency,
386
- base_url: baseUrl,
387
- },
388
- };
389
- }
390
- }
391
321
 
392
322
  function buildDiagnosticsChecks(diagnostics) {
393
323
  const checks = [];
@@ -409,14 +339,9 @@ function buildDiagnosticsChecks(diagnostics) {
409
339
  meta: { configured: notifyConfigured },
410
340
  });
411
341
 
412
- const uploadError = diagnostics?.upload?.last_error || null;
413
- checks.push({
414
- id: "upload.last_error",
415
- status: uploadError ? "warn" : "ok",
416
- detail: uploadError ? "last upload error present" : "no upload errors",
417
- critical: false,
418
- meta: { last_error: uploadError ? uploadError.message || null : null },
419
- });
342
+ // The upload.last_error check is gone with cloud upload: `diagnostics.upload`
343
+ // no longer exists, so the check could only ever report "no upload errors" —
344
+ // a permanent green that says nothing.
420
345
 
421
346
  return checks;
422
347
  }
@@ -0,0 +1,69 @@
1
+ const crypto = require("node:crypto");
2
+ const fssync = require("node:fs");
3
+
4
+ // Fingerprint the first bytes of a file so rotation is detected even when the
5
+ // OS reuses the inode after unlink+recreate (Linux logrotate). Returns "" on
6
+ // any error so callers treat "no fingerprint" as "not changed". Also returns
7
+ // "" when the file is shorter than the fingerprint window: for a sub-256B
8
+ // file that is still being appended to, the window would extend into the
9
+ // not-yet-written append zone, so plain growth (not rotation) would shift
10
+ // the hash and produce a false "rotated" signal.
11
+ function readFileHeadSignature(filePath) {
12
+ try {
13
+ const fd = fssync.openSync(filePath, "r");
14
+ try {
15
+ const buf = Buffer.alloc(256);
16
+ const bytes = fssync.readSync(fd, buf, 0, 256, 0);
17
+ if (bytes < 256) return "";
18
+ return crypto.createHash("sha1").update(buf.subarray(0, bytes)).digest("hex");
19
+ } finally {
20
+ fssync.closeSync(fd);
21
+ }
22
+ } catch {
23
+ return "";
24
+ }
25
+ }
26
+
27
+ // A token that changes whenever a file's contents could have changed. Returns
28
+ // null when the path is not a readable regular file, which callers must treat
29
+ // as "cannot be reused" rather than "unchanged".
30
+ function fileIdentity(filePath) {
31
+ let st = null;
32
+ try {
33
+ st = fssync.statSync(filePath);
34
+ } catch {
35
+ return null;
36
+ }
37
+ if (!st.isFile()) return null;
38
+ return {
39
+ inode: st.ino || 0,
40
+ size: Number.isFinite(st.size) ? st.size : 0,
41
+ mtimeMs: Number.isFinite(st.mtimeMs) ? st.mtimeMs : 0,
42
+ head: readFileHeadSignature(filePath),
43
+ };
44
+ }
45
+
46
+ // Equality, not ordering. Deliberately not `cur.mtimeMs > prev.mtimeMs`: a
47
+ // clock that moves backwards, a restored mtime, or a truncation must all read
48
+ // as "changed" and force a re-read. The cost of a false "changed" is a wasted
49
+ // parse; the cost of a false "unchanged" is serving stale data forever.
50
+ function isUnchanged(prev, cur) {
51
+ if (!prev || !cur) return false;
52
+ // An empty head means the file was too short to fingerprint, so it carries
53
+ // no rotation signal — fall back to inode/size/mtime alone rather than
54
+ // treating "" as a mismatch.
55
+ const headChanged =
56
+ typeof prev.head === "string" && prev.head !== "" && prev.head !== cur.head;
57
+ return (
58
+ prev.inode === cur.inode &&
59
+ prev.size === cur.size &&
60
+ prev.mtimeMs === cur.mtimeMs &&
61
+ !headChanged
62
+ );
63
+ }
64
+
65
+ module.exports = {
66
+ readFileHeadSignature,
67
+ fileIdentity,
68
+ isUnchanged,
69
+ };
@@ -27,9 +27,12 @@ function renderAuthTransition({ authUrl, canAutoOpen }) {
27
27
  }
28
28
 
29
29
  function renderSuccessBox({ configPath, dashboardUrl }) {
30
- const identityLine = "Account linked.";
31
- const lines = ["You are all set!", "", identityLine, `Token saved to: ${configPath}`, ""];
32
- if (dashboardUrl) lines.push(`View your stats at: ${dashboardUrl}`);
30
+ // Local-only: there is no account and no remote dashboard. `dashboardUrl` is
31
+ // null on a clean install now that the cloud default is gone, so fall back to
32
+ // the address `serve` actually listens on — otherwise setup finishes without
33
+ // telling the user where to look.
34
+ const lines = ["You are all set!", "", `Config saved to: ${configPath}`, ""];
35
+ lines.push(`View your stats at: ${dashboardUrl || "http://localhost:7680"}`);
33
36
  lines.push("You can close this terminal window.");
34
37
  process.stdout.write(`${renderBox(lines)}\n`);
35
38
  }