tokentracker-cli 0.12.0 → 0.13.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/README.md +4 -2
- package/dashboard/dist/assets/{main-CjKIAPGE.js → main-Dc116CZl.js} +3 -1
- package/dashboard/dist/brand-logos/kilo.svg +4 -0
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +2 -2
- package/src/commands/init.js +28 -0
- package/src/commands/status.js +18 -0
- package/src/commands/sync.js +81 -8
- package/src/lib/claude-categorizer.js +5 -8
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +272 -6
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M512 0H0V512H512V0Z" fill="black"/>
|
|
3
|
+
<path d="M512 512H0V0H512V512ZM322.783 322.784H278.261V392.747L308.472 422.958H378.435V378.437H322.782L322.783 322.784ZM422.957 308.474L392.746 278.263H322.783V322.784H378.435L378.435 378.437H422.957L422.957 308.474ZM233.739 278.263H189.217V322.784H233.739V278.263ZM89.0435 392.747L119.254 422.958H233.739V378.437H133.565V278.263H89.043L89.0435 392.747ZM372.538 189.217V119.254L342.327 89.0435H278.261V133.565H328.017V189.217H278.261V233.739H422.957V189.217H372.538ZM133.565 89.0435H89.0435V233.739H133.565V183.652H189.218V233.739H233.74V183.652L189.218 139.13H133.565V89.0435ZM233.739 89.0435H189.217L189.218 139.13H233.739V89.0435Z" fill="#FAF74F"/>
|
|
4
|
+
</svg>
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
]
|
|
211
211
|
}
|
|
212
212
|
</script>
|
|
213
|
-
<script type="module" crossorigin src="/assets/main-
|
|
213
|
+
<script type="module" crossorigin src="/assets/main-Dc116CZl.js"></script>
|
|
214
214
|
<link rel="stylesheet" crossorigin href="/assets/main-B-qohcBn.css">
|
|
215
215
|
</head>
|
|
216
216
|
<body>
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"description": "Shareable Token Tracker dashboard snapshot."
|
|
52
52
|
}
|
|
53
53
|
</script>
|
|
54
|
-
<script type="module" crossorigin src="/assets/main-
|
|
54
|
+
<script type="module" crossorigin src="/assets/main-Dc116CZl.js"></script>
|
|
55
55
|
<link rel="stylesheet" crossorigin href="/assets/main-B-qohcBn.css">
|
|
56
56
|
</head>
|
|
57
57
|
<body>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokentracker-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Token usage tracker for AI agent CLIs (Claude Code, Codex, Cursor, Gemini, Kiro, OpenCode, OpenClaw, Every Code, Hermes, GitHub Copilot, Kimi Code, CodeBuddy, oh-my-pi, pi, Craft Agents)",
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"description": "Token usage tracker for AI agent CLIs (Claude Code, Codex, Cursor, Gemini, Kiro, OpenCode, OpenClaw, Every Code, Hermes, GitHub Copilot, Kimi Code, CodeBuddy, oh-my-pi, pi, Craft Agents, Kilo CLI, Kilo Code)",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"tokentracker-cli": "bin/tracker.js",
|
package/src/commands/init.js
CHANGED
|
@@ -459,6 +459,34 @@ async function applyIntegrationSetup({ home, trackerDir, notifyPath, notifyOrigi
|
|
|
459
459
|
}
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
+
// Kilo CLI (kilo.ai @kilocode/plugin): passive reader — no hook installation
|
|
463
|
+
// needed. Reuses OpenCode-fork SQLite schema at ~/.local/share/kilo/kilo.db
|
|
464
|
+
// (override via KILO_HOME).
|
|
465
|
+
{
|
|
466
|
+
const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
|
|
467
|
+
const kiloHome = process.env.KILO_HOME || path.join(xdgDataHome, "kilo");
|
|
468
|
+
const kiloDbPath = path.join(kiloHome, "kilo.db");
|
|
469
|
+
if (fssync.existsSync(kiloDbPath)) {
|
|
470
|
+
summary.push({ label: "Kilo CLI", status: "detected", detail: "Passive reader (no hook needed)" });
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Kilo Code VS Code extension (kilocode.kilo-code): passive reader — no hook
|
|
475
|
+
// installation needed. Scans ui_messages.json under every detected VS Code-
|
|
476
|
+
// family install (Code, Cursor, CodeBuddy, Windsurf, …).
|
|
477
|
+
{
|
|
478
|
+
const { resolveKilocodeTaskFiles } = require("../lib/rollout");
|
|
479
|
+
const taskFiles = resolveKilocodeTaskFiles(process.env);
|
|
480
|
+
if (taskFiles.length > 0) {
|
|
481
|
+
const ides = Array.from(new Set(taskFiles.map((t) => t.ide))).join(", ");
|
|
482
|
+
summary.push({
|
|
483
|
+
label: "Kilo Code (VS Code extension)",
|
|
484
|
+
status: "detected",
|
|
485
|
+
detail: `Passive reader · ${taskFiles.length} task${taskFiles.length !== 1 ? "s" : ""} in ${ides}`,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
462
490
|
// CodeBuddy: Claude-Code fork. Install the SessionEnd hook so finished
|
|
463
491
|
// sessions trigger notify.cjs → tracker sync; passive scan still runs as a
|
|
464
492
|
// safety net for sessions that don't fire SessionEnd cleanly.
|
package/src/commands/status.js
CHANGED
|
@@ -46,6 +46,7 @@ const {
|
|
|
46
46
|
piAgentDirCollidesWithOmp,
|
|
47
47
|
resolveCraftSessionFiles,
|
|
48
48
|
resolveCraftConfigDir,
|
|
49
|
+
resolveKilocodeTaskFiles,
|
|
49
50
|
} = require("../lib/rollout");
|
|
50
51
|
|
|
51
52
|
async function cmdStatus(argv = []) {
|
|
@@ -200,6 +201,17 @@ async function cmdStatus(argv = []) {
|
|
|
200
201
|
const craftInstalled = fssync.existsSync(craftConfigDir);
|
|
201
202
|
const craftFiles = craftInstalled ? resolveCraftSessionFiles(process.env) : [];
|
|
202
203
|
|
|
204
|
+
// Kilo CLI (kilo.ai @kilocode/plugin) — passive scan of kilo.db.
|
|
205
|
+
const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
|
|
206
|
+
const kiloHome = process.env.KILO_HOME || path.join(xdgDataHome, "kilo");
|
|
207
|
+
const kiloDbPath = path.join(kiloHome, "kilo.db");
|
|
208
|
+
const kiloInstalled = fssync.existsSync(kiloDbPath);
|
|
209
|
+
|
|
210
|
+
// Kilo Code VS Code extension — passive scan of all VS Code-family
|
|
211
|
+
// globalStorage/kilocode.kilo-code/tasks/ ui_messages.json files.
|
|
212
|
+
const kilocodeTaskFiles = resolveKilocodeTaskFiles(process.env);
|
|
213
|
+
const kilocodeInstalled = kilocodeTaskFiles.length > 0;
|
|
214
|
+
|
|
203
215
|
const copilotToken = readCopilotOauthToken({ home });
|
|
204
216
|
const copilotOtel = describeCopilotOtelStatus({ home, env: process.env });
|
|
205
217
|
const copilotLines = formatCopilotLines({
|
|
@@ -247,6 +259,12 @@ async function cmdStatus(argv = []) {
|
|
|
247
259
|
craftInstalled
|
|
248
260
|
? `- Craft Agents: passive reader (${craftFiles.length} session jsonl file${craftFiles.length !== 1 ? "s" : ""} found)`
|
|
249
261
|
: null,
|
|
262
|
+
kiloInstalled
|
|
263
|
+
? `- Kilo CLI: passive reader (${kiloDbPath})`
|
|
264
|
+
: null,
|
|
265
|
+
kilocodeInstalled
|
|
266
|
+
? `- Kilo Code (VS Code extension): passive reader (${kilocodeTaskFiles.length} task${kilocodeTaskFiles.length !== 1 ? "s" : ""} across ${new Set(kilocodeTaskFiles.map((t) => t.ide)).size} IDE${new Set(kilocodeTaskFiles.map((t) => t.ide)).size !== 1 ? "s" : ""})`
|
|
267
|
+
: null,
|
|
250
268
|
...copilotLines,
|
|
251
269
|
...subscriptionLines,
|
|
252
270
|
"",
|
package/src/commands/sync.js
CHANGED
|
@@ -40,9 +40,12 @@ const {
|
|
|
40
40
|
resolveKiroCliSessionFiles,
|
|
41
41
|
resolveKiroCliDbPath,
|
|
42
42
|
parseKiroCliIncremental,
|
|
43
|
+
resolveKilocodeTaskFiles,
|
|
44
|
+
parseKilocodeIncremental,
|
|
43
45
|
bucketKey,
|
|
44
46
|
totalsKey,
|
|
45
47
|
groupBucketKey,
|
|
48
|
+
claudeMessageDedupKey,
|
|
46
49
|
} = require("../lib/rollout");
|
|
47
50
|
const { computeClaudeGroundTruthBuckets } = require("../lib/claude-categorizer");
|
|
48
51
|
const { createProgress, renderBar, formatNumber, formatBytes } = require("../lib/progress");
|
|
@@ -83,7 +86,16 @@ const CLAUDE_MEM_OBSERVER_PATH_SEGMENT = "--claude-mem-observer-sessions";
|
|
|
83
86
|
// Project Usage panel stayed inflated. v3 drops every claude /
|
|
84
87
|
// claude-mem row from project.queue.jsonl too, and resets the
|
|
85
88
|
// matching cursors.projectHourly + project.queue.state offset.
|
|
86
|
-
|
|
89
|
+
// v4 fixes the dedup short-circuit (issue #64): v3's ground-truth scan
|
|
90
|
+
// itself used `if (msgId && reqId)` to build the dedup key, which silently
|
|
91
|
+
// disabled dedup for any provider whose jsonl entries lack `requestId`
|
|
92
|
+
// (DeepSeek/Kimi/Mimo/MiniMax anthropic-compatible endpoints, plus Claude
|
|
93
|
+
// Code's sub-agent / thinking transport paths). The repaired ground truth
|
|
94
|
+
// was therefore inflated by 1.6–3.7x on those providers — v3 left it that
|
|
95
|
+
// way. v4 re-runs the same five-step atomic repair against the corrected
|
|
96
|
+
// `claudeMessageDedupKey()` (msgId is globally unique on its own per the
|
|
97
|
+
// Anthropic protocol, so the reqId requirement was always unnecessary).
|
|
98
|
+
const CLAUDE_GROUND_TRUTH_REPAIR_KEY = "claudeGroundTruthRepair_2026_05_v4";
|
|
87
99
|
|
|
88
100
|
async function cmdSync(argv) {
|
|
89
101
|
const opts = parseArgs(argv);
|
|
@@ -123,6 +135,7 @@ async function cmdSync(argv) {
|
|
|
123
135
|
const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
|
|
124
136
|
const opencodeHome = process.env.OPENCODE_HOME || path.join(xdgDataHome, "opencode");
|
|
125
137
|
const opencodeStorageDir = path.join(opencodeHome, "storage");
|
|
138
|
+
const kiloHome = process.env.KILO_HOME || path.join(xdgDataHome, "kilo");
|
|
126
139
|
|
|
127
140
|
// OpenClaw hook integration: allow a hook to request incremental parsing for a single session jsonl.
|
|
128
141
|
// We still parse all regular sources so model/source attribution stays complete (e.g. Kimi sessions).
|
|
@@ -314,6 +327,63 @@ async function cmdSync(argv) {
|
|
|
314
327
|
opencodeResult.bucketsQueued += opencodeDbResult.bucketsQueued;
|
|
315
328
|
}
|
|
316
329
|
|
|
330
|
+
// ── Kilo CLI (kilo.ai @kilocode/plugin — OpenCode-fork SQLite) ──
|
|
331
|
+
// Uses the exact same `message` table schema as OpenCode v1.2+. We reuse
|
|
332
|
+
// the OpenCode DB reader/parser, just with a separate cursor namespace so
|
|
333
|
+
// the message indexes don't collide.
|
|
334
|
+
const kiloDbPath = path.join(kiloHome, "kilo.db");
|
|
335
|
+
let kiloResult = { messagesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
336
|
+
const kiloDbMessages = readOpencodeDbMessages(kiloDbPath);
|
|
337
|
+
if (kiloDbMessages.length > 0) {
|
|
338
|
+
if (progress?.enabled) {
|
|
339
|
+
progress.start(
|
|
340
|
+
`Parsing Kilo CLI ${renderBar(0)} 0/${formatNumber(kiloDbMessages.length)} msgs | buckets 0`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
kiloResult = await parseOpencodeDbIncremental({
|
|
344
|
+
dbMessages: kiloDbMessages,
|
|
345
|
+
cursors,
|
|
346
|
+
queuePath,
|
|
347
|
+
projectQueuePath,
|
|
348
|
+
onProgress: (p) => {
|
|
349
|
+
if (!progress?.enabled) return;
|
|
350
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
351
|
+
progress.update(
|
|
352
|
+
`Parsing Kilo CLI ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
|
|
353
|
+
p.total,
|
|
354
|
+
)} msgs | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
355
|
+
);
|
|
356
|
+
},
|
|
357
|
+
source: "kilo-cli",
|
|
358
|
+
cursorKey: "kiloCli",
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ── Kilo Code VS Code extension (Cline-style ui_messages.json) ──
|
|
363
|
+
const kilocodeTaskFiles = resolveKilocodeTaskFiles(process.env);
|
|
364
|
+
let kilocodeResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
365
|
+
if (kilocodeTaskFiles.length > 0) {
|
|
366
|
+
if (progress?.enabled) {
|
|
367
|
+
progress.start(
|
|
368
|
+
`Parsing Kilo Code ${renderBar(0)} 0/${formatNumber(kilocodeTaskFiles.length)} tasks | buckets 0`,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
kilocodeResult = await parseKilocodeIncremental({
|
|
372
|
+
taskFiles: kilocodeTaskFiles,
|
|
373
|
+
cursors,
|
|
374
|
+
queuePath,
|
|
375
|
+
onProgress: (p) => {
|
|
376
|
+
if (!progress?.enabled) return;
|
|
377
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
378
|
+
progress.update(
|
|
379
|
+
`Parsing Kilo Code ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
|
|
380
|
+
p.total,
|
|
381
|
+
)} tasks | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
382
|
+
);
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
317
387
|
// ── Cursor (API-based) ──
|
|
318
388
|
// One-time migration: earlier CLI versions mis-parsed the Cursor CSV after
|
|
319
389
|
// Cursor inserted new "Cloud Agent ID"/"Automation ID" columns, writing
|
|
@@ -679,7 +749,9 @@ async function cmdSync(argv) {
|
|
|
679
749
|
ompResult.recordsProcessed +
|
|
680
750
|
piResult.recordsProcessed +
|
|
681
751
|
craftResult.recordsProcessed +
|
|
682
|
-
copilotResult.recordsProcessed
|
|
752
|
+
copilotResult.recordsProcessed +
|
|
753
|
+
kiloResult.messagesProcessed +
|
|
754
|
+
kilocodeResult.recordsProcessed;
|
|
683
755
|
const totalBuckets =
|
|
684
756
|
parseResult.bucketsQueued +
|
|
685
757
|
openclawResult.bucketsQueued +
|
|
@@ -695,7 +767,9 @@ async function cmdSync(argv) {
|
|
|
695
767
|
ompResult.bucketsQueued +
|
|
696
768
|
piResult.bucketsQueued +
|
|
697
769
|
craftResult.bucketsQueued +
|
|
698
|
-
copilotResult.bucketsQueued
|
|
770
|
+
copilotResult.bucketsQueued +
|
|
771
|
+
kiloResult.bucketsQueued +
|
|
772
|
+
kilocodeResult.bucketsQueued;
|
|
699
773
|
process.stdout.write(
|
|
700
774
|
[
|
|
701
775
|
"Sync finished:",
|
|
@@ -1394,7 +1468,7 @@ async function repairClaudeQueueFromGroundTruth({
|
|
|
1394
1468
|
}
|
|
1395
1469
|
uploadState.offset = 0;
|
|
1396
1470
|
uploadState.updatedAt = new Date().toISOString();
|
|
1397
|
-
uploadState.note = "
|
|
1471
|
+
uploadState.note = "reset_after_claude_repair_2026_05_v4";
|
|
1398
1472
|
await fs.writeFile(queueStatePath, JSON.stringify(uploadState));
|
|
1399
1473
|
}
|
|
1400
1474
|
|
|
@@ -1461,7 +1535,7 @@ async function repairClaudeQueueFromGroundTruth({
|
|
|
1461
1535
|
}
|
|
1462
1536
|
st.offset = 0;
|
|
1463
1537
|
st.updatedAt = new Date().toISOString();
|
|
1464
|
-
st.note = "
|
|
1538
|
+
st.note = "reset_after_claude_repair_2026_05_v4";
|
|
1465
1539
|
await fs.writeFile(projectQueueStatePath, JSON.stringify(st));
|
|
1466
1540
|
}
|
|
1467
1541
|
}
|
|
@@ -1586,9 +1660,8 @@ async function collectClaudeMessageHashes(filePaths) {
|
|
|
1586
1660
|
} catch (_e) {
|
|
1587
1661
|
continue;
|
|
1588
1662
|
}
|
|
1589
|
-
const
|
|
1590
|
-
|
|
1591
|
-
if (msgId && reqId) hashes.add(`${msgId}:${reqId}`);
|
|
1663
|
+
const hash = claudeMessageDedupKey(obj);
|
|
1664
|
+
if (hash) hashes.add(hash);
|
|
1592
1665
|
}
|
|
1593
1666
|
rl.close();
|
|
1594
1667
|
stream.close?.();
|
|
@@ -26,6 +26,7 @@ const {
|
|
|
26
26
|
buildExecStatsEntry,
|
|
27
27
|
allocateByLargestRemainder,
|
|
28
28
|
} = require("./categorizer-utils");
|
|
29
|
+
const { claudeMessageDedupKey } = require("./rollout");
|
|
29
30
|
|
|
30
31
|
const CATEGORY_KEYS = [
|
|
31
32
|
"system_prefix",
|
|
@@ -478,10 +479,8 @@ async function categorizeSessionFile(filePath, { fromIso, toIso, seenHashes }, b
|
|
|
478
479
|
if (fromIso && ts < fromIso) continue;
|
|
479
480
|
if (toIso && ts > toIso) continue;
|
|
480
481
|
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
if (msgId && reqId) {
|
|
484
|
-
const hash = `${msgId}:${reqId}`;
|
|
482
|
+
const hash = claudeMessageDedupKey(obj);
|
|
483
|
+
if (hash) {
|
|
485
484
|
if (seenHashes.has(hash)) continue;
|
|
486
485
|
seenHashes.add(hash);
|
|
487
486
|
}
|
|
@@ -1133,10 +1132,8 @@ async function computeClaudeGroundTruthBuckets({ rootDir = null } = {}) {
|
|
|
1133
1132
|
const usage = obj?.message?.usage;
|
|
1134
1133
|
if (!usage || typeof usage !== "object") continue;
|
|
1135
1134
|
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
if (msgId && reqId) {
|
|
1139
|
-
const hash = `${msgId}:${reqId}`;
|
|
1135
|
+
const hash = claudeMessageDedupKey(obj);
|
|
1136
|
+
if (hash) {
|
|
1140
1137
|
if (seenHashes.has(hash)) continue;
|
|
1141
1138
|
seenHashes.add(hash);
|
|
1142
1139
|
}
|