tokentracker-cli 0.36.1 → 0.38.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.
- package/dashboard/dist/assets/{ActivityHeatmap-DPmiMH4Q.js → ActivityHeatmap-DN2mmuWt.js} +1 -1
- package/dashboard/dist/assets/{Card-BlIsxuah.js → Card-eNDH0TxH.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-CzS5TxMe.js +19 -0
- package/dashboard/dist/assets/{DevicePage-CzlnwSOX.js → DevicePage-DEVImOIW.js} +1 -1
- package/dashboard/dist/assets/DialogTitle-BJKAi66j.js +1 -0
- package/dashboard/dist/assets/{FadeIn-DCdF-JKA.js → FadeIn-DFk4dbUG.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-C1g-qMrd.js → HeaderGithubStar-D-r1KB-R.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BPNjooka.js → IpCheckPage-D4FmaQyH.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-BDwl_l3N.js → LandingPage-DLwmI1xA.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-V0mkCkzN.js → LeaderboardAvatar-DDZO2W2u.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-c2ebFIDI.js → LeaderboardPage-DcdnX8ha.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfileModal-DHdkIr5W.js → LeaderboardProfileModal-D3PwKHWY.js} +3 -3
- package/dashboard/dist/assets/LeaderboardProfilePage-CnAg45qs.js +1 -0
- package/dashboard/dist/assets/{LimitsPage-rc2lvN6C.js → LimitsPage-onwdR9lM.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-ChC9Z6ZN.js → LocalOnlyNotice-CbVonMVs.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-B6vOBqIw.js → LoginPage-DZpN9E7q.js} +1 -1
- package/dashboard/dist/assets/PopoverPopup-DCGh4euQ.js +1 -0
- package/dashboard/dist/assets/Select-aIEVSJVR.js +1 -0
- package/dashboard/dist/assets/SelectItemText-BH_vsFMS.js +1 -0
- package/dashboard/dist/assets/SettingsPage-D2jlgb1x.js +1 -0
- package/dashboard/dist/assets/SkillsPage-CTVaKIo2.js +1 -0
- package/dashboard/dist/assets/WidgetsPage-B67l27g8.js +1 -0
- package/dashboard/dist/assets/{WrappedPage-CXYvtLhV.js → WrappedPage-wY4L8E_D.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-DjQzMdSw.js → agent-logos-Bfb8Gcna.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-OURL9IAS.js → arrow-up-right-RH7MeYHL.js} +1 -1
- package/dashboard/dist/assets/{download-C1FtauDf.js → download-Ds1ViaQ9.js} +1 -1
- package/dashboard/dist/assets/{info-BTIZccVB.js → info-DD3Xh30V.js} +1 -1
- package/dashboard/dist/assets/{main-QElo_0bY.js → main-BZBATt4l.js} +2 -2
- package/dashboard/dist/assets/main-G_F9Iu_x.css +1 -0
- package/dashboard/dist/assets/{use-limits-display-prefs-ClE847BF.js → use-limits-display-prefs-C9k19eUo.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-D20qXg2x.js → use-native-settings-HjLLYUkn.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-CIWlA4Jt.js → use-usage-limits-iJ3HJWfz.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-CUAxzIqS.js → useCurrency-Cr1NSPgK.js} +1 -1
- package/dashboard/dist/assets/useOpenInteractionType-B65bQ4-n.js +12 -0
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/commands/init.js +3 -3
- package/src/commands/status.js +9 -4
- package/src/commands/sync.js +26 -0
- package/src/lib/pricing/curated-overrides.json +1 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +208 -0
- package/src/lib/usage-limits.js +15 -1
- package/dashboard/dist/assets/DashboardPage-VF6RdezJ.js +0 -19
- package/dashboard/dist/assets/DialogTitle-Ds8Zyp-H.js +0 -12
- package/dashboard/dist/assets/LeaderboardProfilePage-xWY6PJI0.js +0 -1
- package/dashboard/dist/assets/PopoverPopup-DRWRI_6O.js +0 -1
- package/dashboard/dist/assets/SettingsPage-ZDWZ0OtH.js +0 -1
- package/dashboard/dist/assets/SkillsPage-CAJxaQG0.js +0 -1
- package/dashboard/dist/assets/WidgetsPage-BkZjVQSS.js +0 -1
- package/dashboard/dist/assets/check-CESu4eR-.js +0 -1
- package/dashboard/dist/assets/chevron-down-BjIFZNid.js +0 -1
- package/dashboard/dist/assets/main-Ctn8FyBC.css +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -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,
|
package/src/lib/usage-limits.js
CHANGED
|
@@ -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
|
-
|
|
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 } = {}) {
|