tokentracker-cli 0.84.9 → 0.86.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.de.md +5 -4
- package/README.ja.md +5 -4
- package/README.ko.md +5 -4
- package/README.md +8 -7
- package/README.zh-CN.md +5 -4
- package/dashboard/dist/assets/{AchievementBadge-BgBFpxmU.js → AchievementBadge-BgbH_mX4.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-DHPD3Fnu.js → AchievementsPage-CQqkh1JS.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-DouWVD-3.js → AchievementsSection-Dmkxpn0s.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-Df9eQiA3.js → BadgeDetailModal-7OQf37lk.js} +1 -1
- package/dashboard/dist/assets/{Card-CQ9wBnz6.js → Card-1PmWZRTK.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-XIO-I-5v.js → ClawdAnimated-CMJFagpX.js} +1 -1
- package/dashboard/dist/assets/{CommandPalette-Cy1cJ91B.js → CommandPalette-D4OrwF1w.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-CwKG4LQM.js → CommunityStatsModal-BDC9EcwZ.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-BNcWqick.js → ConfirmModal-22DL5C_9.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-CEXWzDtt.js +1 -0
- package/dashboard/dist/assets/{DevicePage-pJ2jNkZV.js → DevicePage-8uhHB7kX.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-C2SOtkJN.js → DialogClose-BcJx6VK-.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-D6zxtOM5.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-CGSbB8G7.js → DialogTitle-gRzunAqv.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-CZSiw7me.js → FadeIn-4rlPdBVe.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-ZmQhX_kR.js → HeaderGithubStar-CR003aO3.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-D_YdG369.js → HoverTooltip-Dgz-dM8Q.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-du886dF0.js → IpCheckPage-B4PJRJP_.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-BuAak8lU.js → LandingPage-3JQe-jOZ.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-nU0ARYnN.js → LeaderboardAvatar-CL9LxiEf.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-CngwCvC7.js → LeaderboardPage-DhIzM2Z2.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-CXZJ0e-T.js → LeaderboardProfileModal-bbf5uJR5.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-CZve6s6b.js → LeaderboardProfilePage-CzuPxscw.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-CWvoJPk8.js → LimitsPage-B59rb2gg.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-DyUAAyyE.js → LocalOnlyNotice-BkK-6n1R.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-eEiRfbdv.js → LoginCard-BFGCzEjE.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-C16WdIQm.js → LoginModal-yhkyscFn.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-B2WqpE1t.js → LoginPage-DwU5s0F7.js} +1 -1
- package/dashboard/dist/assets/{PetPage-DtQWrGmI.js → PetPage-BPscYR6z.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-CRhqtFLB.js → PopoverPopup-E7PyGTTB.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-DWNH_wYu.js → ProviderIcon-CvmnzwKU.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-DzCczcWZ.js → ResetPasswordPage-DD6qX616.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-CV9ccq6R.js → SegmentedControl-DjGBL_l1.js} +1 -1
- package/dashboard/dist/assets/{Select-BrogNNk0.js → Select-B2MGmsi2.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-Bpu3swt0.js → SelectItemText-BaTwA_ZF.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-D_FIrMJP.js → ServiceStatusPage-0K0y8A6m.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-C8TvTy1o.js → SessionsPage-CxrtRm3B.js} +2 -2
- package/dashboard/dist/assets/{SettingsPage-Bd-tzJAR.js → SettingsPage-CyvSWvDP.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-CKGU9wQ4.js → SkillsPage-BVOU8t3t.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-DhHW6L-U.js → TokenGalaxy-w7bwGloC.js} +1 -1
- package/dashboard/dist/assets/{ToolbarRootContext-DQo-4pPB.js → ToolbarRootContext-BAzpDZej.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-Ejl8d45g.js → TrendMonitor-DyT7Q4xp.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-CY7f4wCD.js → WidgetsPage-eDDtopQT.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-B4-B2v-v.js → WrappedPage-CKkbdWHF.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-BNUFXCfp.js → __vite-browser-external-SU8sCTLB.js} +23 -16
- package/dashboard/dist/assets/{agent-logos-BRzutmeR.js → agent-logos-bkh9XZJd.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-BcrbPk8c.js → arrow-up-right-D16zCIw8.js} +1 -1
- package/dashboard/dist/assets/{check-BMxeFPfQ.js → check-BFL17JWQ.js} +1 -1
- package/dashboard/dist/assets/{chevron-right-DOstgiRf.js → chevron-right-CUJgrtkp.js} +1 -1
- package/dashboard/dist/assets/{copy-BsKO524t.js → copy-CRQiCjgs.js} +1 -1
- package/dashboard/dist/assets/{download-dadqxej7.js → download-Cd12nDrQ.js} +1 -1
- package/dashboard/dist/assets/{external-link-CTu3BMeU.js → external-link-CvAfnsG-.js} +1 -1
- package/dashboard/dist/assets/{flame-B_IvWcMn.js → flame-CCi681Yl.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-DIT6hLSb.js → format-tokens-BAVt8uTZ.js} +1 -1
- package/dashboard/dist/assets/{icons-mWku76bu.js → icons-CbKC96gR.js} +1 -1
- package/dashboard/dist/assets/{index-D5-w-aGy.js → index--TDvaL1y.js} +1 -1
- package/dashboard/dist/assets/{index-D-gQV-hF.js → index-B5b6PCh5.js} +1 -1
- package/dashboard/dist/assets/{index-CboN9E82.js → index-B9LWn-mo.js} +1 -1
- package/dashboard/dist/assets/{info-x6l352SW.js → info-BPFpD--M.js} +1 -1
- package/dashboard/dist/assets/{limits-providers-Bo12VlLJ.js → limits-providers-Cyedt-WJ.js} +1 -1
- package/dashboard/dist/assets/{link-2-eo_xPxH1.js → link-2-BkEPPdfC.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-C4n1iGBv.js → loader-circle-CzASiylJ.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-pR8xwsI_.js → maximize-2-CkwnHUJn.js} +1 -1
- package/dashboard/dist/assets/{provider-display-ErggQWu4.js → provider-display-BXBBedC-.js} +2 -2
- package/dashboard/dist/assets/{react-DYKg02Aq.js → react-BXSctDQj.js} +1 -1
- package/dashboard/dist/assets/{search-B_jbUwPS.js → search-_qGma9tM.js} +1 -1
- package/dashboard/dist/assets/{skills-api-DY5ruGmw.js → skills-api-DdmJjnJ9.js} +1 -1
- package/dashboard/dist/assets/{store-Cu8i1y26.js → store-owYCN530.js} +1 -1
- package/dashboard/dist/assets/{terminal-D3j6X68s.js → terminal-Clzw-KEF.js} +1 -1
- package/dashboard/dist/assets/{trash-2-CFpwgJw5.js → trash-2-C5Giwp_E.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-C0fKq66G.js → use-community-stats-Dk8UjLEr.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-TOp4UAnI.js → use-limits-display-prefs-CArM595Q.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-BZie_7z1.js → use-native-settings-Cpd69kiF.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-Bq92YyUp.js → use-ordered-list--CzwhBTx.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-CcILnpU1.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-BcpytkES.js → use-session-efficiency-pref-DUXO8cCg.js} +1 -1
- package/dashboard/dist/assets/{use-transform-Dmmrne2z.js → use-transform-Cg3kuMj_.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-Cw0Z9Tew.js → use-usage-limits-DY9_mV28.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-DyyonINj.js → useCurrency-BIe86hMn.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-DvWw86lJ.js → useScrollLock-7-hejkNC.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-CBkvGfJ1.js → useTokenFormat-DEtLbRgQ.js} +1 -1
- package/dashboard/dist/assets/{zap-CnYA39Ug.js → zap-DUxY1d-O.js} +1 -1
- package/dashboard/dist/index.html +12 -10
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/llms.txt +3 -2
- package/dashboard/dist/share.html +1 -1
- package/package.json +2 -2
- package/src/commands/init.js +1 -0
- package/src/commands/status.js +129 -9
- package/src/commands/sync.js +265 -54
- package/src/lib/claude-categorizer.js +36 -9
- package/src/lib/diagnostics.js +86 -13
- package/src/lib/doctor.js +17 -0
- package/src/lib/grok-context-breakdown.js +794 -0
- package/src/lib/install-resolver.js +10 -2
- package/src/lib/local-api.js +21 -3
- package/src/lib/multi-install-parser.js +9 -1
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +788 -44
- package/src/lib/session-analytics.js +332 -9
- package/dashboard/dist/assets/DashboardPage-Dns_1yXL.js +0 -1
- package/dashboard/dist/assets/DialogDescription-Bhfqqmll.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-ZiVeOkvb.js +0 -1
package/src/commands/status.js
CHANGED
|
@@ -48,6 +48,9 @@ const {
|
|
|
48
48
|
resolveKimiCodeWireFiles,
|
|
49
49
|
resolveKiroCliDbPath,
|
|
50
50
|
resolveKiroCliSessionFiles,
|
|
51
|
+
resolveKiroBasePath,
|
|
52
|
+
resolveKiroDbPath,
|
|
53
|
+
resolveKiroJsonlPath,
|
|
51
54
|
resolveCodebuddyHome,
|
|
52
55
|
resolveCodebuddyProjectFiles,
|
|
53
56
|
resolveWorkbuddyHome,
|
|
@@ -63,6 +66,7 @@ const {
|
|
|
63
66
|
resolveRoocodeTaskFiles,
|
|
64
67
|
resolveZedDbPath,
|
|
65
68
|
resolveQoderDbPaths,
|
|
69
|
+
resolveClaudeScienceDbPaths,
|
|
66
70
|
resolveAnythingllmDbPath,
|
|
67
71
|
resolveGooseDbPath,
|
|
68
72
|
listDroidSettingsFiles,
|
|
@@ -235,10 +239,100 @@ async function cmdStatus(argv = []) {
|
|
|
235
239
|
// event sessions. End-user dashboards show them merged under "Kiro"; this
|
|
236
240
|
// status line surfaces which passive sources are actually present.
|
|
237
241
|
const kiroCliDbPath = resolveKiroCliDbPath(process.env);
|
|
238
|
-
const kiroCliDbExists = fssync.existsSync(kiroCliDbPath);
|
|
239
242
|
const kiroCliSessionFiles = resolveKiroCliSessionFiles(process.env);
|
|
240
|
-
const
|
|
241
|
-
|
|
243
|
+
const kiroCliNativePresent =
|
|
244
|
+
fssync.existsSync(kiroCliDbPath) || kiroCliSessionFiles.length > 0;
|
|
245
|
+
// WSL install discovery mirrors sync: overrides pin a single install.
|
|
246
|
+
let kiroCliWsl = null; // { db, sessionFiles }
|
|
247
|
+
if (
|
|
248
|
+
process.platform === "win32" &&
|
|
249
|
+
!process.env.KIRO_CLI_DB_PATH && !process.env.KIRO_HOME &&
|
|
250
|
+
wsl.shouldProbeWsl(process.env)
|
|
251
|
+
) {
|
|
252
|
+
const wslKiroHomeDir = wsl.discoverWslHome(".kiro");
|
|
253
|
+
const wslCliDataDir = wsl.discoverWslHome(".local/share/kiro-cli");
|
|
254
|
+
const wslHomeRoot = wslKiroHomeDir
|
|
255
|
+
? path.dirname(wslKiroHomeDir)
|
|
256
|
+
: (wslCliDataDir ? path.dirname(path.dirname(path.dirname(wslCliDataDir))) : null);
|
|
257
|
+
if (wslHomeRoot) {
|
|
258
|
+
const wslDb = path.join(wslHomeRoot, ".local", "share", "kiro-cli", "data.sqlite3");
|
|
259
|
+
const wslFiles = resolveKiroCliSessionFiles({
|
|
260
|
+
...process.env,
|
|
261
|
+
KIRO_CLI_DB_PATH: wslDb,
|
|
262
|
+
KIRO_HOME: path.join(wslHomeRoot, ".kiro"),
|
|
263
|
+
});
|
|
264
|
+
if (fssync.existsSync(wslDb) || wslFiles.length > 0) {
|
|
265
|
+
kiroCliWsl = { db: wslDb, sessionFiles: wslFiles };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const kiroCliPaths = process.platform === "win32"
|
|
270
|
+
? wsl.resolveAllWin32Paths({
|
|
271
|
+
nativeValue: kiroCliNativePresent ? kiroCliDbPath : null,
|
|
272
|
+
wslValue: kiroCliWsl ? kiroCliWsl.db : null,
|
|
273
|
+
env: process.env,
|
|
274
|
+
platform: "win32",
|
|
275
|
+
})
|
|
276
|
+
: { native: kiroCliNativePresent ? kiroCliDbPath : null, wsl: null };
|
|
277
|
+
// Non-both modes park the picked value in the native slot — label by
|
|
278
|
+
// marker identity, not slot name. A session-files-only install has no DB
|
|
279
|
+
// on disk; show the sessions dir instead of a nonexistent DB path.
|
|
280
|
+
const kiroCliMarkers = [kiroCliPaths.native, kiroCliPaths.wsl].filter(Boolean);
|
|
281
|
+
const kiroCliActive = kiroCliMarkers.map((m) => {
|
|
282
|
+
if (kiroCliWsl && m === kiroCliWsl.db) {
|
|
283
|
+
const shown = fssync.existsSync(m) || kiroCliWsl.sessionFiles.length === 0
|
|
284
|
+
? m
|
|
285
|
+
: path.dirname(kiroCliWsl.sessionFiles[0]);
|
|
286
|
+
return `WSL: ${shown}`;
|
|
287
|
+
}
|
|
288
|
+
const shown = fssync.existsSync(m) || kiroCliSessionFiles.length === 0
|
|
289
|
+
? m
|
|
290
|
+
: path.dirname(kiroCliSessionFiles[0]);
|
|
291
|
+
return `native: ${shown}`;
|
|
292
|
+
});
|
|
293
|
+
const kiroCliFileCount =
|
|
294
|
+
(kiroCliMarkers.includes(kiroCliDbPath) ? kiroCliSessionFiles.length : 0) +
|
|
295
|
+
(kiroCliWsl && kiroCliMarkers.includes(kiroCliWsl.db) ? kiroCliWsl.sessionFiles.length : 0);
|
|
296
|
+
const kiroCliDbFound = kiroCliMarkers.some((m) => {
|
|
297
|
+
try { return fssync.existsSync(m); } catch (_e) { return false; }
|
|
298
|
+
});
|
|
299
|
+
const kiroCliInstalled = kiroCliMarkers.length > 0;
|
|
300
|
+
|
|
301
|
+
// Kiro IDE — passive scan of globalStorage dev_data (SQLite or JSONL).
|
|
302
|
+
const kiroIdeNativeBase = resolveKiroBasePath(process.env);
|
|
303
|
+
const wslKiroIdeBase = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
|
|
304
|
+
? wsl.discoverWslHome(".config/Kiro/User/globalStorage/kiro.kiroagent")
|
|
305
|
+
: null;
|
|
306
|
+
const kiroIdePaths = resolveInstallPaths({ nativeValue: kiroIdeNativeBase, wslValue: wslKiroIdeBase });
|
|
307
|
+
const kiroIdeHasData = (base) => Boolean(base)
|
|
308
|
+
&& (fssync.existsSync(resolveKiroDbPath(base)) || fssync.existsSync(resolveKiroJsonlPath(base)));
|
|
309
|
+
const kiroIdeActive = [kiroIdePaths.native, kiroIdePaths.wsl]
|
|
310
|
+
.filter((base) => kiroIdeHasData(base))
|
|
311
|
+
.map((base) => (wslKiroIdeBase && base === wslKiroIdeBase ? `WSL: ${base}` : `native: ${base}`));
|
|
312
|
+
const kiroIdeInstalled = kiroIdeActive.length > 0;
|
|
313
|
+
|
|
314
|
+
// Claude Code — dual-install aware projects scan (#307). Mirrors sync:
|
|
315
|
+
// installs are UNIONED (not single-picked by mode) so a WSL ~/.claude
|
|
316
|
+
// never hides the native one from view, and labels come from install
|
|
317
|
+
// identity rather than resolveAllWin32Paths' slot names.
|
|
318
|
+
const claudeCodeActive = [];
|
|
319
|
+
{
|
|
320
|
+
const claudeHomesStatus = [];
|
|
321
|
+
if (process.platform !== "win32" || wsl.shouldProbeNative(process.env)) {
|
|
322
|
+
claudeHomesStatus.push({ dir: path.join(home, ".claude"), label: "native" });
|
|
323
|
+
}
|
|
324
|
+
const wslClaudeHomeStatus = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
|
|
325
|
+
? wsl.discoverWslHome(".claude")
|
|
326
|
+
: null;
|
|
327
|
+
if (wslClaudeHomeStatus) claudeHomesStatus.push({ dir: wslClaudeHomeStatus, label: "WSL" });
|
|
328
|
+
for (const { dir, label } of claudeHomesStatus) {
|
|
329
|
+
const projects = path.join(dir, "projects");
|
|
330
|
+
try {
|
|
331
|
+
if (fssync.existsSync(projects)) claudeCodeActive.push(`${label}: ${projects}`);
|
|
332
|
+
} catch (_e) {}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const claudeCodeInstalled = claudeCodeActive.length > 0;
|
|
242
336
|
|
|
243
337
|
// AnythingLLM Desktop — per-message token metrics in workspace_chats.
|
|
244
338
|
const anythingllmDbPath = resolveAnythingllmDbPath(process.env);
|
|
@@ -332,6 +426,14 @@ async function cmdStatus(argv = []) {
|
|
|
332
426
|
const qoderInstalled = qoderActive.length > 0;
|
|
333
427
|
const qoderDbPath = qoderActive.join(" | ");
|
|
334
428
|
|
|
429
|
+
// Claude Science — token usage lives on the `frames` table of operon-cli.db.
|
|
430
|
+
// Unlike the native/WSL pair other providers resolve to, this is an open-ended
|
|
431
|
+
// list: multi-org installs keep one DB per org (and on Windows they all sit
|
|
432
|
+
// inside WSL), so the resolver already returns only paths that exist.
|
|
433
|
+
const claudeScienceActive = resolveClaudeScienceDbPaths({ home, env: process.env });
|
|
434
|
+
const claudeScienceInstalled = claudeScienceActive.length > 0;
|
|
435
|
+
const claudeScienceDbPath = claudeScienceActive.join(" | ");
|
|
436
|
+
|
|
335
437
|
// OpenCode (JSON files + SQLite DB) — passive scan of storage/message/ and opencode.db.
|
|
336
438
|
const opencodeStorageNativeValue = process.env.OPENCODE_HOME || path.join(xdgDataHome, "opencode");
|
|
337
439
|
const wslOpencodeStorageDir = process.platform === "win32" && shouldProbeWsl(process.env)
|
|
@@ -650,13 +752,19 @@ async function cmdStatus(argv = []) {
|
|
|
650
752
|
kiro_cli: kiroCliInstalled
|
|
651
753
|
? {
|
|
652
754
|
installed: true,
|
|
653
|
-
detail:
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
files:
|
|
755
|
+
detail: kiroCliActive.join(" | "),
|
|
756
|
+
database: kiroCliMarkers.find((m) => {
|
|
757
|
+
try { return fssync.existsSync(m); } catch (_e) { return false; }
|
|
758
|
+
}) || null,
|
|
759
|
+
files: kiroCliFileCount,
|
|
658
760
|
}
|
|
659
761
|
: { installed: false },
|
|
762
|
+
kiro_ide: kiroIdeInstalled
|
|
763
|
+
? { installed: true, detail: kiroIdeActive.join(" | ") }
|
|
764
|
+
: { installed: false },
|
|
765
|
+
claude_code: claudeCodeInstalled
|
|
766
|
+
? { installed: true, detail: claudeCodeActive.join(" | ") }
|
|
767
|
+
: { installed: false },
|
|
660
768
|
codebuddy: codebuddyInstalled
|
|
661
769
|
? { installed: true, files: codebuddyFiles.length }
|
|
662
770
|
: { installed: false },
|
|
@@ -687,6 +795,9 @@ async function cmdStatus(argv = []) {
|
|
|
687
795
|
qoder: qoderInstalled
|
|
688
796
|
? { installed: true, detail: qoderDbPath }
|
|
689
797
|
: { installed: false },
|
|
798
|
+
"claude-science": claudeScienceInstalled
|
|
799
|
+
? { installed: true, detail: claudeScienceDbPath }
|
|
800
|
+
: { installed: false },
|
|
690
801
|
kilocode: kilocodeInstalled
|
|
691
802
|
? { installed: true, files: kilocodeTaskFiles.length }
|
|
692
803
|
: { installed: false },
|
|
@@ -761,6 +872,9 @@ async function cmdStatus(argv = []) {
|
|
|
761
872
|
`- Codex notify: ${notifyConfigured ? JSON.stringify(codexNotify) : "unset"}`,
|
|
762
873
|
`- Every Code notify: ${everyCodeConfigured ? JSON.stringify(everyCodeNotify) : "unset"}`,
|
|
763
874
|
`- Claude hooks: ${claudeHookConfigured ? "set" : "unset"}`,
|
|
875
|
+
claudeCodeInstalled
|
|
876
|
+
? `- Claude Code: projects found (${claudeCodeActive.join(" | ")})`
|
|
877
|
+
: null,
|
|
764
878
|
`- Gemini hooks: ${geminiHookConfigured ? "set" : "unset"}`,
|
|
765
879
|
`- Opencode plugin: ${opencodePluginConfigured ? "set" : "unset"}`,
|
|
766
880
|
`- OpenClaw session plugin: ${openclawSessionPluginState?.configured ? "set" : "unset"}`,
|
|
@@ -770,7 +884,10 @@ async function cmdStatus(argv = []) {
|
|
|
770
884
|
? `- Kimi Code: passive reader (${kimiWireFiles.length + kimiCodeWireFiles.length} wire.jsonl file${(kimiWireFiles.length + kimiCodeWireFiles.length) !== 1 ? "s" : ""} found, directories: ${kimiActive.join(" | ") || "none"})`
|
|
771
885
|
: null,
|
|
772
886
|
kiroCliInstalled
|
|
773
|
-
? `- Kiro CLI: passive reader (${
|
|
887
|
+
? `- Kiro CLI: passive reader (${kiroCliFileCount} session file${kiroCliFileCount !== 1 ? "s" : ""} found, SQLite ${kiroCliDbFound ? "found" : "not found"}, installs: ${kiroCliActive.join(" | ")}; tokens approximated from char lengths and merged under 'kiro')`
|
|
888
|
+
: null,
|
|
889
|
+
kiroIdeInstalled
|
|
890
|
+
? `- Kiro IDE: passive reader (${kiroIdeActive.join(" | ")})`
|
|
774
891
|
: null,
|
|
775
892
|
codebuddyInstalled
|
|
776
893
|
? `- CodeBuddy hooks: ${codebuddyHookConfigured ? "set" : "unset"} (${codebuddyFiles.length} usage file${codebuddyFiles.length !== 1 ? "s" : ""} found)`
|
|
@@ -802,6 +919,9 @@ async function cmdStatus(argv = []) {
|
|
|
802
919
|
qoderInstalled
|
|
803
920
|
? `- Qoder: passive reader (${qoderDbPath})`
|
|
804
921
|
: null,
|
|
922
|
+
claudeScienceInstalled
|
|
923
|
+
? `- Claude Science: passive reader (${claudeScienceDbPath})`
|
|
924
|
+
: null,
|
|
805
925
|
opencodeInstalled
|
|
806
926
|
? `- OpenCode: passive reader (storage: ${opencodeStorageActive.join(" | ") || "not found"}, DB: ${opencodeDbActive.join(" | ") || "not found"})`
|
|
807
927
|
: null,
|
package/src/commands/sync.js
CHANGED
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
readQoderDbMessages,
|
|
25
25
|
resolveKiroDbPath,
|
|
26
26
|
resolveKiroJsonlPath,
|
|
27
|
+
resolveKiroBasePath,
|
|
27
28
|
resolveHermesPath,
|
|
28
29
|
resolveCopilotOtelPaths,
|
|
29
30
|
normalizeCopilotDbPath,
|
|
@@ -39,12 +40,20 @@ const {
|
|
|
39
40
|
parseOpencodeDbIncremental,
|
|
40
41
|
parseQoderDbIncremental,
|
|
41
42
|
parseOpenclawIncremental,
|
|
43
|
+
resolveOpenclawSessionFiles,
|
|
44
|
+
resolveOpenclawHome,
|
|
45
|
+
openclawCursorKey,
|
|
46
|
+
resolveClaudeScienceDbPaths,
|
|
47
|
+
readClaudeScienceFrames,
|
|
48
|
+
parseClaudeScienceIncremental,
|
|
42
49
|
parseCursorApiIncremental,
|
|
43
50
|
parseKiroIncremental,
|
|
44
51
|
parseHermesIncremental,
|
|
45
52
|
gooseInstallOwnsCursor,
|
|
46
53
|
zedInstallOwnsCursor,
|
|
47
54
|
hermesInstallOwnsCursor,
|
|
55
|
+
kiroInstallOwnsCursor,
|
|
56
|
+
kiroCliInstallOwnsCursor,
|
|
48
57
|
copilotOtelCursorHasLegacyCliUsage,
|
|
49
58
|
pruneCopilotUsageClaims,
|
|
50
59
|
parseCopilotIncremental,
|
|
@@ -234,6 +243,7 @@ const AUTO_SYNC_SOURCES = new Set([
|
|
|
234
243
|
"antigravity",
|
|
235
244
|
"anythingllm",
|
|
236
245
|
"claude",
|
|
246
|
+
"claude-science",
|
|
237
247
|
"codebuddy",
|
|
238
248
|
"codex",
|
|
239
249
|
"copilot",
|
|
@@ -443,7 +453,25 @@ async function cmdSync(argv, context = {}) {
|
|
|
443
453
|
}
|
|
444
454
|
let grokHookSignalConsumed = false;
|
|
445
455
|
|
|
446
|
-
|
|
456
|
+
// Claude Code home — dual-install aware (#307): a Windows host may carry
|
|
457
|
+
// a native ~/.claude plus a WSL install reachable over \\wsl$. Unlike the
|
|
458
|
+
// SQLite providers, Claude UNIONS every allowed install instead of taking
|
|
459
|
+
// resolveAllWin32Paths' single pick: under the default wsl-first mode a
|
|
460
|
+
// WSL ~/.claude would otherwise silently evict the native one from
|
|
461
|
+
// collection — and from the "disk is truth" ground-truth repair below,
|
|
462
|
+
// which would then erase the native history. The file-hash and
|
|
463
|
+
// message-hash dedup layers make the union safe. Native is listed first
|
|
464
|
+
// so the cross-environment file dedup keeps the native copy as primary.
|
|
465
|
+
const claudeNativeHome = path.join(home, ".claude");
|
|
466
|
+
const wslClaudeHome = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
|
|
467
|
+
? wsl.discoverWslHome(".claude")
|
|
468
|
+
: null;
|
|
469
|
+
const claudeInstallHomes = [];
|
|
470
|
+
if (process.platform !== "win32" || wsl.shouldProbeNative(process.env)) {
|
|
471
|
+
claudeInstallHomes.push(claudeNativeHome);
|
|
472
|
+
}
|
|
473
|
+
if (wslClaudeHome) claudeInstallHomes.push(wslClaudeHome);
|
|
474
|
+
const claudeProjectsDirs = claudeInstallHomes.map((h) => path.join(h, "projects"));
|
|
447
475
|
const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
|
|
448
476
|
const kiloHome = process.env.KILO_HOME || path.join(xdgDataHome, "kilo");
|
|
449
477
|
const mimoHome = process.env.MIMO_HOME || path.join(xdgDataHome, "mimocode");
|
|
@@ -452,7 +480,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
452
480
|
// OpenClaw session plugin integration: lifecycle hooks request an
|
|
453
481
|
// OpenClaw-only auto sync so unrelated providers do not get walked.
|
|
454
482
|
const openclawSignal = opts.fromOpenclaw
|
|
455
|
-
? resolveOpenclawSignal({
|
|
483
|
+
? resolveOpenclawSignal({ env: process.env })
|
|
456
484
|
: null;
|
|
457
485
|
|
|
458
486
|
const autoSourceScope = resolveAutoSourceScope(opts);
|
|
@@ -647,9 +675,38 @@ async function cmdSync(argv, context = {}) {
|
|
|
647
675
|
codexCursorLoadRestarted = Boolean(loadResult?.restarted);
|
|
648
676
|
}
|
|
649
677
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
678
|
+
// Plugin-triggered sync points at one specific session file; a normal full
|
|
679
|
+
// sync also passively scans every on-disk OpenClaw transcript so usage is
|
|
680
|
+
// captured even when the session plugin never fires (issue #264). The scan
|
|
681
|
+
// is gated to full syncs so a scoped `--from-openclaw` hook still only
|
|
682
|
+
// touches its own file and the 5-minute background tick stays cheap. The
|
|
683
|
+
// event-identity dedup makes the plugin and passive paths idempotent, so
|
|
684
|
+
// overlap on the same file is safe.
|
|
685
|
+
//
|
|
686
|
+
// Keyed by openclawCursorKey, not the raw path: that is the same identity
|
|
687
|
+
// the parser assigns cursors by, so a plugin-supplied path and a scanned
|
|
688
|
+
// path that differ only in Windows casing collapse to one entry instead of
|
|
689
|
+
// being parsed twice.
|
|
690
|
+
const openclawSessionFiles = new Map();
|
|
691
|
+
if (openclawSignal?.sessionFile) {
|
|
692
|
+
openclawSessionFiles.set(openclawCursorKey(openclawSignal.sessionFile), {
|
|
693
|
+
path: openclawSignal.sessionFile,
|
|
694
|
+
source: "openclaw",
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
if (isFullSourceScan && sourceAllowed("openclaw")) {
|
|
698
|
+
try {
|
|
699
|
+
for (const f of await resolveOpenclawSessionFiles(process.env)) {
|
|
700
|
+
const key = openclawCursorKey(f);
|
|
701
|
+
if (!openclawSessionFiles.has(key)) {
|
|
702
|
+
openclawSessionFiles.set(key, { path: f, source: "openclaw" });
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
} catch (err) {
|
|
706
|
+
warnProviderParseFailure("OpenClaw", err, opts);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
const openclawFiles = Array.from(openclawSessionFiles.values());
|
|
653
710
|
|
|
654
711
|
if (progress?.enabled) {
|
|
655
712
|
progress.start(
|
|
@@ -719,7 +776,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
719
776
|
|
|
720
777
|
let openclawResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
721
778
|
if (sourceAllowed("openclaw") && openclawFiles.length > 0) {
|
|
722
|
-
//
|
|
779
|
+
// Parses plugin-triggered and/or passively discovered session files.
|
|
723
780
|
try {
|
|
724
781
|
openclawResult = await parseOpenclawIncremental({
|
|
725
782
|
sessionFiles: openclawFiles,
|
|
@@ -751,7 +808,18 @@ async function cmdSync(argv, context = {}) {
|
|
|
751
808
|
openclawResult.eventsAggregated += openclawFallback.eventsAggregated;
|
|
752
809
|
openclawResult.bucketsQueued += openclawFallback.bucketsQueued;
|
|
753
810
|
|
|
754
|
-
|
|
811
|
+
let claudeFiles = [];
|
|
812
|
+
if (sourceAllowed("claude")) {
|
|
813
|
+
const seenClaudeFiles = new Set();
|
|
814
|
+
for (const dir of claudeProjectsDirs) {
|
|
815
|
+
for (const f of await listClaudeProjectFiles(dir)) {
|
|
816
|
+
if (!seenClaudeFiles.has(f)) {
|
|
817
|
+
seenClaudeFiles.add(f);
|
|
818
|
+
claudeFiles.push(f);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
755
823
|
if (isFullSourceScan) {
|
|
756
824
|
await reincludeClaudeMemObserverFiles({ cursors, claudeFiles, queuePath, queueStatePath });
|
|
757
825
|
await repairClaudeQueueFromGroundTruth({
|
|
@@ -760,6 +828,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
760
828
|
queueStatePath,
|
|
761
829
|
projectQueuePath,
|
|
762
830
|
projectQueueStatePath,
|
|
831
|
+
rootDirs: claudeProjectsDirs,
|
|
763
832
|
});
|
|
764
833
|
}
|
|
765
834
|
let claudeResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
@@ -1022,6 +1091,45 @@ async function cmdSync(argv, context = {}) {
|
|
|
1022
1091
|
}
|
|
1023
1092
|
}
|
|
1024
1093
|
|
|
1094
|
+
// ── Claude Science (Anthropic's local research workbench, issue #246) ──
|
|
1095
|
+
// Per-frame token usage lives on the `frames` table of operon-cli.db.
|
|
1096
|
+
// Passive, incremental, subtract-on-change. There can be more than one DB:
|
|
1097
|
+
// multi-org installs keep one per org, and on Windows the app runs inside
|
|
1098
|
+
// WSL so the DB is on the distro home.
|
|
1099
|
+
let claudeScienceResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1100
|
+
if (sourceAllowed("claude-science")) {
|
|
1101
|
+
// Resolution is inside the error isolation too: it touches the filesystem
|
|
1102
|
+
// and (on Windows) probes WSL, and a throw here would abort cmdSync before
|
|
1103
|
+
// any provider's buckets are written, not just this one's.
|
|
1104
|
+
let claudeScienceDbPaths = [];
|
|
1105
|
+
try {
|
|
1106
|
+
claudeScienceDbPaths = resolveClaudeScienceDbPaths({ home, env: process.env });
|
|
1107
|
+
} catch (err) {
|
|
1108
|
+
warnProviderParseFailure("Claude Science", err, opts);
|
|
1109
|
+
}
|
|
1110
|
+
if (claudeScienceDbPaths.length > 0 && progress?.enabled) {
|
|
1111
|
+
progress.start(`Parsing Claude Science ${renderBar(0)} | buckets 0`);
|
|
1112
|
+
}
|
|
1113
|
+
for (const claudeScienceDbPath of claudeScienceDbPaths) {
|
|
1114
|
+
try {
|
|
1115
|
+
const dbRows = await readClaudeScienceFrames(claudeScienceDbPath);
|
|
1116
|
+
const parsed = await parseClaudeScienceIncremental({
|
|
1117
|
+
dbRows,
|
|
1118
|
+
cursors,
|
|
1119
|
+
queuePath,
|
|
1120
|
+
onProgress: makeProviderProgress("Claude Science"),
|
|
1121
|
+
});
|
|
1122
|
+
claudeScienceResult = mergeParseResult(claudeScienceResult, {
|
|
1123
|
+
recordsProcessed: parsed.recordsProcessed || 0,
|
|
1124
|
+
eventsAggregated: parsed.eventsAggregated || 0,
|
|
1125
|
+
bucketsQueued: parsed.bucketsQueued || 0,
|
|
1126
|
+
});
|
|
1127
|
+
} catch (err) {
|
|
1128
|
+
warnProviderParseFailure("Claude Science", err, opts);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1025
1133
|
async function parseOpencodeDbForInstall({ dbPath, readFn, source, cursorKey, ...rest }) {
|
|
1026
1134
|
if (!dbPath || !fssync.existsSync(dbPath)) {
|
|
1027
1135
|
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
@@ -1344,30 +1452,44 @@ async function cmdSync(argv, context = {}) {
|
|
|
1344
1452
|
}
|
|
1345
1453
|
}
|
|
1346
1454
|
|
|
1347
|
-
// ── Kiro (SQLite-based, with JSONL fallback) ──
|
|
1455
|
+
// ── Kiro (SQLite-based, with JSONL fallback; dual-install aware) ──
|
|
1348
1456
|
let kiroResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1457
|
+
if (sourceAllowed("kiro")) {
|
|
1458
|
+
const kiroNativeBase = resolveKiroBasePath(process.env);
|
|
1459
|
+
const wslKiroBase = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
|
|
1460
|
+
? wsl.discoverWslHome(".config/Kiro/User/globalStorage/kiro.kiroagent")
|
|
1461
|
+
: null;
|
|
1462
|
+
const kiroPaths = resolveInstallPaths({ nativeValue: kiroNativeBase, wslValue: wslKiroBase });
|
|
1463
|
+
// resolveInstallPaths only checks the base dir (and skips the check off
|
|
1464
|
+
// win32); keep the original per-install db/jsonl presence gate so empty
|
|
1465
|
+
// installs never spin up a parse or seed a cursor namespace.
|
|
1466
|
+
const kiroHasData = (base) => Boolean(base)
|
|
1467
|
+
&& (fssync.existsSync(resolveKiroDbPath(base)) || fssync.existsSync(resolveKiroJsonlPath(base)));
|
|
1468
|
+
if (!kiroHasData(kiroPaths.native)) kiroPaths.native = null;
|
|
1469
|
+
if (!kiroHasData(kiroPaths.wsl)) kiroPaths.wsl = null;
|
|
1470
|
+
if (kiroPaths.native || kiroPaths.wsl) {
|
|
1471
|
+
if (progress?.enabled) {
|
|
1472
|
+
progress.start(`Parsing Kiro ${renderBar(0)} | buckets 0`);
|
|
1473
|
+
}
|
|
1474
|
+
try {
|
|
1475
|
+
kiroResult = await multiInstallParse({
|
|
1476
|
+
paths: kiroPaths,
|
|
1477
|
+
parserFn: parseKiroIncremental,
|
|
1478
|
+
providerName: "kiro",
|
|
1479
|
+
cursors,
|
|
1480
|
+
getParams: (base) => ({
|
|
1481
|
+
basePath: base,
|
|
1482
|
+
dbPath: resolveKiroDbPath(base),
|
|
1483
|
+
jsonlPath: resolveKiroJsonlPath(base),
|
|
1484
|
+
}),
|
|
1485
|
+
queuePath,
|
|
1486
|
+
onProgress: makeProviderProgress("Kiro"),
|
|
1487
|
+
detectInstall: (base, flatState) =>
|
|
1488
|
+
kiroInstallOwnsCursor(resolveKiroDbPath(base), flatState),
|
|
1489
|
+
});
|
|
1490
|
+
} catch (err) {
|
|
1491
|
+
warnProviderParseFailure("Kiro", err, opts);
|
|
1492
|
+
}
|
|
1371
1493
|
}
|
|
1372
1494
|
}
|
|
1373
1495
|
|
|
@@ -1454,28 +1576,79 @@ async function cmdSync(argv, context = {}) {
|
|
|
1454
1576
|
// token counts (billing is credit-based on Bedrock); we approximate at
|
|
1455
1577
|
// 4 chars/token from user prompt chars and assistant response chars.
|
|
1456
1578
|
let kiroCliResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1579
|
+
if (sourceAllowed("kiro")) {
|
|
1580
|
+
const kiroCliDb = resolveKiroCliDbPath(process.env);
|
|
1581
|
+
const kiroCliSessionFiles = resolveKiroCliSessionFiles(process.env);
|
|
1582
|
+
const nativeCliPresent = fssync.existsSync(kiroCliDb) || kiroCliSessionFiles.length > 0;
|
|
1583
|
+
|
|
1584
|
+
// Explicit overrides pin a single install — never mix them with WSL
|
|
1585
|
+
// auto-discovery (mirrors the Hermes TOKENTRACKER_HERMES_HOME branch).
|
|
1586
|
+
const kiroCliOverride = Boolean(process.env.KIRO_CLI_DB_PATH || process.env.KIRO_HOME);
|
|
1587
|
+
let wslKiroCliEnv = null;
|
|
1588
|
+
let wslKiroCliMarker = null;
|
|
1589
|
+
if (!kiroCliOverride && process.platform === "win32" && wsl.shouldProbeWsl(process.env)) {
|
|
1590
|
+
// A WSL install owns BOTH a data dir (~/.local/share/kiro-cli) and a
|
|
1591
|
+
// sessions home (~/.kiro). Derive both from whichever probe hits so
|
|
1592
|
+
// the per-install env never falls back to native paths.
|
|
1593
|
+
const wslKiroHomeDir = wsl.discoverWslHome(".kiro");
|
|
1594
|
+
const wslCliDataDir = wsl.discoverWslHome(".local/share/kiro-cli");
|
|
1595
|
+
const wslHomeRoot = wslKiroHomeDir
|
|
1596
|
+
? path.dirname(wslKiroHomeDir)
|
|
1597
|
+
: (wslCliDataDir ? path.dirname(path.dirname(path.dirname(wslCliDataDir))) : null);
|
|
1598
|
+
if (wslHomeRoot) {
|
|
1599
|
+
const wslCliDb = path.join(wslHomeRoot, ".local", "share", "kiro-cli", "data.sqlite3");
|
|
1600
|
+
wslKiroCliEnv = {
|
|
1601
|
+
...process.env,
|
|
1602
|
+
KIRO_CLI_DB_PATH: wslCliDb,
|
|
1603
|
+
KIRO_HOME: path.join(wslHomeRoot, ".kiro"),
|
|
1604
|
+
};
|
|
1605
|
+
const wslCliPresent = fssync.existsSync(wslCliDb)
|
|
1606
|
+
|| resolveKiroCliSessionFiles(wslKiroCliEnv).length > 0;
|
|
1607
|
+
if (wslCliPresent) wslKiroCliMarker = wslCliDb;
|
|
1608
|
+
}
|
|
1462
1609
|
}
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1610
|
+
|
|
1611
|
+
// Paths here are install markers only — the parser resolves its DB and
|
|
1612
|
+
// session files from the per-install env (KIRO_CLI_DB_PATH/KIRO_HOME).
|
|
1613
|
+
const kiroCliPaths = process.platform === "win32"
|
|
1614
|
+
? wsl.resolveAllWin32Paths({
|
|
1615
|
+
nativeValue: nativeCliPresent ? kiroCliDb : null,
|
|
1616
|
+
wslValue: wslKiroCliMarker,
|
|
1467
1617
|
env: process.env,
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1618
|
+
platform: "win32",
|
|
1619
|
+
})
|
|
1620
|
+
: { native: nativeCliPresent ? kiroCliDb : null, wsl: null };
|
|
1621
|
+
const kiroCliEnvFor = (p) =>
|
|
1622
|
+
wslKiroCliEnv && p === wslKiroCliMarker ? wslKiroCliEnv : process.env;
|
|
1623
|
+
if (kiroCliPaths.native || kiroCliPaths.wsl) {
|
|
1624
|
+
if (progress?.enabled) {
|
|
1625
|
+
progress.start(`Parsing Kiro CLI ${renderBar(0)} | buckets 0`);
|
|
1626
|
+
}
|
|
1627
|
+
try {
|
|
1628
|
+
kiroCliResult = await multiInstallParse({
|
|
1629
|
+
paths: kiroCliPaths,
|
|
1630
|
+
parserFn: parseKiroCliIncremental,
|
|
1631
|
+
providerName: "kiroCli",
|
|
1632
|
+
cursors,
|
|
1633
|
+
// Per-install env MUST come from getParams: multiInstallParse
|
|
1634
|
+
// spreads shared params over it, so a top-level env would clobber
|
|
1635
|
+
// the per-install one.
|
|
1636
|
+
getParams: (p) => ({ env: kiroCliEnvFor(p) }),
|
|
1637
|
+
queuePath,
|
|
1638
|
+
onProgress: (p) => {
|
|
1639
|
+
if (!progress?.enabled) return;
|
|
1640
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
1641
|
+
progress.update(
|
|
1642
|
+
`Parsing Kiro CLI ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} sessions | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
1643
|
+
);
|
|
1644
|
+
},
|
|
1645
|
+
detectInstall: (p, flatState) =>
|
|
1646
|
+
kiroCliInstallOwnsCursor(kiroCliEnvFor(p).KIRO_CLI_DB_PATH || kiroCliDb, flatState),
|
|
1647
|
+
});
|
|
1648
|
+
} catch (err) {
|
|
1649
|
+
if (!opts.auto) {
|
|
1650
|
+
process.stderr.write(`Kiro CLI sync: ${err.message}\n`);
|
|
1651
|
+
}
|
|
1479
1652
|
}
|
|
1480
1653
|
}
|
|
1481
1654
|
}
|
|
@@ -2117,6 +2290,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
2117
2290
|
antigravityResult.filesProcessed +
|
|
2118
2291
|
opencodeResult.filesProcessed +
|
|
2119
2292
|
qoderResult.recordsProcessed +
|
|
2293
|
+
claudeScienceResult.recordsProcessed +
|
|
2120
2294
|
cursorResult.recordsProcessed +
|
|
2121
2295
|
kiroResult.recordsProcessed +
|
|
2122
2296
|
kiroCliResult.recordsProcessed +
|
|
@@ -2147,6 +2321,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
2147
2321
|
antigravityResult.bucketsQueued +
|
|
2148
2322
|
opencodeResult.bucketsQueued +
|
|
2149
2323
|
qoderResult.bucketsQueued +
|
|
2324
|
+
claudeScienceResult.bucketsQueued +
|
|
2150
2325
|
cursorResult.bucketsQueued +
|
|
2151
2326
|
kiroResult.bucketsQueued +
|
|
2152
2327
|
kiroCliResult.bucketsQueued +
|
|
@@ -2488,15 +2663,18 @@ function normalizeString(value) {
|
|
|
2488
2663
|
return trimmed.length > 0 ? trimmed : null;
|
|
2489
2664
|
}
|
|
2490
2665
|
|
|
2491
|
-
function resolveOpenclawSignal({
|
|
2666
|
+
function resolveOpenclawSignal({ env } = {}) {
|
|
2492
2667
|
if (!env) return null;
|
|
2493
2668
|
|
|
2494
2669
|
const agentId = normalizeString(env.TOKENTRACKER_OPENCLAW_AGENT_ID);
|
|
2495
2670
|
const sessionId = normalizeString(env.TOKENTRACKER_OPENCLAW_PREV_SESSION_ID);
|
|
2496
2671
|
if (!agentId || !sessionId) return null;
|
|
2497
2672
|
|
|
2498
|
-
|
|
2499
|
-
|
|
2673
|
+
// Resolve the OpenClaw home identically to the passive scanner so the
|
|
2674
|
+
// plugin-triggered file and passively discovered files share one path
|
|
2675
|
+
// spelling — otherwise the same transcript could get two cursors and be
|
|
2676
|
+
// counted twice (issue #264 review).
|
|
2677
|
+
const openclawHome = resolveOpenclawHome(env);
|
|
2500
2678
|
const sessionFile = path.join(openclawHome, "agents", agentId, "sessions", `${sessionId}.jsonl`);
|
|
2501
2679
|
|
|
2502
2680
|
const prevTotals = {
|
|
@@ -2529,6 +2707,16 @@ async function applyOpenclawTotalsFallback({
|
|
|
2529
2707
|
return { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
2530
2708
|
}
|
|
2531
2709
|
|
|
2710
|
+
// The passive/plugin transcript parse is authoritative: if this session's
|
|
2711
|
+
// transcript has ever yielded real per-event usage, synthesizing sessions.json
|
|
2712
|
+
// totals on top would count the same tokens twice (issue #264 review). Defer
|
|
2713
|
+
// to the real events and advance the fallback baseline so no stale delta lingers.
|
|
2714
|
+
const transcriptCursor =
|
|
2715
|
+
signal.sessionFile && cursors?.files
|
|
2716
|
+
? cursors.files[openclawCursorKey(signal.sessionFile)]
|
|
2717
|
+
: null;
|
|
2718
|
+
const transcriptHasRealUsage = Boolean(transcriptCursor?.hasRealUsage);
|
|
2719
|
+
|
|
2532
2720
|
const sessionKey = `${signal.agentId}:${signal.sessionId}`;
|
|
2533
2721
|
const statePath = path.join(trackerDir, "openclaw.fallback.state.json");
|
|
2534
2722
|
const fallbackFilePath = path.join(trackerDir, "openclaw.fallback.jsonl");
|
|
@@ -2537,6 +2725,22 @@ async function applyOpenclawTotalsFallback({
|
|
|
2537
2725
|
const prev =
|
|
2538
2726
|
sessions[sessionKey] && typeof sessions[sessionKey] === "object" ? sessions[sessionKey] : null;
|
|
2539
2727
|
|
|
2728
|
+
if (transcriptHasRealUsage) {
|
|
2729
|
+
sessions[sessionKey] = {
|
|
2730
|
+
totalTokens: normalizeNonNegativeInt(signal?.prevTotals?.totalTokens) || 0,
|
|
2731
|
+
inputTokens: normalizeNonNegativeInt(signal?.prevTotals?.inputTokens) || 0,
|
|
2732
|
+
outputTokens: normalizeNonNegativeInt(signal?.prevTotals?.outputTokens) || 0,
|
|
2733
|
+
model: normalizeString(signal?.prevTotals?.model) || "unknown",
|
|
2734
|
+
updatedAt: normalizeIsoOrEpoch(signal?.prevTotals?.updatedAt) || new Date().toISOString(),
|
|
2735
|
+
seenAt: new Date().toISOString(),
|
|
2736
|
+
coveredByEvents: true,
|
|
2737
|
+
};
|
|
2738
|
+
state.version = 1;
|
|
2739
|
+
state.sessions = sessions;
|
|
2740
|
+
await writeJson(statePath, state);
|
|
2741
|
+
return { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2540
2744
|
const current = {
|
|
2541
2745
|
totalTokens: normalizeNonNegativeInt(signal?.prevTotals?.totalTokens) || 0,
|
|
2542
2746
|
inputTokens: normalizeNonNegativeInt(signal?.prevTotals?.inputTokens) || 0,
|
|
@@ -4688,6 +4892,7 @@ async function repairClaudeQueueFromGroundTruth({
|
|
|
4688
4892
|
queueStatePath = null,
|
|
4689
4893
|
projectQueuePath = null,
|
|
4690
4894
|
projectQueueStatePath = null,
|
|
4895
|
+
rootDirs = null,
|
|
4691
4896
|
}) {
|
|
4692
4897
|
if (!cursors || typeof cursors !== "object") return false;
|
|
4693
4898
|
const migrations = (cursors.migrations ||= {});
|
|
@@ -4695,7 +4900,13 @@ async function repairClaudeQueueFromGroundTruth({
|
|
|
4695
4900
|
|
|
4696
4901
|
let result;
|
|
4697
4902
|
try {
|
|
4698
|
-
|
|
4903
|
+
// rootDirs must cover every install the incremental parser scans (native
|
|
4904
|
+
// AND WSL): this repair's semantics are "disk is truth" — it clears all
|
|
4905
|
+
// claude buckets and replaces claudeHashes wholesale, so scanning fewer
|
|
4906
|
+
// roots than sync would silently drop the missing install's history.
|
|
4907
|
+
result = await computeClaudeGroundTruthBuckets(
|
|
4908
|
+
Array.isArray(rootDirs) && rootDirs.length > 0 ? { rootDirs } : {},
|
|
4909
|
+
);
|
|
4699
4910
|
} catch (e) {
|
|
4700
4911
|
console.error("[sync] claude ground-truth repair: scan failed:", e?.message || e);
|
|
4701
4912
|
return false;
|