tokentracker-cli 0.48.0 → 0.49.1
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.ja.md +3 -1
- package/README.ko.md +3 -1
- package/README.md +3 -1
- package/README.zh-CN.md +3 -1
- package/dashboard/dist/assets/{ActivityHeatmap-BW7r63JV.js → ActivityHeatmap-CdSIa7qF.js} +1 -1
- package/dashboard/dist/assets/{Card-BWgji0yz.js → Card-Ds-oKKa8.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-fwmRrxV3.js → DashboardPage-DlSGoNwE.js} +1 -1
- package/dashboard/dist/assets/DevicePage-BmuuVq8P.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-ltZ7viR5.js → DialogTitle-DvcD6xbM.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-DkGtOY3l.js → FadeIn-DVysFsQD.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-CUKVYH9j.js → HeaderGithubStar-DfFuzPIQ.js} +1 -1
- package/dashboard/dist/assets/IpCheckPage-Cx201GiV.js +20 -0
- package/dashboard/dist/assets/{LandingPage-Y2ovBPVG.js → LandingPage-CwVgEDkV.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-DSGfEE93.js → LeaderboardAvatar-ziBbRVvs.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-BX4cvtg7.js → LeaderboardPage-Biqvdo4g.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfileModal-CJfefuSR.js → LeaderboardProfileModal-DqPL0kqy.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-Xy0K-ppJ.js → LeaderboardProfilePage-DmRnMtr0.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-utdNxAg_.js → LimitsPage-Cp5h_6V-.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-CwGk0Fn3.js → LocalOnlyNotice-C0NrEMPx.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-BHlX2ZHJ.js → LoginPage-MPkEezJe.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-BC4rHujH.js → PopoverPopup-tas7bWDM.js} +1 -1
- package/dashboard/dist/assets/{Select-BpDJKpfl.js → Select-C1XPo279.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-D1_H3hHS.js → SelectItemText-BXdfdMoI.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-DCVw-3Cv.js → SettingsPage-uCq1kQJB.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-B-9_Ufpw.js → SkillsPage-BBK1FZHz.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-DJ4i97QU.js → WidgetsPage-B4vqrcSC.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-W8pPcYr2.js → WrappedPage-BKjocXbt.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-yJi-7lhN.js → agent-logos-aCP1gjjY.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-BXhFthH9.js → arrow-up-right-CKicCc9n.js} +1 -1
- package/dashboard/dist/assets/{download-D32KO7Ua.js → download-CyWoylMR.js} +1 -1
- package/dashboard/dist/assets/{info-C8QLdyUg.js → info-Eoucvx_Y.js} +1 -1
- package/dashboard/dist/assets/main-Br5SsufY.css +1 -0
- package/dashboard/dist/assets/{main-BmTIgbZL.js → main-D3YoNdym.js} +17 -17
- package/dashboard/dist/assets/{use-limits-display-prefs-DcmWcNeA.js → use-limits-display-prefs-CXcRsYJQ.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-BLZi1heD.js → use-native-settings-zPoh9XtP.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-C3UnLp-v.js +1 -0
- package/dashboard/dist/assets/{useCurrency-ERbWumqM.js → useCurrency-Bh791muK.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-PJWfD6AS.js → useScrollLock-CQdlrFZs.js} +1 -1
- package/dashboard/dist/brand-logos/hermes.svg +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/commands/device-login.js +19 -3
- package/src/commands/sync.js +538 -348
- package/src/lib/local-api.js +11 -2
- package/src/lib/pricing/curated-overrides.json +2 -1
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +98 -16
- package/src/lib/runtime-config.js +7 -4
- package/src/lib/usage-limits.js +118 -25
- package/src/lib/wrapped-aggregator.js +12 -1
- package/dashboard/dist/assets/DevicePage-rxxuM-iC.js +0 -1
- package/dashboard/dist/assets/IpCheckPage-DqrupqCq.js +0 -15
- package/dashboard/dist/assets/main-BfK9LoKV.css +0 -1
- package/dashboard/dist/assets/use-usage-limits-BIJk3Nmb.js +0 -1
package/src/commands/sync.js
CHANGED
|
@@ -122,6 +122,20 @@ const CLAUDE_MEM_OBSERVER_PATH_SEGMENT = "--claude-mem-observer-sessions";
|
|
|
122
122
|
// for whatever data is actually on disk. A targeted, log-gap-safe mimo
|
|
123
123
|
// migration will ship later under its own key.
|
|
124
124
|
const CLAUDE_GROUND_TRUTH_REPAIR_KEY = "claudeGroundTruthRepair_2026_05_v4";
|
|
125
|
+
// One-time full re-upload: the cloud ingest dropped `conversation_count` to 0
|
|
126
|
+
// from 2026-04-18 until the 2026-06-10 field-mapping fix (it read
|
|
127
|
+
// `b.conversations`; queue rows carry `conversation_count`). Historical cloud
|
|
128
|
+
// rows can only be repaired from each user's local queue.jsonl — resetting the
|
|
129
|
+
// upload offset replays the full queue and the ingest's whole-row upsert
|
|
130
|
+
// overwrites every historical bucket with the correct conversation counts
|
|
131
|
+
// (token columns replay to the same final values: last emission per key wins,
|
|
132
|
+
// exactly how the cloud rows were built the first time).
|
|
133
|
+
const CLOUD_CONVERSATIONS_BACKFILL_KEY = "cloudConversationsBackfill_2026_06";
|
|
134
|
+
|
|
135
|
+
function warnProviderParseFailure(label, err, opts) {
|
|
136
|
+
if (opts?.auto) return;
|
|
137
|
+
process.stderr.write(`${label} sync: ${err && err.message ? err.message : err}\n`);
|
|
138
|
+
}
|
|
125
139
|
|
|
126
140
|
async function cmdSync(argv) {
|
|
127
141
|
const opts = parseArgs(argv);
|
|
@@ -210,47 +224,61 @@ async function cmdSync(argv) {
|
|
|
210
224
|
);
|
|
211
225
|
}
|
|
212
226
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
227
|
+
let parseResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
228
|
+
try {
|
|
229
|
+
parseResult = await parseRolloutIncremental({
|
|
230
|
+
rolloutFiles,
|
|
231
|
+
cursors,
|
|
232
|
+
queuePath,
|
|
233
|
+
projectQueuePath,
|
|
234
|
+
onProgress: (p) => {
|
|
235
|
+
if (!progress?.enabled) return;
|
|
236
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
237
|
+
progress.update(
|
|
238
|
+
`Parsing ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(
|
|
239
|
+
p.bucketsQueued,
|
|
240
|
+
)}`,
|
|
241
|
+
);
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
} catch (err) {
|
|
245
|
+
warnProviderParseFailure("Codex", err, opts);
|
|
246
|
+
}
|
|
228
247
|
|
|
229
248
|
let openclawResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
230
249
|
if (openclawFiles.length > 0) {
|
|
231
250
|
// Only runs when explicitly triggered by OpenClaw hooks.
|
|
232
|
-
|
|
233
|
-
|
|
251
|
+
try {
|
|
252
|
+
openclawResult = await parseOpenclawIncremental({
|
|
253
|
+
sessionFiles: openclawFiles,
|
|
254
|
+
cursors,
|
|
255
|
+
queuePath,
|
|
256
|
+
projectQueuePath,
|
|
257
|
+
source: "openclaw",
|
|
258
|
+
});
|
|
259
|
+
} catch (err) {
|
|
260
|
+
warnProviderParseFailure("OpenClaw", err, opts);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let openclawFallback = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
265
|
+
try {
|
|
266
|
+
openclawFallback = await applyOpenclawTotalsFallback({
|
|
267
|
+
trackerDir,
|
|
268
|
+
signal: openclawSignal,
|
|
234
269
|
cursors,
|
|
235
270
|
queuePath,
|
|
236
271
|
projectQueuePath,
|
|
237
|
-
source: "openclaw",
|
|
238
272
|
});
|
|
273
|
+
} catch (err) {
|
|
274
|
+
warnProviderParseFailure("OpenClaw", err, opts);
|
|
239
275
|
}
|
|
240
|
-
|
|
241
|
-
const openclawFallback = await applyOpenclawTotalsFallback({
|
|
242
|
-
trackerDir,
|
|
243
|
-
signal: openclawSignal,
|
|
244
|
-
cursors,
|
|
245
|
-
queuePath,
|
|
246
|
-
projectQueuePath,
|
|
247
|
-
});
|
|
248
276
|
openclawResult.filesProcessed += openclawFallback.filesProcessed;
|
|
249
277
|
openclawResult.eventsAggregated += openclawFallback.eventsAggregated;
|
|
250
278
|
openclawResult.bucketsQueued += openclawFallback.bucketsQueued;
|
|
251
279
|
|
|
252
280
|
const claudeFiles = await listClaudeProjectFiles(claudeProjectsDir);
|
|
253
|
-
await reincludeClaudeMemObserverFiles({ cursors, claudeFiles, queuePath });
|
|
281
|
+
await reincludeClaudeMemObserverFiles({ cursors, claudeFiles, queuePath, queueStatePath });
|
|
254
282
|
await repairClaudeQueueFromGroundTruth({
|
|
255
283
|
cursors,
|
|
256
284
|
queuePath,
|
|
@@ -265,22 +293,26 @@ async function cmdSync(argv) {
|
|
|
265
293
|
`Parsing Claude ${renderBar(0)} 0/${formatNumber(claudeFiles.length)} files | buckets 0`,
|
|
266
294
|
);
|
|
267
295
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
p.
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
296
|
+
try {
|
|
297
|
+
claudeResult = await parseClaudeIncremental({
|
|
298
|
+
projectFiles: claudeFiles,
|
|
299
|
+
cursors,
|
|
300
|
+
queuePath,
|
|
301
|
+
projectQueuePath,
|
|
302
|
+
onProgress: (p) => {
|
|
303
|
+
if (!progress?.enabled) return;
|
|
304
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
305
|
+
progress.update(
|
|
306
|
+
`Parsing Claude ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(
|
|
307
|
+
p.bucketsQueued,
|
|
308
|
+
)}`,
|
|
309
|
+
);
|
|
310
|
+
},
|
|
311
|
+
source: "claude",
|
|
312
|
+
});
|
|
313
|
+
} catch (err) {
|
|
314
|
+
warnProviderParseFailure("Claude", err, opts);
|
|
315
|
+
}
|
|
284
316
|
}
|
|
285
317
|
|
|
286
318
|
const geminiFiles = await listGeminiSessionFiles(geminiTmpDir);
|
|
@@ -291,22 +323,26 @@ async function cmdSync(argv) {
|
|
|
291
323
|
`Parsing Gemini ${renderBar(0)} 0/${formatNumber(geminiFiles.length)} files | buckets 0`,
|
|
292
324
|
);
|
|
293
325
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
p.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
326
|
+
try {
|
|
327
|
+
geminiResult = await parseGeminiIncremental({
|
|
328
|
+
sessionFiles: geminiFiles,
|
|
329
|
+
cursors,
|
|
330
|
+
queuePath,
|
|
331
|
+
projectQueuePath,
|
|
332
|
+
onProgress: (p) => {
|
|
333
|
+
if (!progress?.enabled) return;
|
|
334
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
335
|
+
progress.update(
|
|
336
|
+
`Parsing Gemini ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(
|
|
337
|
+
p.bucketsQueued,
|
|
338
|
+
)}`,
|
|
339
|
+
);
|
|
340
|
+
},
|
|
341
|
+
source: "gemini",
|
|
342
|
+
});
|
|
343
|
+
} catch (err) {
|
|
344
|
+
warnProviderParseFailure("Gemini", err, opts);
|
|
345
|
+
}
|
|
310
346
|
}
|
|
311
347
|
|
|
312
348
|
const antigravityFiles = await listAntigravityTranscripts(geminiHome);
|
|
@@ -317,22 +353,26 @@ async function cmdSync(argv) {
|
|
|
317
353
|
`Parsing Antigravity ${renderBar(0)} 0/${formatNumber(antigravityFiles.length)} files | buckets 0`,
|
|
318
354
|
);
|
|
319
355
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
p.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
356
|
+
try {
|
|
357
|
+
antigravityResult = await parseAntigravityIncremental({
|
|
358
|
+
sessionFiles: antigravityFiles,
|
|
359
|
+
cursors,
|
|
360
|
+
queuePath,
|
|
361
|
+
projectQueuePath,
|
|
362
|
+
onProgress: (p) => {
|
|
363
|
+
if (!progress?.enabled) return;
|
|
364
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
365
|
+
progress.update(
|
|
366
|
+
`Parsing Antigravity ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(
|
|
367
|
+
p.bucketsQueued,
|
|
368
|
+
)}`,
|
|
369
|
+
);
|
|
370
|
+
},
|
|
371
|
+
source: "antigravity",
|
|
372
|
+
});
|
|
373
|
+
} catch (err) {
|
|
374
|
+
warnProviderParseFailure("Antigravity", err, opts);
|
|
375
|
+
}
|
|
336
376
|
}
|
|
337
377
|
|
|
338
378
|
const opencodeFiles = await listOpencodeMessageFiles(opencodeStorageDir);
|
|
@@ -343,22 +383,26 @@ async function cmdSync(argv) {
|
|
|
343
383
|
`Parsing Opencode ${renderBar(0)} 0/${formatNumber(opencodeFiles.length)} files | buckets 0`,
|
|
344
384
|
);
|
|
345
385
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
p.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
386
|
+
try {
|
|
387
|
+
opencodeResult = await parseOpencodeIncremental({
|
|
388
|
+
messageFiles: opencodeFiles,
|
|
389
|
+
cursors,
|
|
390
|
+
queuePath,
|
|
391
|
+
projectQueuePath,
|
|
392
|
+
onProgress: (p) => {
|
|
393
|
+
if (!progress?.enabled) return;
|
|
394
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
395
|
+
progress.update(
|
|
396
|
+
`Parsing Opencode ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
|
|
397
|
+
p.total,
|
|
398
|
+
)} files | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
399
|
+
);
|
|
400
|
+
},
|
|
401
|
+
source: "opencode",
|
|
402
|
+
});
|
|
403
|
+
} catch (err) {
|
|
404
|
+
warnProviderParseFailure("Opencode", err, opts);
|
|
405
|
+
}
|
|
362
406
|
}
|
|
363
407
|
|
|
364
408
|
// OpenCode v1.2+ stores messages in SQLite (opencode.db) instead of JSON files.
|
|
@@ -371,22 +415,26 @@ async function cmdSync(argv) {
|
|
|
371
415
|
`Parsing Opencode DB ${renderBar(0)} 0/${formatNumber(dbMessages.length)} msgs | buckets 0`,
|
|
372
416
|
);
|
|
373
417
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
p.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
418
|
+
try {
|
|
419
|
+
opencodeDbResult = await parseOpencodeDbIncremental({
|
|
420
|
+
dbMessages,
|
|
421
|
+
cursors,
|
|
422
|
+
queuePath,
|
|
423
|
+
projectQueuePath,
|
|
424
|
+
onProgress: (p) => {
|
|
425
|
+
if (!progress?.enabled) return;
|
|
426
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
427
|
+
progress.update(
|
|
428
|
+
`Parsing Opencode DB ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
|
|
429
|
+
p.total,
|
|
430
|
+
)} msgs | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
431
|
+
);
|
|
432
|
+
},
|
|
433
|
+
source: "opencode",
|
|
434
|
+
});
|
|
435
|
+
} catch (err) {
|
|
436
|
+
warnProviderParseFailure("Opencode DB", err, opts);
|
|
437
|
+
}
|
|
390
438
|
opencodeResult.filesProcessed += opencodeDbResult.messagesProcessed;
|
|
391
439
|
opencodeResult.eventsAggregated += opencodeDbResult.eventsAggregated;
|
|
392
440
|
opencodeResult.bucketsQueued += opencodeDbResult.bucketsQueued;
|
|
@@ -405,23 +453,27 @@ async function cmdSync(argv) {
|
|
|
405
453
|
`Parsing Kilo CLI ${renderBar(0)} 0/${formatNumber(kiloDbMessages.length)} msgs | buckets 0`,
|
|
406
454
|
);
|
|
407
455
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
p.
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
456
|
+
try {
|
|
457
|
+
kiloResult = await parseOpencodeDbIncremental({
|
|
458
|
+
dbMessages: kiloDbMessages,
|
|
459
|
+
cursors,
|
|
460
|
+
queuePath,
|
|
461
|
+
projectQueuePath,
|
|
462
|
+
onProgress: (p) => {
|
|
463
|
+
if (!progress?.enabled) return;
|
|
464
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
465
|
+
progress.update(
|
|
466
|
+
`Parsing Kilo CLI ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
|
|
467
|
+
p.total,
|
|
468
|
+
)} msgs | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
469
|
+
);
|
|
470
|
+
},
|
|
471
|
+
source: "kilo-cli",
|
|
472
|
+
cursorKey: "kiloCli",
|
|
473
|
+
});
|
|
474
|
+
} catch (err) {
|
|
475
|
+
warnProviderParseFailure("Kilo CLI", err, opts);
|
|
476
|
+
}
|
|
425
477
|
}
|
|
426
478
|
|
|
427
479
|
// ── Kilo Code VS Code extension (Cline-style ui_messages.json) ──
|
|
@@ -433,20 +485,24 @@ async function cmdSync(argv) {
|
|
|
433
485
|
`Parsing Kilo Code ${renderBar(0)} 0/${formatNumber(kilocodeTaskFiles.length)} tasks | buckets 0`,
|
|
434
486
|
);
|
|
435
487
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
p.
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
488
|
+
try {
|
|
489
|
+
kilocodeResult = await parseKilocodeIncremental({
|
|
490
|
+
taskFiles: kilocodeTaskFiles,
|
|
491
|
+
cursors,
|
|
492
|
+
queuePath,
|
|
493
|
+
onProgress: (p) => {
|
|
494
|
+
if (!progress?.enabled) return;
|
|
495
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
496
|
+
progress.update(
|
|
497
|
+
`Parsing Kilo Code ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
|
|
498
|
+
p.total,
|
|
499
|
+
)} tasks | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
500
|
+
);
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
} catch (err) {
|
|
504
|
+
warnProviderParseFailure("Kilo Code", err, opts);
|
|
505
|
+
}
|
|
450
506
|
}
|
|
451
507
|
|
|
452
508
|
// ── Goose (Block) — SQLite sessions with cumulative tokens per session ──
|
|
@@ -456,20 +512,24 @@ async function cmdSync(argv) {
|
|
|
456
512
|
if (progress?.enabled) {
|
|
457
513
|
progress.start(`Parsing Goose ${renderBar(0)} 0 sessions | buckets 0`);
|
|
458
514
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
p.
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
515
|
+
try {
|
|
516
|
+
gooseResult = await parseGooseIncremental({
|
|
517
|
+
dbPath: gooseDbPath,
|
|
518
|
+
cursors,
|
|
519
|
+
queuePath,
|
|
520
|
+
onProgress: (p) => {
|
|
521
|
+
if (!progress?.enabled) return;
|
|
522
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
523
|
+
progress.update(
|
|
524
|
+
`Parsing Goose ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
|
|
525
|
+
p.total,
|
|
526
|
+
)} sessions | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
527
|
+
);
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
} catch (err) {
|
|
531
|
+
warnProviderParseFailure("Goose", err, opts);
|
|
532
|
+
}
|
|
473
533
|
}
|
|
474
534
|
|
|
475
535
|
// ── Droid (Factory CLI) — passive reader for ~/.factory/sessions/*.settings.json ──
|
|
@@ -481,24 +541,28 @@ async function cmdSync(argv) {
|
|
|
481
541
|
`Parsing Droid ${renderBar(0)} 0/${formatNumber(droidSettingsFiles.length)} sessions | buckets 0`,
|
|
482
542
|
);
|
|
483
543
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
p.
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
544
|
+
try {
|
|
545
|
+
droidResult = await parseDroidIncremental({
|
|
546
|
+
settingsFiles: droidSettingsFiles,
|
|
547
|
+
cursors,
|
|
548
|
+
queuePath,
|
|
549
|
+
// Full-scan sync: drop cursor entries for any session whose
|
|
550
|
+
// settings.json has disappeared off disk so cursors.droid stays
|
|
551
|
+
// bounded by the actual on-disk session count.
|
|
552
|
+
prune: true,
|
|
553
|
+
onProgress: (p) => {
|
|
554
|
+
if (!progress?.enabled) return;
|
|
555
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
556
|
+
progress.update(
|
|
557
|
+
`Parsing Droid ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
|
|
558
|
+
p.total,
|
|
559
|
+
)} sessions | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
560
|
+
);
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
} catch (err) {
|
|
564
|
+
warnProviderParseFailure("Droid", err, opts);
|
|
565
|
+
}
|
|
502
566
|
}
|
|
503
567
|
|
|
504
568
|
// ── Zed Agent (all providers; cumulative-delta over SQLite threads) ──
|
|
@@ -508,20 +572,24 @@ async function cmdSync(argv) {
|
|
|
508
572
|
if (progress?.enabled) {
|
|
509
573
|
progress.start(`Parsing Zed Agent ${renderBar(0)} 0 threads | buckets 0`);
|
|
510
574
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
p.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
575
|
+
try {
|
|
576
|
+
zedResult = await parseZedIncremental({
|
|
577
|
+
dbPath: zedDbPath,
|
|
578
|
+
cursors,
|
|
579
|
+
queuePath,
|
|
580
|
+
onProgress: (p) => {
|
|
581
|
+
if (!progress?.enabled) return;
|
|
582
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
583
|
+
progress.update(
|
|
584
|
+
`Parsing Zed Agent ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
|
|
585
|
+
p.total,
|
|
586
|
+
)} threads | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
587
|
+
);
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
} catch (err) {
|
|
591
|
+
warnProviderParseFailure("Zed Agent", err, opts);
|
|
592
|
+
}
|
|
525
593
|
}
|
|
526
594
|
|
|
527
595
|
// ── Roo Code VS Code extension (Cline-derived; rooveterinaryinc.roo-cline) ──
|
|
@@ -533,20 +601,24 @@ async function cmdSync(argv) {
|
|
|
533
601
|
`Parsing Roo Code ${renderBar(0)} 0/${formatNumber(roocodeTaskFiles.length)} tasks | buckets 0`,
|
|
534
602
|
);
|
|
535
603
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
p.
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
604
|
+
try {
|
|
605
|
+
roocodeResult = await parseRoocodeIncremental({
|
|
606
|
+
taskFiles: roocodeTaskFiles,
|
|
607
|
+
cursors,
|
|
608
|
+
queuePath,
|
|
609
|
+
onProgress: (p) => {
|
|
610
|
+
if (!progress?.enabled) return;
|
|
611
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
612
|
+
progress.update(
|
|
613
|
+
`Parsing Roo Code ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
|
|
614
|
+
p.total,
|
|
615
|
+
)} tasks | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
616
|
+
);
|
|
617
|
+
},
|
|
618
|
+
});
|
|
619
|
+
} catch (err) {
|
|
620
|
+
warnProviderParseFailure("Roo Code", err, opts);
|
|
621
|
+
}
|
|
550
622
|
}
|
|
551
623
|
|
|
552
624
|
// ── Cursor (API-based) ──
|
|
@@ -605,19 +677,23 @@ async function cmdSync(argv) {
|
|
|
605
677
|
if (progress?.enabled) {
|
|
606
678
|
progress.start(`Parsing Kiro ${renderBar(0)} | buckets 0`);
|
|
607
679
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
680
|
+
try {
|
|
681
|
+
kiroResult = await parseKiroIncremental({
|
|
682
|
+
dbPath: kiroDbPath,
|
|
683
|
+
jsonlPath: kiroJsonlPath,
|
|
684
|
+
cursors,
|
|
685
|
+
queuePath,
|
|
686
|
+
onProgress: (p) => {
|
|
687
|
+
if (!progress?.enabled) return;
|
|
688
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
689
|
+
progress.update(
|
|
690
|
+
`Parsing Kiro ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} records | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
691
|
+
);
|
|
692
|
+
},
|
|
693
|
+
});
|
|
694
|
+
} catch (err) {
|
|
695
|
+
warnProviderParseFailure("Kiro", err, opts);
|
|
696
|
+
}
|
|
621
697
|
}
|
|
622
698
|
|
|
623
699
|
// ── Hermes Agent (SQLite-based) ──
|
|
@@ -627,18 +703,22 @@ async function cmdSync(argv) {
|
|
|
627
703
|
if (progress?.enabled) {
|
|
628
704
|
progress.start(`Parsing Hermes ${renderBar(0)} | buckets 0`);
|
|
629
705
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
706
|
+
try {
|
|
707
|
+
hermesResult = await parseHermesIncremental({
|
|
708
|
+
hermesPath,
|
|
709
|
+
cursors,
|
|
710
|
+
queuePath,
|
|
711
|
+
onProgress: (p) => {
|
|
712
|
+
if (!progress?.enabled) return;
|
|
713
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
714
|
+
progress.update(
|
|
715
|
+
`Parsing Hermes ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} sessions | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
716
|
+
);
|
|
717
|
+
},
|
|
718
|
+
});
|
|
719
|
+
} catch (err) {
|
|
720
|
+
warnProviderParseFailure("Hermes", err, opts);
|
|
721
|
+
}
|
|
642
722
|
}
|
|
643
723
|
|
|
644
724
|
// ── Kiro CLI (reads ~/Library/Application Support/kiro-cli/data.sqlite3
|
|
@@ -682,19 +762,23 @@ async function cmdSync(argv) {
|
|
|
682
762
|
if (progress?.enabled) {
|
|
683
763
|
progress.start(`Parsing Kimi Code ${renderBar(0)} | buckets 0`);
|
|
684
764
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
765
|
+
try {
|
|
766
|
+
kimiResult = await parseKimiIncremental({
|
|
767
|
+
wireFiles: kimiWireFiles,
|
|
768
|
+
cursors,
|
|
769
|
+
queuePath,
|
|
770
|
+
env: process.env,
|
|
771
|
+
onProgress: (p) => {
|
|
772
|
+
if (!progress?.enabled) return;
|
|
773
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
774
|
+
progress.update(
|
|
775
|
+
`Parsing Kimi Code ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
776
|
+
);
|
|
777
|
+
},
|
|
778
|
+
});
|
|
779
|
+
} catch (err) {
|
|
780
|
+
warnProviderParseFailure("Kimi Code", err, opts);
|
|
781
|
+
}
|
|
698
782
|
}
|
|
699
783
|
|
|
700
784
|
// ── Kimi Code official (@moonshot-ai/kimi-code, ~/.kimi-code) ──
|
|
@@ -704,19 +788,23 @@ async function cmdSync(argv) {
|
|
|
704
788
|
if (progress?.enabled) {
|
|
705
789
|
progress.start(`Parsing Kimi Code (official) ${renderBar(0)} | buckets 0`);
|
|
706
790
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
791
|
+
try {
|
|
792
|
+
kimiCodeResult = await parseKimiCodeIncremental({
|
|
793
|
+
wireFiles: kimiCodeWireFiles,
|
|
794
|
+
cursors,
|
|
795
|
+
queuePath,
|
|
796
|
+
env: process.env,
|
|
797
|
+
onProgress: (p) => {
|
|
798
|
+
if (!progress?.enabled) return;
|
|
799
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
800
|
+
progress.update(
|
|
801
|
+
`Parsing Kimi Code (official) ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
802
|
+
);
|
|
803
|
+
},
|
|
804
|
+
});
|
|
805
|
+
} catch (err) {
|
|
806
|
+
warnProviderParseFailure("Kimi Code (official)", err, opts);
|
|
807
|
+
}
|
|
720
808
|
}
|
|
721
809
|
|
|
722
810
|
// ── CodeBuddy CLI (passive ~/.codebuddy/projects/**/*.jsonl reader) ──
|
|
@@ -728,19 +816,23 @@ async function cmdSync(argv) {
|
|
|
728
816
|
if (progress?.enabled) {
|
|
729
817
|
progress.start(`Parsing CodeBuddy ${renderBar(0)} | buckets 0`);
|
|
730
818
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
819
|
+
try {
|
|
820
|
+
codebuddyResult = await parseCodebuddyIncremental({
|
|
821
|
+
projectFiles: codebuddyFiles,
|
|
822
|
+
cursors,
|
|
823
|
+
queuePath,
|
|
824
|
+
env: process.env,
|
|
825
|
+
onProgress: (p) => {
|
|
826
|
+
if (!progress?.enabled) return;
|
|
827
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
828
|
+
progress.update(
|
|
829
|
+
`Parsing CodeBuddy ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
830
|
+
);
|
|
831
|
+
},
|
|
832
|
+
});
|
|
833
|
+
} catch (err) {
|
|
834
|
+
warnProviderParseFailure("CodeBuddy", err, opts);
|
|
835
|
+
}
|
|
744
836
|
}
|
|
745
837
|
|
|
746
838
|
// ── oh-my-pi (passive ~/.omp/agent/sessions/**/*.jsonl reader) ──
|
|
@@ -750,19 +842,23 @@ async function cmdSync(argv) {
|
|
|
750
842
|
if (progress?.enabled) {
|
|
751
843
|
progress.start(`Parsing oh-my-pi ${renderBar(0)} | buckets 0`);
|
|
752
844
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
845
|
+
try {
|
|
846
|
+
ompResult = await parseOmpIncremental({
|
|
847
|
+
sessionFiles: ompFiles,
|
|
848
|
+
cursors,
|
|
849
|
+
queuePath,
|
|
850
|
+
env: process.env,
|
|
851
|
+
onProgress: (p) => {
|
|
852
|
+
if (!progress?.enabled) return;
|
|
853
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
854
|
+
progress.update(
|
|
855
|
+
`Parsing oh-my-pi ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
856
|
+
);
|
|
857
|
+
},
|
|
858
|
+
});
|
|
859
|
+
} catch (err) {
|
|
860
|
+
warnProviderParseFailure("oh-my-pi", err, opts);
|
|
861
|
+
}
|
|
766
862
|
}
|
|
767
863
|
|
|
768
864
|
// ── pi (@mariozechner/pi-coding-agent) — passive ~/.pi/agent/sessions/**/*.jsonl reader ──
|
|
@@ -777,19 +873,23 @@ async function cmdSync(argv) {
|
|
|
777
873
|
if (progress?.enabled) {
|
|
778
874
|
progress.start(`Parsing pi ${renderBar(0)} | buckets 0`);
|
|
779
875
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
876
|
+
try {
|
|
877
|
+
piResult = await parsePiIncremental({
|
|
878
|
+
sessionFiles: piFiles,
|
|
879
|
+
cursors,
|
|
880
|
+
queuePath,
|
|
881
|
+
env: process.env,
|
|
882
|
+
onProgress: (p) => {
|
|
883
|
+
if (!progress?.enabled) return;
|
|
884
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
885
|
+
progress.update(
|
|
886
|
+
`Parsing pi ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
887
|
+
);
|
|
888
|
+
},
|
|
889
|
+
});
|
|
890
|
+
} catch (err) {
|
|
891
|
+
warnProviderParseFailure("pi", err, opts);
|
|
892
|
+
}
|
|
793
893
|
}
|
|
794
894
|
|
|
795
895
|
// ── Craft Agents (passive ~/.craft-agent + workspaces session.jsonl reader) ──
|
|
@@ -799,19 +899,23 @@ async function cmdSync(argv) {
|
|
|
799
899
|
if (progress?.enabled) {
|
|
800
900
|
progress.start(`Parsing Craft ${renderBar(0)} | buckets 0`);
|
|
801
901
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
902
|
+
try {
|
|
903
|
+
craftResult = await parseCraftIncremental({
|
|
904
|
+
sessionFiles: craftFiles,
|
|
905
|
+
cursors,
|
|
906
|
+
queuePath,
|
|
907
|
+
env: process.env,
|
|
908
|
+
onProgress: (p) => {
|
|
909
|
+
if (!progress?.enabled) return;
|
|
910
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
911
|
+
progress.update(
|
|
912
|
+
`Parsing Craft ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
913
|
+
);
|
|
914
|
+
},
|
|
915
|
+
});
|
|
916
|
+
} catch (err) {
|
|
917
|
+
warnProviderParseFailure("Craft", err, opts);
|
|
918
|
+
}
|
|
815
919
|
}
|
|
816
920
|
|
|
817
921
|
// ── Grok Build (xAI) ──
|
|
@@ -860,19 +964,24 @@ async function cmdSync(argv) {
|
|
|
860
964
|
if (progress?.enabled) {
|
|
861
965
|
progress.start(`Parsing Grok Build ${renderBar(0)} | buckets 0`);
|
|
862
966
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
967
|
+
let grokScanResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
968
|
+
try {
|
|
969
|
+
grokScanResult = await parseGrokBuildIncremental({
|
|
970
|
+
sessions: grokSessionInputs,
|
|
971
|
+
cursors,
|
|
972
|
+
queuePath,
|
|
973
|
+
env: process.env,
|
|
974
|
+
onProgress: (p) => {
|
|
975
|
+
if (!progress?.enabled) return;
|
|
976
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
977
|
+
progress.update(
|
|
978
|
+
`Parsing Grok Build ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} sessions | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
979
|
+
);
|
|
980
|
+
},
|
|
981
|
+
});
|
|
982
|
+
} catch (err) {
|
|
983
|
+
warnProviderParseFailure("Grok Build", err, opts);
|
|
984
|
+
}
|
|
876
985
|
grokResult = {
|
|
877
986
|
recordsProcessed: grokResult.recordsProcessed + grokScanResult.recordsProcessed,
|
|
878
987
|
eventsAggregated: grokResult.eventsAggregated + grokScanResult.eventsAggregated,
|
|
@@ -890,19 +999,23 @@ async function cmdSync(argv) {
|
|
|
890
999
|
if (progress?.enabled) {
|
|
891
1000
|
progress.start(`Parsing Copilot ${renderBar(0)} | buckets 0`);
|
|
892
1001
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1002
|
+
try {
|
|
1003
|
+
copilotResult = await parseCopilotIncremental({
|
|
1004
|
+
otelPaths: copilotPaths,
|
|
1005
|
+
cursors,
|
|
1006
|
+
queuePath,
|
|
1007
|
+
env: process.env,
|
|
1008
|
+
onProgress: (p) => {
|
|
1009
|
+
if (!progress?.enabled) return;
|
|
1010
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
1011
|
+
progress.update(
|
|
1012
|
+
`Parsing Copilot ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
1013
|
+
);
|
|
1014
|
+
},
|
|
1015
|
+
});
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
warnProviderParseFailure("Copilot", err, opts);
|
|
1018
|
+
}
|
|
906
1019
|
}
|
|
907
1020
|
|
|
908
1021
|
if (cursors?.projectHourly?.projects && projectQueuePath && projectQueueStatePath) {
|
|
@@ -920,6 +1033,8 @@ async function cmdSync(argv) {
|
|
|
920
1033
|
}
|
|
921
1034
|
}
|
|
922
1035
|
|
|
1036
|
+
await applyCloudConversationsBackfill({ cursors, queueStatePath });
|
|
1037
|
+
|
|
923
1038
|
cursors.updatedAt = new Date().toISOString();
|
|
924
1039
|
await writeJson(cursorsPath, cursors);
|
|
925
1040
|
if (grokHookSignalConsumed && grokHookSignalPath) {
|
|
@@ -1095,10 +1210,12 @@ module.exports = {
|
|
|
1095
1210
|
migrateRolloutCumulativeDeltaBuckets,
|
|
1096
1211
|
reincludeClaudeMemObserverFiles,
|
|
1097
1212
|
repairGrokQueueFromSessionSnapshots,
|
|
1213
|
+
applyCloudConversationsBackfill,
|
|
1098
1214
|
CURSOR_UNKNOWN_MIGRATION_KEY,
|
|
1099
1215
|
ROLLOUT_CUMULATIVE_DELTA_MIGRATION_KEY,
|
|
1100
1216
|
CLAUDE_MEM_OBSERVER_REINCLUDE_KEY,
|
|
1101
1217
|
GROK_APPEND_ONLY_REPAIR_MIGRATION_KEY,
|
|
1218
|
+
CLOUD_CONVERSATIONS_BACKFILL_KEY,
|
|
1102
1219
|
};
|
|
1103
1220
|
|
|
1104
1221
|
function normalizeString(value) {
|
|
@@ -1458,6 +1575,12 @@ async function readQueueBatch(queuePath, startOffset, maxBuckets) {
|
|
|
1458
1575
|
const model = (typeof bucket?.model === "string" ? bucket.model.trim() : "") || "unknown";
|
|
1459
1576
|
bucket.source = source;
|
|
1460
1577
|
bucket.model = model;
|
|
1578
|
+
// Apply the same legacy-row corrections every local reader applies
|
|
1579
|
+
// (local-api readQueueData / project queue / wrapped aggregator). Without
|
|
1580
|
+
// this the cloud permanently kept the RAW legacy values — e.g. old Codex
|
|
1581
|
+
// rows whose input_tokens still include cached tokens (6-7x inflated) —
|
|
1582
|
+
// while the local dashboard showed corrected numbers.
|
|
1583
|
+
bucket = require("../lib/local-api").normalizeQueueRow(bucket);
|
|
1461
1584
|
bucketMap.set(`${source}|${model}|${hourStart}`, bucket);
|
|
1462
1585
|
linesRead += 1;
|
|
1463
1586
|
if (linesRead >= maxBuckets) break;
|
|
@@ -1806,6 +1929,32 @@ async function repairGrokQueueFromSessionSnapshots({ cursors, queuePath, queueSt
|
|
|
1806
1929
|
return true;
|
|
1807
1930
|
}
|
|
1808
1931
|
|
|
1932
|
+
async function applyCloudConversationsBackfill({ cursors, queueStatePath }) {
|
|
1933
|
+
if (!cursors || typeof cursors !== "object") return false;
|
|
1934
|
+
cursors.migrations = cursors.migrations || {};
|
|
1935
|
+
if (cursors.migrations[CLOUD_CONVERSATIONS_BACKFILL_KEY]) return false;
|
|
1936
|
+
|
|
1937
|
+
// Reset ONLY the cloud upload offset. The queue file itself is untouched;
|
|
1938
|
+
// ingest upserts are idempotent per (user, device, hour, source, model),
|
|
1939
|
+
// so replaying the whole queue is safe — it costs upload batches, not
|
|
1940
|
+
// correctness. Project queue is never uploaded and is not touched.
|
|
1941
|
+
let prevOffset = 0;
|
|
1942
|
+
try {
|
|
1943
|
+
const st = (await readJson(queueStatePath)) || {};
|
|
1944
|
+
prevOffset = Number(st.offset || 0);
|
|
1945
|
+
} catch (_e) {
|
|
1946
|
+
/* missing state file — nothing to reset */
|
|
1947
|
+
}
|
|
1948
|
+
if (prevOffset > 0) {
|
|
1949
|
+
await writeJson(queueStatePath, { offset: 0, updatedAt: new Date().toISOString() });
|
|
1950
|
+
}
|
|
1951
|
+
cursors.migrations[CLOUD_CONVERSATIONS_BACKFILL_KEY] = {
|
|
1952
|
+
appliedAt: new Date().toISOString(),
|
|
1953
|
+
previousOffset: prevOffset,
|
|
1954
|
+
};
|
|
1955
|
+
return prevOffset > 0;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1809
1958
|
async function migrateCursorUnknownBuckets({ cursors, queuePath }) {
|
|
1810
1959
|
if (!cursors || typeof cursors !== "object") return;
|
|
1811
1960
|
cursors.migrations = cursors.migrations || {};
|
|
@@ -2169,7 +2318,7 @@ async function repairClaudeQueueFromGroundTruth({
|
|
|
2169
2318
|
return true;
|
|
2170
2319
|
}
|
|
2171
2320
|
|
|
2172
|
-
async function reincludeClaudeMemObserverFiles({ cursors, claudeFiles, queuePath }) {
|
|
2321
|
+
async function reincludeClaudeMemObserverFiles({ cursors, claudeFiles, queuePath, queueStatePath }) {
|
|
2173
2322
|
if (!cursors || typeof cursors !== "object") return false;
|
|
2174
2323
|
const migrations = (cursors.migrations ||= {});
|
|
2175
2324
|
if (migrations[CLAUDE_MEM_OBSERVER_REINCLUDE_KEY]) return false;
|
|
@@ -2207,7 +2356,7 @@ async function reincludeClaudeMemObserverFiles({ cursors, claudeFiles, queuePath
|
|
|
2207
2356
|
}
|
|
2208
2357
|
|
|
2209
2358
|
const queueRowsRelabeled = typeof queuePath === "string" && queuePath
|
|
2210
|
-
? await relabelClaudeMemQueueRows(queuePath)
|
|
2359
|
+
? await relabelClaudeMemQueueRows(queuePath, queueStatePath)
|
|
2211
2360
|
: 0;
|
|
2212
2361
|
|
|
2213
2362
|
migrations[CLAUDE_MEM_OBSERVER_REINCLUDE_KEY] = {
|
|
@@ -2219,7 +2368,7 @@ async function reincludeClaudeMemObserverFiles({ cursors, claudeFiles, queuePath
|
|
|
2219
2368
|
return filesReset > 0 || hashesRemoved > 0 || queueRowsRelabeled > 0;
|
|
2220
2369
|
}
|
|
2221
2370
|
|
|
2222
|
-
async function relabelClaudeMemQueueRows(queuePath) {
|
|
2371
|
+
async function relabelClaudeMemQueueRows(queuePath, queueStatePath = null) {
|
|
2223
2372
|
let raw;
|
|
2224
2373
|
try {
|
|
2225
2374
|
raw = await fs.readFile(queuePath, "utf8");
|
|
@@ -2228,32 +2377,73 @@ async function relabelClaudeMemQueueRows(queuePath) {
|
|
|
2228
2377
|
}
|
|
2229
2378
|
if (!raw || !raw.includes('"claude-mem"')) return 0;
|
|
2230
2379
|
|
|
2380
|
+
// The cloud-upload cursor (queue.state.json `offset`) is a byte position in
|
|
2381
|
+
// the pre-rewrite file. Relabeling shrinks rewritten lines ("claude-mem" →
|
|
2382
|
+
// "claude"), so the old offset would land mid-line in the new file and the
|
|
2383
|
+
// next drainQueueToCloud batch would skip part of a row (or a whole row).
|
|
2384
|
+
// Track the old→new byte mapping while rewriting and remap the offset to
|
|
2385
|
+
// the equivalent line boundary (same pattern as project-usage-purge.js).
|
|
2386
|
+
let previousOffset = 0;
|
|
2387
|
+
if (typeof queueStatePath === "string" && queueStatePath) {
|
|
2388
|
+
try {
|
|
2389
|
+
const st = JSON.parse(await fs.readFile(queueStatePath, "utf8"));
|
|
2390
|
+
const off = Number(st?.offset || 0);
|
|
2391
|
+
if (Number.isFinite(off) && off > 0) previousOffset = off;
|
|
2392
|
+
} catch (_e) {
|
|
2393
|
+
previousOffset = 0;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2231
2397
|
const lines = raw.split("\n");
|
|
2232
2398
|
const out = [];
|
|
2233
2399
|
let relabeled = 0;
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2400
|
+
let inputOffset = 0;
|
|
2401
|
+
let outputOffset = 0;
|
|
2402
|
+
let nextOffset = 0;
|
|
2403
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2404
|
+
const line = lines[i];
|
|
2405
|
+
const isLast = i === lines.length - 1;
|
|
2406
|
+
let outLine = line;
|
|
2407
|
+
if (line) {
|
|
2408
|
+
try {
|
|
2409
|
+
const obj = JSON.parse(line);
|
|
2410
|
+
if (obj && obj.source === "claude-mem") {
|
|
2411
|
+
obj.source = "claude";
|
|
2412
|
+
relabeled += 1;
|
|
2413
|
+
outLine = JSON.stringify(obj);
|
|
2414
|
+
}
|
|
2415
|
+
} catch (_e) {
|
|
2416
|
+
// keep malformed lines verbatim
|
|
2417
|
+
}
|
|
2238
2418
|
}
|
|
2239
|
-
|
|
2419
|
+
out.push(outLine);
|
|
2420
|
+
inputOffset += Buffer.byteLength(line, "utf8") + (isLast ? 0 : 1);
|
|
2421
|
+
outputOffset += Buffer.byteLength(outLine, "utf8") + (isLast ? 0 : 1);
|
|
2422
|
+
// Upload offsets always sit at line boundaries; a mid-line offset
|
|
2423
|
+
// (corruption) rounds down to the previous boundary so no row is skipped
|
|
2424
|
+
// — worst case a row is re-uploaded, and cloud ingest upserts by key.
|
|
2425
|
+
if (inputOffset <= previousOffset) nextOffset = outputOffset;
|
|
2426
|
+
}
|
|
2427
|
+
if (relabeled === 0) return 0;
|
|
2428
|
+
|
|
2429
|
+
// Atomic rewrite: temp file in the same directory + rename, so a crash
|
|
2430
|
+
// mid-write can never leave queue.jsonl truncated.
|
|
2431
|
+
const tmpPath = `${queuePath}.tmp`;
|
|
2432
|
+
await fs.writeFile(tmpPath, out.join("\n"), "utf8");
|
|
2433
|
+
await fs.rename(tmpPath, queuePath);
|
|
2434
|
+
|
|
2435
|
+
if (typeof queueStatePath === "string" && queueStatePath && previousOffset > 0) {
|
|
2436
|
+
let state = {};
|
|
2240
2437
|
try {
|
|
2241
|
-
|
|
2438
|
+
state = JSON.parse(await fs.readFile(queueStatePath, "utf8"));
|
|
2439
|
+
if (!state || typeof state !== "object") state = {};
|
|
2242
2440
|
} catch (_e) {
|
|
2243
|
-
|
|
2244
|
-
continue;
|
|
2245
|
-
}
|
|
2246
|
-
if (obj && obj.source === "claude-mem") {
|
|
2247
|
-
obj.source = "claude";
|
|
2248
|
-
relabeled += 1;
|
|
2249
|
-
out.push(JSON.stringify(obj));
|
|
2250
|
-
} else {
|
|
2251
|
-
out.push(line);
|
|
2441
|
+
state = {};
|
|
2252
2442
|
}
|
|
2443
|
+
state.offset = nextOffset;
|
|
2444
|
+
state.updatedAt = new Date().toISOString();
|
|
2445
|
+
await fs.writeFile(queueStatePath, JSON.stringify(state), "utf8");
|
|
2253
2446
|
}
|
|
2254
|
-
if (relabeled === 0) return 0;
|
|
2255
|
-
|
|
2256
|
-
await fs.writeFile(queuePath, out.join("\n"), "utf8");
|
|
2257
2447
|
return relabeled;
|
|
2258
2448
|
}
|
|
2259
2449
|
|