tokentracker-cli 0.6.7 → 0.7.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokentracker-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
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, Craft Agents)",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
package/src/commands/sync.js
CHANGED
|
@@ -3,6 +3,7 @@ const path = require("node:path");
|
|
|
3
3
|
const fs = require("node:fs/promises");
|
|
4
4
|
const fssync = require("node:fs");
|
|
5
5
|
const cp = require("node:child_process");
|
|
6
|
+
const readline = require("node:readline");
|
|
6
7
|
|
|
7
8
|
const { ensureDir, readJson, writeJson, openLock } = require("../lib/fs");
|
|
8
9
|
const {
|
|
@@ -57,6 +58,8 @@ const { resolveRuntimeConfig } = require("../lib/runtime-config");
|
|
|
57
58
|
|
|
58
59
|
const CURSOR_UNKNOWN_MIGRATION_KEY = "cursorUnknownPurge_2026_04";
|
|
59
60
|
const ROLLOUT_CUMULATIVE_DELTA_MIGRATION_KEY = "rolloutCumulativeDeltaReparse_2026_05";
|
|
61
|
+
const CLAUDE_MEM_OBSERVER_REINCLUDE_KEY = "claudeMemObserverReinclude_2026_05_v3";
|
|
62
|
+
const CLAUDE_MEM_OBSERVER_PATH_SEGMENT = "--claude-mem-observer-sessions";
|
|
60
63
|
|
|
61
64
|
async function cmdSync(argv) {
|
|
62
65
|
const opts = parseArgs(argv);
|
|
@@ -171,6 +174,7 @@ async function cmdSync(argv) {
|
|
|
171
174
|
openclawResult.bucketsQueued += openclawFallback.bucketsQueued;
|
|
172
175
|
|
|
173
176
|
const claudeFiles = await listClaudeProjectFiles(claudeProjectsDir);
|
|
177
|
+
await reincludeClaudeMemObserverFiles({ cursors, claudeFiles, queuePath });
|
|
174
178
|
let claudeResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
175
179
|
if (claudeFiles.length > 0) {
|
|
176
180
|
if (progress?.enabled) {
|
|
@@ -680,8 +684,10 @@ module.exports = {
|
|
|
680
684
|
cmdSync,
|
|
681
685
|
migrateCursorUnknownBuckets,
|
|
682
686
|
migrateRolloutCumulativeDeltaBuckets,
|
|
687
|
+
reincludeClaudeMemObserverFiles,
|
|
683
688
|
CURSOR_UNKNOWN_MIGRATION_KEY,
|
|
684
689
|
ROLLOUT_CUMULATIVE_DELTA_MIGRATION_KEY,
|
|
690
|
+
CLAUDE_MEM_OBSERVER_REINCLUDE_KEY,
|
|
685
691
|
};
|
|
686
692
|
|
|
687
693
|
function normalizeString(value) {
|
|
@@ -959,8 +965,6 @@ async function writeOpenclawSignal(trackerDir) {
|
|
|
959
965
|
const AUTO_RETRY_FILENAME = "auto.retry.json";
|
|
960
966
|
const AUTO_RETRY_MAX_DELAY_MS = 2 * 60 * 60 * 1000;
|
|
961
967
|
|
|
962
|
-
const readline = require("node:readline");
|
|
963
|
-
|
|
964
968
|
const INGEST_SLUG = "tokentracker-ingest";
|
|
965
969
|
const MAX_INGEST_BUCKETS = 500;
|
|
966
970
|
|
|
@@ -1159,3 +1163,119 @@ async function migrateRolloutCumulativeDeltaBuckets({ cursors, queuePath, rollou
|
|
|
1159
1163
|
|
|
1160
1164
|
cursors.migrations[ROLLOUT_CUMULATIVE_DELTA_MIGRATION_KEY] = new Date().toISOString();
|
|
1161
1165
|
}
|
|
1166
|
+
|
|
1167
|
+
async function reincludeClaudeMemObserverFiles({ cursors, claudeFiles, queuePath }) {
|
|
1168
|
+
if (!cursors || typeof cursors !== "object") return false;
|
|
1169
|
+
const migrations = (cursors.migrations ||= {});
|
|
1170
|
+
if (migrations[CLAUDE_MEM_OBSERVER_REINCLUDE_KEY]) return false;
|
|
1171
|
+
|
|
1172
|
+
const observerPaths = (Array.isArray(claudeFiles) ? claudeFiles : [])
|
|
1173
|
+
.map((entry) => (typeof entry === "string" ? entry : entry?.path))
|
|
1174
|
+
.filter((p) => typeof p === "string" && p.includes(CLAUDE_MEM_OBSERVER_PATH_SEGMENT));
|
|
1175
|
+
|
|
1176
|
+
if (!cursors.files || typeof cursors.files !== "object") {
|
|
1177
|
+
cursors.files = {};
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
let filesReset = 0;
|
|
1181
|
+
for (const filePath of observerPaths) {
|
|
1182
|
+
if (cursors.files[filePath]) {
|
|
1183
|
+
delete cursors.files[filePath];
|
|
1184
|
+
filesReset += 1;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const hashesToRemove = observerPaths.length > 0
|
|
1189
|
+
? await collectClaudeMessageHashes(observerPaths)
|
|
1190
|
+
: new Set();
|
|
1191
|
+
let hashesRemoved = 0;
|
|
1192
|
+
if (Array.isArray(cursors.claudeHashes) && hashesToRemove.size > 0) {
|
|
1193
|
+
const nextHashes = [];
|
|
1194
|
+
for (const hash of cursors.claudeHashes) {
|
|
1195
|
+
if (hashesToRemove.has(hash)) {
|
|
1196
|
+
hashesRemoved += 1;
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
nextHashes.push(hash);
|
|
1200
|
+
}
|
|
1201
|
+
cursors.claudeHashes = nextHashes;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const queueRowsRelabeled = typeof queuePath === "string" && queuePath
|
|
1205
|
+
? await relabelClaudeMemQueueRows(queuePath)
|
|
1206
|
+
: 0;
|
|
1207
|
+
|
|
1208
|
+
migrations[CLAUDE_MEM_OBSERVER_REINCLUDE_KEY] = {
|
|
1209
|
+
appliedAt: new Date().toISOString(),
|
|
1210
|
+
filesReset,
|
|
1211
|
+
hashesRemoved,
|
|
1212
|
+
queueRowsRelabeled,
|
|
1213
|
+
};
|
|
1214
|
+
return filesReset > 0 || hashesRemoved > 0 || queueRowsRelabeled > 0;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
async function relabelClaudeMemQueueRows(queuePath) {
|
|
1218
|
+
let raw;
|
|
1219
|
+
try {
|
|
1220
|
+
raw = await fs.readFile(queuePath, "utf8");
|
|
1221
|
+
} catch (_e) {
|
|
1222
|
+
return 0;
|
|
1223
|
+
}
|
|
1224
|
+
if (!raw || !raw.includes('"claude-mem"')) return 0;
|
|
1225
|
+
|
|
1226
|
+
const lines = raw.split("\n");
|
|
1227
|
+
const out = [];
|
|
1228
|
+
let relabeled = 0;
|
|
1229
|
+
for (const line of lines) {
|
|
1230
|
+
if (!line) {
|
|
1231
|
+
out.push(line);
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
let obj;
|
|
1235
|
+
try {
|
|
1236
|
+
obj = JSON.parse(line);
|
|
1237
|
+
} catch (_e) {
|
|
1238
|
+
out.push(line);
|
|
1239
|
+
continue;
|
|
1240
|
+
}
|
|
1241
|
+
if (obj && obj.source === "claude-mem") {
|
|
1242
|
+
obj.source = "claude";
|
|
1243
|
+
relabeled += 1;
|
|
1244
|
+
out.push(JSON.stringify(obj));
|
|
1245
|
+
} else {
|
|
1246
|
+
out.push(line);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
if (relabeled === 0) return 0;
|
|
1250
|
+
|
|
1251
|
+
await fs.writeFile(queuePath, out.join("\n"), "utf8");
|
|
1252
|
+
return relabeled;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
async function collectClaudeMessageHashes(filePaths) {
|
|
1256
|
+
const hashes = new Set();
|
|
1257
|
+
for (const filePath of filePaths) {
|
|
1258
|
+
let stream;
|
|
1259
|
+
try {
|
|
1260
|
+
stream = fssync.createReadStream(filePath, { encoding: "utf8" });
|
|
1261
|
+
} catch (_e) {
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
1265
|
+
for await (const line of rl) {
|
|
1266
|
+
if (!line.includes('"usage"')) continue;
|
|
1267
|
+
let obj;
|
|
1268
|
+
try {
|
|
1269
|
+
obj = JSON.parse(line);
|
|
1270
|
+
} catch (_e) {
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
const msgId = obj?.message?.id;
|
|
1274
|
+
const reqId = obj?.requestId;
|
|
1275
|
+
if (msgId && reqId) hashes.add(`${msgId}:${reqId}`);
|
|
1276
|
+
}
|
|
1277
|
+
rl.close();
|
|
1278
|
+
stream.close?.();
|
|
1279
|
+
}
|
|
1280
|
+
return hashes;
|
|
1281
|
+
}
|