tokentracker-cli 0.19.0 → 0.20.0

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 (37) hide show
  1. package/README.md +2 -2
  2. package/README.zh-CN.md +2 -2
  3. package/dashboard/dist/assets/{Card-ANC9ZmIT.js → Card-jA08WeEw.js} +1 -1
  4. package/dashboard/dist/assets/{DashboardPage-DpZaCksZ.js → DashboardPage-chDVOYmG.js} +1 -1
  5. package/dashboard/dist/assets/{FadeIn-WXGyOn0H.js → FadeIn-DqSYXuUL.js} +1 -1
  6. package/dashboard/dist/assets/{HeaderGithubStar-DUkE0Dwd.js → HeaderGithubStar-C11rWv0B.js} +1 -1
  7. package/dashboard/dist/assets/{IpCheckPage-BPF8eGpg.js → IpCheckPage-CkEZ9yLK.js} +1 -1
  8. package/dashboard/dist/assets/{LandingPage-0uTpqpAU.js → LandingPage-BgckTHRQ.js} +1 -1
  9. package/dashboard/dist/assets/{LeaderboardPage-DzxRJEzb.js → LeaderboardPage-BCNW7UWp.js} +1 -1
  10. package/dashboard/dist/assets/{LeaderboardProfilePage-C3_oUxhG.js → LeaderboardProfilePage-BLATxMt-.js} +1 -1
  11. package/dashboard/dist/assets/{LimitsPage-BVuvoeY9.js → LimitsPage-arF--WgR.js} +1 -1
  12. package/dashboard/dist/assets/{LoginPage-CfkNRmT6.js → LoginPage-DpoFP0va.js} +1 -1
  13. package/dashboard/dist/assets/{PopoverPopup-CfxiYbJm.js → PopoverPopup-kdgc2H6C.js} +1 -1
  14. package/dashboard/dist/assets/{ProviderIcon-DiPzAed2.js → ProviderIcon-DV5r9qqP.js} +1 -1
  15. package/dashboard/dist/assets/{SettingsPage-Devu7beE.js → SettingsPage-Bb22ORmU.js} +1 -1
  16. package/dashboard/dist/assets/{SkillsPage-CSe8fW4V.js → SkillsPage-xhtBqVKC.js} +1 -1
  17. package/dashboard/dist/assets/{WidgetsPage-BrLp5YLk.js → WidgetsPage-CUoSVDET.js} +1 -1
  18. package/dashboard/dist/assets/{chevron-down-nFF6Yj_r.js → chevron-down-DYb2EChD.js} +1 -1
  19. package/dashboard/dist/assets/{download-DhSZ--68.js → download-C-_8o6dh.js} +1 -1
  20. package/dashboard/dist/assets/{leaderboard-columns-CvFdXrw5.js → leaderboard-columns-BgzBlYo7.js} +1 -1
  21. package/dashboard/dist/assets/{main-DtrPNYb7.js → main-11hApDak.js} +3 -3
  22. package/dashboard/dist/assets/{use-limits-display-prefs-Yy8t7tbB.js → use-limits-display-prefs-BeGKWUuk.js} +1 -1
  23. package/dashboard/dist/assets/{use-native-settings-uemf9RSH.js → use-native-settings-nTTHktn0.js} +1 -1
  24. package/dashboard/dist/assets/{use-reduced-motion-DH8DxE18.js → use-reduced-motion-DU8Gm6j1.js} +1 -1
  25. package/dashboard/dist/assets/{use-usage-limits-C3vUT6PH.js → use-usage-limits-DTPmEB8Y.js} +1 -1
  26. package/dashboard/dist/index.html +1 -1
  27. package/dashboard/dist/share.html +1 -1
  28. package/package.json +1 -1
  29. package/src/commands/init.js +9 -5
  30. package/src/commands/serve.js +0 -12
  31. package/src/commands/sync.js +370 -7
  32. package/src/lib/grok-hook.js +86 -7
  33. package/src/lib/pricing/curated-overrides.json +1 -1
  34. package/src/lib/pricing/seed-snapshot.json +1 -1
  35. package/src/lib/rollout.js +403 -140
  36. package/src/lib/subscriptions.js +92 -40
  37. package/src/lib/usage-limits.js +1 -1
@@ -94,14 +94,15 @@ function buildGrokSessionEndHandler({ trackerDir }) {
94
94
  // It must be self-contained enough or rely on the copied runtime.
95
95
  // For simplicity and reliability we write a small script that:
96
96
  // 1. Reads GROK_SESSION_ID + GROK_WORKSPACE_ROOT from env
97
- // 2. Locates the signals.json
98
- // 3. Extracts usage
97
+ // 2. Locates the session metadata files
98
+ // 3. Extracts usage metadata only
99
99
  // 4. Writes a signal file under trackerDir that sync.js will pick up on next run
100
100
 
101
101
  return `#!/usr/bin/env node
102
102
  const fs = require('node:fs');
103
103
  const path = require('node:path');
104
104
  const os = require('node:os');
105
+ const readline = require('node:readline');
105
106
 
106
107
  const GROK_HOME = process.env.TOKENTRACKER_GROK_HOME || process.env.GROK_HOME || path.join(os.homedir(), '.grok');
107
108
  const SESSION_ID = process.env.GROK_SESSION_ID;
@@ -120,14 +121,17 @@ const encodedCwd = encodeGrokCwd(WORKSPACE_ROOT);
120
121
  const sessionDir = path.join(GROK_HOME, 'sessions', encodedCwd, SESSION_ID);
121
122
  const signalsPath = path.join(sessionDir, 'signals.json');
122
123
  const summaryPath = path.join(sessionDir, 'summary.json');
124
+ const updatesPath = path.join(sessionDir, 'updates.jsonl');
123
125
 
124
- let signals = null;
126
+ let signals = {};
125
127
  try {
126
128
  const raw = fs.readFileSync(signalsPath, 'utf8');
127
129
  signals = JSON.parse(raw);
128
130
  } catch (err) {
129
- // Session may still be active or signals not written yet; exit quietly
130
- process.exit(0);
131
+ signals = {};
132
+ }
133
+ if (!signals || typeof signals !== 'object') {
134
+ signals = {};
131
135
  }
132
136
 
133
137
  const summary = (() => {
@@ -143,10 +147,74 @@ function toNonNegativeFiniteNumber(value) {
143
147
  return Number.isFinite(number) && number > 0 ? number : 0;
144
148
  }
145
149
 
146
- const totalTokens = toNonNegativeFiniteNumber(signals.contextTokensUsed);
150
+ function timestampToIso(value) {
151
+ if (value == null) return null;
152
+ if (typeof value === 'number') {
153
+ if (!Number.isFinite(value) || value <= 0) return null;
154
+ const millis = value < 10000000000 ? value * 1000 : value;
155
+ const dt = new Date(millis);
156
+ return Number.isFinite(dt.getTime()) ? dt.toISOString() : null;
157
+ }
158
+ if (typeof value === 'string') {
159
+ const trimmed = value.trim();
160
+ if (!trimmed) return null;
161
+ if (/^[0-9]+(?:\\.[0-9]+)?$/.test(trimmed)) return timestampToIso(Number(trimmed));
162
+ const dt = new Date(trimmed);
163
+ return Number.isFinite(dt.getTime()) ? dt.toISOString() : null;
164
+ }
165
+ return null;
166
+ }
167
+
168
+ async function readUpdateTelemetry(filePath) {
169
+ let totalTokens = 0;
170
+ let lastEventId = null;
171
+ let lastEventTimestamp = null;
172
+ let lineNumber = 0;
173
+
174
+ try {
175
+ const input = fs.createReadStream(filePath, { encoding: 'utf8' });
176
+ const rl = readline.createInterface({ input, crlfDelay: Infinity });
177
+ try {
178
+ for await (const line of rl) {
179
+ lineNumber += 1;
180
+ if (!line || !line.trim()) continue;
181
+ let record = null;
182
+ try {
183
+ record = JSON.parse(line);
184
+ } catch {
185
+ continue;
186
+ }
187
+ const meta = record && record.params && record.params._meta ? record.params._meta : record && record._meta;
188
+ if (!meta || typeof meta !== 'object') continue;
189
+ const nextTotal = toNonNegativeFiniteNumber(meta.totalTokens);
190
+ if (nextTotal <= 0) continue;
191
+ totalTokens = Math.max(totalTokens, nextTotal);
192
+ lastEventId = meta.eventId != null ? String(meta.eventId) : String(lineNumber);
193
+ lastEventTimestamp =
194
+ timestampToIso(meta.agentTimestampMs) ||
195
+ timestampToIso(meta.timestampMs) ||
196
+ timestampToIso(record.timestamp_ms) ||
197
+ timestampToIso(record.timestamp) ||
198
+ lastEventTimestamp;
199
+ }
200
+ } finally {
201
+ rl.close();
202
+ }
203
+ } catch {
204
+ return { totalTokens, lastEventId, lastEventTimestamp };
205
+ }
206
+ return { totalTokens, lastEventId, lastEventTimestamp };
207
+ }
208
+
209
+ async function main() {
210
+ const contextTokensUsed = toNonNegativeFiniteNumber(signals.contextTokensUsed ?? signals.totalTokens);
211
+ const totalTokensBeforeCompaction = toNonNegativeFiniteNumber(signals.totalTokensBeforeCompaction);
212
+ const signalTotalTokens = totalTokensBeforeCompaction + contextTokensUsed;
213
+ const updateTelemetry = await readUpdateTelemetry(updatesPath);
214
+ const totalTokens = Math.max(updateTelemetry.totalTokens, signalTotalTokens);
147
215
  const messageCount = toNonNegativeFiniteNumber(signals.assistantMessageCount || signals.num_chat_messages);
148
216
  const model = signals.primaryModelId || (Array.isArray(signals.modelsUsed) ? signals.modelsUsed[0] : 'grok-build');
149
- const lastActive = signals.lastActiveAt || summary.updated_at || new Date().toISOString();
217
+ const lastActive = signals.lastActiveAt || updateTelemetry.lastEventTimestamp || summary.updated_at || new Date().toISOString();
150
218
 
151
219
  if (totalTokens <= 0) {
152
220
  process.exit(0);
@@ -163,8 +231,16 @@ const signal = {
163
231
  cwd: WORKSPACE_ROOT,
164
232
  model,
165
233
  totalTokens,
234
+ contextTokensUsed,
235
+ totalTokensBeforeCompaction,
166
236
  messageCount,
167
237
  lastActive,
238
+ sessionDir,
239
+ updatesPath,
240
+ signalsPath,
241
+ summaryPath,
242
+ lastEventId: updateTelemetry.lastEventId,
243
+ lastEventTimestamp: updateTelemetry.lastEventTimestamp,
168
244
  capturedAt: new Date().toISOString()
169
245
  };
170
246
 
@@ -177,6 +253,9 @@ try {
177
253
  } catch {}
178
254
 
179
255
  process.exit(0);
256
+ }
257
+
258
+ main().catch(() => process.exit(0));
180
259
  `;
181
260
  }
182
261
 
@@ -18,7 +18,7 @@
18
18
  "deepseek-v4-flash":{ "input": 0.14, "output": 0.28, "cache_read": 0.0028, "cache_write": 0.14 },
19
19
  "deepseek-v4-pro": { "input": 0.435,"output": 0.87, "cache_read": 0.003625, "cache_write": 0.435 },
20
20
  "deepseek-chat": { "input": 0.14, "output": 0.28, "cache_read": 0.0028, "cache_write": 0.14 },
21
- "grok-build": { "input": 1.25, "output": 2.50, "cache_read": 0.20, "note": "Grok Build TUI estimate. signals.json currently exposes contextTokensUsed snapshots, so TokenTracker estimates input/output split until Grok exposes per-call telemetry." },
21
+ "grok-build": { "input": 1.25, "output": 2.50, "cache_read": 0.20, "note": "Grok Build TUI estimate. Local telemetry currently exposes totalTokens without a stable prompt/output/cache split, so TokenTracker estimates input/output split until Grok exposes per-call usage details." },
22
22
  "grok-4-0709": { "input": 3.00, "output": 15.00, "cache_read": 0.75 },
23
23
  "grok-4": { "input": 3.00, "output": 15.00, "cache_read": 0.75 },
24
24
  "grok-4-latest": { "input": 3.00, "output": 15.00, "cache_read": 0.75 },