tokentracker-cli 0.37.0 → 0.39.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 (43) hide show
  1. package/dashboard/dist/assets/{ActivityHeatmap-CHyQx8kb.js → ActivityHeatmap-DDzq43pV.js} +1 -1
  2. package/dashboard/dist/assets/{Card-DnFsEPmk.js → Card-RRZhPmAq.js} +1 -1
  3. package/dashboard/dist/assets/{DashboardPage-C7Ec-LrI.js → DashboardPage-CVpa4Krq.js} +1 -1
  4. package/dashboard/dist/assets/{DevicePage-DFp21WlU.js → DevicePage-CwjjtGy7.js} +1 -1
  5. package/dashboard/dist/assets/{DialogTitle-C7CwLbYz.js → DialogTitle-Caa_Fqbe.js} +1 -1
  6. package/dashboard/dist/assets/{FadeIn-CmqK4vog.js → FadeIn-BxYlG4qs.js} +1 -1
  7. package/dashboard/dist/assets/{HeaderGithubStar-Cu0-A7WB.js → HeaderGithubStar-Drsogiil.js} +1 -1
  8. package/dashboard/dist/assets/{IpCheckPage-Ct9pgqbB.js → IpCheckPage-23cwrKD6.js} +1 -1
  9. package/dashboard/dist/assets/{LandingPage-Bb4n4XoT.js → LandingPage-CVX86qgQ.js} +1 -1
  10. package/dashboard/dist/assets/{LeaderboardAvatar-snuxOX5K.js → LeaderboardAvatar-BsUf4diw.js} +1 -1
  11. package/dashboard/dist/assets/{LeaderboardPage-B-kigZ8J.js → LeaderboardPage-BlRsDd1Y.js} +3 -3
  12. package/dashboard/dist/assets/{LeaderboardProfileModal-OqodFCGv.js → LeaderboardProfileModal-Bvz_sBn-.js} +1 -1
  13. package/dashboard/dist/assets/{LeaderboardProfilePage-BspW68Xd.js → LeaderboardProfilePage-Bidu8F3I.js} +1 -1
  14. package/dashboard/dist/assets/{LimitsPage-CS2NBEIP.js → LimitsPage-DlZbIhSx.js} +1 -1
  15. package/dashboard/dist/assets/{LocalOnlyNotice-D0XXnH8l.js → LocalOnlyNotice-p9TjRIic.js} +1 -1
  16. package/dashboard/dist/assets/{LoginPage-8S9O3Gsb.js → LoginPage-CgJ62gMn.js} +1 -1
  17. package/dashboard/dist/assets/{PopoverPopup-BJb6Du8x.js → PopoverPopup-CyUrMrGa.js} +1 -1
  18. package/dashboard/dist/assets/{Select-DksFSHRb.js → Select-Bcduv8la.js} +1 -1
  19. package/dashboard/dist/assets/{SelectItemText-DmE9ZyEL.js → SelectItemText-lRVDxgrq.js} +1 -1
  20. package/dashboard/dist/assets/{SettingsPage-BDRMT04n.js → SettingsPage-C2Qi3eZI.js} +1 -1
  21. package/dashboard/dist/assets/{SkillsPage-DPHemuRK.js → SkillsPage-x2Y3gIqM.js} +1 -1
  22. package/dashboard/dist/assets/{WidgetsPage-BOX3tPr8.js → WidgetsPage-Cbk85G0Y.js} +1 -1
  23. package/dashboard/dist/assets/{WrappedPage-DWVJ6uf1.js → WrappedPage-CaFHKfBB.js} +1 -1
  24. package/dashboard/dist/assets/{agent-logos-C2IT3yLV.js → agent-logos-ADrK4Olm.js} +1 -1
  25. package/dashboard/dist/assets/{arrow-up-right-CnvG5D97.js → arrow-up-right-H5gfqSzn.js} +1 -1
  26. package/dashboard/dist/assets/{download-C1zysMDD.js → download-BnlEkhpl.js} +1 -1
  27. package/dashboard/dist/assets/{info-DnI8z26t.js → info-D3HmZNML.js} +1 -1
  28. package/dashboard/dist/assets/{main-BpChBpB7.js → main-C52dR5OB.js} +2 -2
  29. package/dashboard/dist/assets/{use-limits-display-prefs-sb2nFpV_.js → use-limits-display-prefs-Dispof5a.js} +1 -1
  30. package/dashboard/dist/assets/{use-native-settings-DWh-bpfp.js → use-native-settings-BWsO3g0J.js} +1 -1
  31. package/dashboard/dist/assets/{use-usage-limits-DLSUBzW9.js → use-usage-limits-DyFBw4HA.js} +1 -1
  32. package/dashboard/dist/assets/{useCurrency-B1Aa7Unc.js → useCurrency-DBRwXqVn.js} +1 -1
  33. package/dashboard/dist/assets/{useOpenInteractionType-2Q4xhr62.js → useOpenInteractionType-TVDd89tp.js} +1 -1
  34. package/dashboard/dist/index.html +1 -1
  35. package/dashboard/dist/share.html +1 -1
  36. package/package.json +1 -1
  37. package/src/commands/init.js +3 -3
  38. package/src/commands/status.js +9 -4
  39. package/src/commands/sync.js +26 -0
  40. package/src/lib/pricing/curated-overrides.json +1 -0
  41. package/src/lib/pricing/seed-snapshot.json +1 -1
  42. package/src/lib/rollout.js +208 -0
  43. package/src/lib/usage-limits.js +15 -1
@@ -4291,6 +4291,210 @@ async function parseKimiIncremental({ wireFiles, cursors, queuePath, onProgress,
4291
4291
  return { recordsProcessed, eventsAggregated, bucketsQueued };
4292
4292
  }
4293
4293
 
4294
+ // ─────────────────────────────────────────────────────────────────────────────
4295
+ // Kimi Code (official @moonshot-ai/kimi-code) — passive JSONL reader.
4296
+ //
4297
+ // Distinct from the legacy community `kimi-cli` above (Python, ~/.kimi). The
4298
+ // official single-binary product stores under ~/.kimi-code/ with a different
4299
+ // session layout and wire protocol:
4300
+ //
4301
+ // ~/.kimi-code/sessions/<wd_dir_hash>/<session_id>/agents/<name>/wire.jsonl
4302
+ //
4303
+ // proto 1.x events are namespaced and carry `type` at the top level. Per-step
4304
+ // token usage rides on a `step.end` loop event (wrapped in
4305
+ // `context.append_loop_event`) with an Anthropic-style usage object:
4306
+ //
4307
+ // {"type":"context.append_loop_event",
4308
+ // "event":{"type":"step.end","uuid":"<stepUuid>","turnId":"..","step":N,
4309
+ // "usage":{"input_tokens":N,"output_tokens":N,
4310
+ // "cache_read_input_tokens":N,"cache_creation_input_tokens":N}},
4311
+ // "time":<epoch_ms>}
4312
+ //
4313
+ // Model comes from the per-session `config.update` event's `modelAlias`
4314
+ // (e.g. "kimi-code/kimi-k2.6" -> "kimi-k2.6"). Emitted under source "kimi" so
4315
+ // new + legacy sessions aggregate together. Independent cursor (cursors.kimiCode)
4316
+ // keeps state from colliding with the legacy reader's cursors.kimi.
4317
+ function resolveKimiCodeHome(env = process.env) {
4318
+ const home = require("node:os").homedir();
4319
+ const explicit = typeof env?.KIMI_CODE_HOME === "string" ? env.KIMI_CODE_HOME.trim() : "";
4320
+ return explicit ? path.resolve(explicit) : path.join(home, ".kimi-code");
4321
+ }
4322
+
4323
+ function resolveKimiCodeWireFiles(env = process.env) {
4324
+ const sessionsDir = path.join(resolveKimiCodeHome(env), "sessions");
4325
+ if (!fssync.existsSync(sessionsDir)) return [];
4326
+ const files = [];
4327
+ const walk = (dir, depth) => {
4328
+ if (depth > 5) return;
4329
+ let entries;
4330
+ try { entries = fssync.readdirSync(dir, { withFileTypes: true }); } catch { return; }
4331
+ for (const ent of entries) {
4332
+ const full = path.join(dir, ent.name);
4333
+ if (ent.isDirectory()) walk(full, depth + 1);
4334
+ else if (ent.name === "wire.jsonl") files.push(full);
4335
+ }
4336
+ };
4337
+ walk(sessionsDir, 0);
4338
+ return files;
4339
+ }
4340
+
4341
+ function resolveKimiCodeDefaultModel(env = process.env) {
4342
+ const fallback = "kimi-for-coding";
4343
+ try {
4344
+ const cfgPath = path.join(resolveKimiCodeHome(env), "config.toml");
4345
+ const raw = fssync.readFileSync(cfgPath, "utf8");
4346
+ const m = raw.match(/^\s*default_model\s*=\s*"([^"]+)"/m);
4347
+ if (!m) return fallback;
4348
+ return m[1].includes("/") ? m[1].split("/").pop() : m[1] || fallback;
4349
+ } catch {
4350
+ return fallback;
4351
+ }
4352
+ }
4353
+
4354
+ function kimiCodeModelAlias(value) {
4355
+ if (typeof value !== "string" || !value) return null;
4356
+ return value.includes("/") ? value.split("/").pop() : value;
4357
+ }
4358
+
4359
+ async function parseKimiCodeIncremental({ wireFiles, cursors, queuePath, onProgress, env, model } = {}) {
4360
+ await ensureDir(path.dirname(queuePath));
4361
+ const state = cursors.kimiCode && typeof cursors.kimiCode === "object" ? cursors.kimiCode : {};
4362
+ const seenIds = new Set(Array.isArray(state.seenIds) ? state.seenIds : []);
4363
+ const fileOffsets =
4364
+ state.fileOffsets && typeof state.fileOffsets === "object" ? { ...state.fileOffsets } : {};
4365
+
4366
+ const files = Array.isArray(wireFiles) ? wireFiles : resolveKimiCodeWireFiles(env || process.env);
4367
+ const fallbackModel = model || resolveKimiCodeDefaultModel(env || process.env);
4368
+ if (files.length === 0) {
4369
+ cursors.kimiCode = { ...state, seenIds: Array.from(seenIds), fileOffsets, updatedAt: new Date().toISOString() };
4370
+ return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
4371
+ }
4372
+
4373
+ const hourlyState = normalizeHourlyState(cursors?.hourly);
4374
+ const touchedBuckets = new Set();
4375
+ const cb = typeof onProgress === "function" ? onProgress : null;
4376
+ let recordsProcessed = 0;
4377
+ let eventsAggregated = 0;
4378
+
4379
+ for (let fileIdx = 0; fileIdx < files.length; fileIdx++) {
4380
+ const filePath = files[fileIdx];
4381
+ let stat;
4382
+ try { stat = fssync.statSync(filePath); } catch { continue; }
4383
+
4384
+ const prevEntry = fileOffsets[filePath] || {};
4385
+ const prevSize = Number(prevEntry.size) || 0;
4386
+ const prevIno = prevEntry.ino;
4387
+ const inodeChanged = typeof prevIno === "number" && prevIno !== stat.ino;
4388
+ const startOffset = stat.size < prevSize || inodeChanged ? 0 : prevSize;
4389
+ // Model is declared in a `config.update` near the file head; persist it on
4390
+ // the cursor so incremental resumes (which start past that line) keep it.
4391
+ let fileModel = (typeof prevEntry.model === "string" && prevEntry.model) || fallbackModel;
4392
+ if (stat.size <= startOffset) continue;
4393
+
4394
+ let stream;
4395
+ try {
4396
+ stream = fssync.createReadStream(filePath, { encoding: "utf8", start: startOffset });
4397
+ } catch { continue; }
4398
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
4399
+
4400
+ for await (const line of rl) {
4401
+ if (!line || !line.trim()) continue;
4402
+ let entry;
4403
+ try { entry = JSON.parse(line); } catch { continue; }
4404
+
4405
+ if (entry.type === "config.update") {
4406
+ const alias = kimiCodeModelAlias(entry.modelAlias);
4407
+ if (alias) fileModel = alias;
4408
+ continue;
4409
+ }
4410
+
4411
+ const evt =
4412
+ entry.type === "context.append_loop_event" && entry.event && typeof entry.event === "object"
4413
+ ? entry.event
4414
+ : entry;
4415
+ if (!evt || evt.type !== "step.end") continue;
4416
+ const usage = evt.usage;
4417
+ if (!usage || typeof usage !== "object") continue;
4418
+ const id = evt.uuid;
4419
+ if (!id || seenIds.has(id)) continue;
4420
+
4421
+ recordsProcessed++;
4422
+
4423
+ // Anthropic-style usage: input_tokens already excludes cache. OpenAI-compat
4424
+ // models instead fold cached reads into input_tokens and expose them via
4425
+ // input_tokens_details.cached_tokens — mirror kimi-code's own extractUsage
4426
+ // so we never double-count cache as fresh input.
4427
+ const cacheCreation = toNonNegativeInt(usage.cache_creation_input_tokens);
4428
+ let cacheRead;
4429
+ let input;
4430
+ if (usage.cache_read_input_tokens != null) {
4431
+ cacheRead = toNonNegativeInt(usage.cache_read_input_tokens);
4432
+ input = toNonNegativeInt(usage.input_tokens);
4433
+ } else {
4434
+ const details =
4435
+ usage.input_tokens_details && typeof usage.input_tokens_details === "object"
4436
+ ? usage.input_tokens_details
4437
+ : null;
4438
+ const cached = toNonNegativeInt(details ? details.cached_tokens : 0);
4439
+ cacheRead = cached;
4440
+ input = Math.max(0, toNonNegativeInt(usage.input_tokens) - cached);
4441
+ }
4442
+ const output = toNonNegativeInt(usage.output_tokens);
4443
+ if (input === 0 && output === 0 && cacheRead === 0 && cacheCreation === 0) {
4444
+ seenIds.add(id);
4445
+ continue;
4446
+ }
4447
+
4448
+ const ms = entry.time ?? evt.time;
4449
+ if (ms == null || !Number.isFinite(Number(ms))) continue;
4450
+ const tsIso = new Date(Number(ms)).toISOString();
4451
+ const bucketStart = toUtcHalfHourStart(tsIso);
4452
+ if (!bucketStart) continue;
4453
+
4454
+ const delta = {
4455
+ input_tokens: input,
4456
+ cached_input_tokens: cacheRead,
4457
+ cache_creation_input_tokens: cacheCreation,
4458
+ output_tokens: output,
4459
+ reasoning_output_tokens: 0,
4460
+ total_tokens: input + output + cacheRead + cacheCreation,
4461
+ conversation_count: 1,
4462
+ };
4463
+
4464
+ const bucket = getHourlyBucket(hourlyState, "kimi", fileModel, bucketStart);
4465
+ addTotals(bucket.totals, delta);
4466
+ touchedBuckets.add(bucketKey("kimi", fileModel, bucketStart));
4467
+ seenIds.add(id);
4468
+ eventsAggregated++;
4469
+
4470
+ if (cb) {
4471
+ cb({
4472
+ index: fileIdx + 1,
4473
+ total: files.length,
4474
+ recordsProcessed,
4475
+ eventsAggregated,
4476
+ bucketsQueued: touchedBuckets.size,
4477
+ });
4478
+ }
4479
+ }
4480
+
4481
+ let postStat = stat;
4482
+ try { postStat = fssync.statSync(filePath); } catch {}
4483
+ fileOffsets[filePath] = { size: postStat.size, mtimeMs: postStat.mtimeMs, ino: postStat.ino, model: fileModel };
4484
+ }
4485
+
4486
+ const seenArr = Array.from(seenIds);
4487
+ const cappedSeen = seenArr.length > 10_000 ? seenArr.slice(seenArr.length - 10_000) : seenArr;
4488
+
4489
+ const bucketsQueued = await enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets });
4490
+ const updatedAt = new Date().toISOString();
4491
+ hourlyState.updatedAt = updatedAt;
4492
+ cursors.hourly = hourlyState;
4493
+ cursors.kimiCode = { ...state, seenIds: cappedSeen, fileOffsets, updatedAt };
4494
+
4495
+ return { recordsProcessed, eventsAggregated, bucketsQueued };
4496
+ }
4497
+
4294
4498
  // ─────────────────────────────────────────────────────────────────────────────
4295
4499
  // CodeBuddy CLI — passive JSONL reader (~/.codebuddy/projects/<cwd>/<sid>.jsonl)
4296
4500
  //
@@ -8152,6 +8356,10 @@ module.exports = {
8152
8356
  resolveKimiWireFiles,
8153
8357
  resolveKimiDefaultModel,
8154
8358
  parseKimiIncremental,
8359
+ resolveKimiCodeHome,
8360
+ resolveKimiCodeWireFiles,
8361
+ resolveKimiCodeDefaultModel,
8362
+ parseKimiCodeIncremental,
8155
8363
  resolveCodebuddyHome,
8156
8364
  resolveCodebuddyProjectFiles,
8157
8365
  resolveCodebuddyDefaultModel,
@@ -298,7 +298,21 @@ async function fetchCursorLimits({ home, fetchImpl = fetch } = {}) {
298
298
 
299
299
  function resolveKimiHome({ home, env } = {}) {
300
300
  const explicit = typeof env?.KIMI_HOME === "string" ? env.KIMI_HOME.trim() : "";
301
- return explicit ? path.resolve(explicit) : path.join(home || os.homedir(), ".kimi");
301
+ if (explicit) return path.resolve(explicit);
302
+ const base = home || os.homedir();
303
+ // Prefer the official Kimi Code (@moonshot-ai/kimi-code, ~/.kimi-code) when it
304
+ // holds a login — its credential file shape (kimi-code.json) and the
305
+ // auth/usages endpoints are identical to the legacy kimi-cli (~/.kimi), so the
306
+ // existing fetch path works unchanged. Fall back to legacy when kimi-code has
307
+ // no credentials, keeping old kimi-cli users untouched.
308
+ const explicitCode = typeof env?.KIMI_CODE_HOME === "string" ? env.KIMI_CODE_HOME.trim() : "";
309
+ const codeHome = explicitCode ? path.resolve(explicitCode) : path.join(base, ".kimi-code");
310
+ const codeCredsPath = path.join(codeHome, "credentials", "kimi-code.json");
311
+ try {
312
+ const raw = fs.readFileSync(codeCredsPath, "utf8").trim();
313
+ if (raw && JSON.parse(raw)?.access_token) return codeHome;
314
+ } catch { /* missing / empty / corrupt — fall through to legacy */ }
315
+ return path.join(base, ".kimi");
302
316
  }
303
317
 
304
318
  function loadKimiCredentials({ home, env } = {}) {