tokentracker-cli 0.67.0 → 0.68.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 +2 -0
- package/README.ko.md +2 -0
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/dashboard/dist/assets/{ActivityHeatmap-BLL6xjLG.js → ActivityHeatmap-stlH952K.js} +1 -1
- package/dashboard/dist/assets/{Card-BB6LiYM8.js → Card-CofgedFz.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-CX5gv4tk.js → ConfirmModal-n09o_RU7.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-DMvTS8wX.js → DashboardPage-iBPCT4vZ.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-vyJEBiMw.js → DevicePage-BGkZILd3.js} +1 -1
- package/dashboard/dist/assets/{DialogTitle-R2g9r9PR.js → DialogTitle-BbxEzA03.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-CQUbHzNN.js → FadeIn-E5ehYYk1.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-FwJX-pXB.js → HeaderGithubStar-CndrILKO.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-D_-1u2LS.js → IpCheckPage-5E_xMiUW.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-C-lWMwXP.js → LandingPage-CgTigoW1.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-DskcTB9C.js → LeaderboardAvatar-Dvo78IQE.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-BjS-vLBW.js → LeaderboardPage-C4ksvNko.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-C6k4VVFS.js → LeaderboardProfileModal-UHYujOQG.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-o4fbDnzy.js → LeaderboardProfilePage-CNXemSOF.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-qv3ovWi8.js → LimitsPage-DHAbTe14.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-C1qyMDpn.js → LocalOnlyNotice-DzocMcPr.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-BKNFuYEQ.js → LoginPage-CVFvbmek.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-leUqNKuk.js → PopoverPopup-CH29Dl4m.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-D5yP5dZa.js → ResetPasswordPage-Oj3VwcUT.js} +1 -1
- package/dashboard/dist/assets/{Select-Bq8XjKo9.js → Select-COJepoOt.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-BUE4AAIY.js → SelectItemText-B1dgrCIE.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-DXTRXh8r.js → SettingsPage-CG0Eiwb0.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-Cz17a9ep.js → SkillsPage-DAht7mz-.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-CSYCzAMh.js → WidgetsPage-CCunqqWC.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-BM2BvUUN.js → WrappedPage-taTQKVSr.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-DfysM5Ft.js → agent-logos-CQnbfUYM.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-KJ5nNRaR.js → arrow-up-right-gF_F8D6S.js} +1 -1
- package/dashboard/dist/assets/{download-DPMzNjm9.js → download-DD5R30tG.js} +1 -1
- package/dashboard/dist/assets/{main-V7ji8RkP.js → main-BS4wXJQk.js} +2 -2
- package/dashboard/dist/assets/{use-limits-display-prefs-BhRZ2Vbm.js → use-limits-display-prefs-CWkXT8WC.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-CUwxJneK.js → use-native-settings-CkkMdU8D.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-Cg8uqKy8.js → use-ordered-list-DnvBYQP_.js} +1 -1
- package/dashboard/dist/assets/{use-quality-per-dollar-pref-DFYl9bWd.js → use-quality-per-dollar-pref-Cunr2tA3.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-TD09WmDE.js → use-usage-limits-DO7Asd2u.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-mDQb_32d.js → useCurrency-Bz7Vj74A.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-CP5GKZtC.js → useScrollLock-BnSHhdwy.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/serve.js +32 -1
- package/src/commands/status.js +2 -2
- package/src/commands/sync.js +151 -70
- package/src/lib/browser-auth.js +120 -20
- package/src/lib/install-resolver.js +62 -0
- package/src/lib/multi-install-parser.js +103 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +104 -40
- package/src/lib/wsl-probe.js +21 -0
package/src/commands/sync.js
CHANGED
|
@@ -5,6 +5,9 @@ const fssync = require("node:fs");
|
|
|
5
5
|
const cp = require("node:child_process");
|
|
6
6
|
const readline = require("node:readline");
|
|
7
7
|
|
|
8
|
+
const { resolveInstallPaths, ensureFlatCursor } = require("../lib/install-resolver");
|
|
9
|
+
const { multiInstallParse, mergeBothFileSources } = require("../lib/multi-install-parser");
|
|
10
|
+
const wsl = require("../lib/wsl-probe");
|
|
8
11
|
const { ensureDir, readJson, writeJson, openLock } = require("../lib/fs");
|
|
9
12
|
const {
|
|
10
13
|
listRolloutFiles,
|
|
@@ -30,6 +33,9 @@ const {
|
|
|
30
33
|
parseCursorApiIncremental,
|
|
31
34
|
parseKiroIncremental,
|
|
32
35
|
parseHermesIncremental,
|
|
36
|
+
gooseInstallOwnsCursor,
|
|
37
|
+
zedInstallOwnsCursor,
|
|
38
|
+
hermesInstallOwnsCursor,
|
|
33
39
|
parseCopilotIncremental,
|
|
34
40
|
resolveKimiWireFiles,
|
|
35
41
|
parseKimiIncremental,
|
|
@@ -697,7 +703,9 @@ async function cmdSync(argv) {
|
|
|
697
703
|
}
|
|
698
704
|
|
|
699
705
|
// ── Kilo Code VS Code extension (Cline-style ui_messages.json) ──
|
|
700
|
-
const kilocodeTaskFiles = sourceAllowed("kilocode")
|
|
706
|
+
const kilocodeTaskFiles = sourceAllowed("kilocode")
|
|
707
|
+
? mergeBothFileSources({ resolveFiles: resolveKilocodeTaskFiles, env: process.env })
|
|
708
|
+
: [];
|
|
701
709
|
let kilocodeResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
702
710
|
if (kilocodeTaskFiles.length > 0) {
|
|
703
711
|
if (progress?.enabled) {
|
|
@@ -728,32 +736,40 @@ async function cmdSync(argv) {
|
|
|
728
736
|
// ── Goose (Block) — SQLite sessions with cumulative tokens per session ──
|
|
729
737
|
const gooseDbPath = resolveGooseDbPath(process.env);
|
|
730
738
|
let gooseResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
731
|
-
if (sourceAllowed("goose")
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
);
|
|
748
|
-
},
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
739
|
+
if (sourceAllowed("goose")) {
|
|
740
|
+
const gooseMode = wsl.getWslMode(process.env);
|
|
741
|
+
if (gooseMode === "both" && process.platform === "win32") {
|
|
742
|
+
const home = os.homedir();
|
|
743
|
+
const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
744
|
+
const nativeDb = path.join(appData, "goose", "sessions", "sessions.db");
|
|
745
|
+
const wslDir = wsl.shouldProbeWsl(process.env) ? wsl.discoverWslHome(".local/share/goose/sessions") : null;
|
|
746
|
+
const wslDb = wslDir ? path.join(wslDir, "sessions.db") : null;
|
|
747
|
+
const goosePaths = resolveInstallPaths({ nativeValue: nativeDb, wslValue: wslDb });
|
|
748
|
+
if (goosePaths.native || goosePaths.wsl) {
|
|
749
|
+
if (progress?.enabled) progress.start(`Parsing Goose ${renderBar(0)} 0 sessions | buckets 0`);
|
|
750
|
+
try {
|
|
751
|
+
gooseResult = await multiInstallParse({
|
|
752
|
+
paths: goosePaths, parserFn: parseGooseIncremental, providerName: "goose",
|
|
753
|
+
cursors, getParams: (p) => ({ dbPath: p }), queuePath, onProgress: gooseOnProgress,
|
|
754
|
+
detectInstall: gooseInstallOwnsCursor,
|
|
755
|
+
});
|
|
756
|
+
} catch (err) { warnProviderParseFailure("Goose", err, opts); }
|
|
757
|
+
}
|
|
758
|
+
} else if (gooseDbPath && fssync.existsSync(gooseDbPath)) {
|
|
759
|
+
if (progress?.enabled) progress.start(`Parsing Goose ${renderBar(0)} 0 sessions | buckets 0`);
|
|
760
|
+
ensureFlatCursor(cursors, "goose", process.env);
|
|
761
|
+
try {
|
|
762
|
+
gooseResult = await parseGooseIncremental({
|
|
763
|
+
dbPath: gooseDbPath, cursors, queuePath, onProgress: gooseOnProgress,
|
|
764
|
+
});
|
|
765
|
+
} catch (err) { warnProviderParseFailure("Goose", err, opts); }
|
|
752
766
|
}
|
|
753
767
|
}
|
|
754
768
|
|
|
755
769
|
// ── Droid (Factory CLI) — passive reader for ~/.factory/sessions/*.settings.json ──
|
|
756
|
-
const droidSettingsFiles = sourceAllowed("droid")
|
|
770
|
+
const droidSettingsFiles = sourceAllowed("droid")
|
|
771
|
+
? mergeBothFileSources({ resolveFiles: listDroidSettingsFiles, env: process.env })
|
|
772
|
+
: [];
|
|
757
773
|
let droidResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
758
774
|
if (droidSettingsFiles.length > 0) {
|
|
759
775
|
if (progress?.enabled) {
|
|
@@ -788,27 +804,33 @@ async function cmdSync(argv) {
|
|
|
788
804
|
// ── Zed Agent (all providers; cumulative-delta over SQLite threads) ──
|
|
789
805
|
const zedDbPath = resolveZedDbPath(process.env);
|
|
790
806
|
let zedResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
791
|
-
if (sourceAllowed("zed")
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
);
|
|
808
|
-
},
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
|
|
807
|
+
if (sourceAllowed("zed")) {
|
|
808
|
+
const zedMode = wsl.getWslMode(process.env);
|
|
809
|
+
if (zedMode === "both" && process.platform === "win32") {
|
|
810
|
+
const home = os.homedir();
|
|
811
|
+
const local = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
|
|
812
|
+
const nativeDb = path.join(local, "Zed", "threads", "threads.db");
|
|
813
|
+
const wslThreadsDir = wsl.shouldProbeWsl(process.env) ? wsl.discoverWslHome(".local/share/zed/threads") : null;
|
|
814
|
+
const wslDb = wslThreadsDir ? path.join(wslThreadsDir, "threads.db") : null;
|
|
815
|
+
const zedPaths = resolveInstallPaths({ nativeValue: nativeDb, wslValue: wslDb });
|
|
816
|
+
if (zedPaths.native || zedPaths.wsl) {
|
|
817
|
+
if (progress?.enabled) progress.start(`Parsing Zed Agent ${renderBar(0)} 0 threads | buckets 0`);
|
|
818
|
+
try {
|
|
819
|
+
zedResult = await multiInstallParse({
|
|
820
|
+
paths: zedPaths, parserFn: parseZedIncremental, providerName: "zed",
|
|
821
|
+
cursors, getParams: (p) => ({ dbPath: p }), queuePath, onProgress: zedOnProgress,
|
|
822
|
+
detectInstall: zedInstallOwnsCursor,
|
|
823
|
+
});
|
|
824
|
+
} catch (err) { warnProviderParseFailure("Zed Agent", err, opts); }
|
|
825
|
+
}
|
|
826
|
+
} else if (zedDbPath && fssync.existsSync(zedDbPath)) {
|
|
827
|
+
if (progress?.enabled) progress.start(`Parsing Zed Agent ${renderBar(0)} 0 threads | buckets 0`);
|
|
828
|
+
ensureFlatCursor(cursors, "zed", process.env);
|
|
829
|
+
try {
|
|
830
|
+
zedResult = await parseZedIncremental({
|
|
831
|
+
dbPath: zedDbPath, cursors, queuePath, onProgress: zedOnProgress,
|
|
832
|
+
});
|
|
833
|
+
} catch (err) { warnProviderParseFailure("Zed Agent", err, opts); }
|
|
812
834
|
}
|
|
813
835
|
}
|
|
814
836
|
|
|
@@ -920,29 +942,78 @@ async function cmdSync(argv) {
|
|
|
920
942
|
|
|
921
943
|
// ── Hermes Agent (SQLite-based) ──
|
|
922
944
|
let hermesResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
cursors,
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
);
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
945
|
+
if (sourceAllowed("hermes")) {
|
|
946
|
+
const override = process.env.TOKENTRACKER_HERMES_HOME;
|
|
947
|
+
const overridePath = typeof override === "string" && override.trim().length > 0 ? override.trim() : null;
|
|
948
|
+
if (overridePath) {
|
|
949
|
+
if (fssync.existsSync(overridePath)) {
|
|
950
|
+
if (progress?.enabled) {
|
|
951
|
+
progress.start(`Parsing Hermes ${renderBar(0)} | buckets 0`);
|
|
952
|
+
}
|
|
953
|
+
ensureFlatCursor(cursors, "hermes", process.env);
|
|
954
|
+
try {
|
|
955
|
+
hermesResult = await parseHermesIncremental({
|
|
956
|
+
hermesPath: overridePath,
|
|
957
|
+
cursors,
|
|
958
|
+
queuePath,
|
|
959
|
+
onProgress: hermesOnProgress,
|
|
960
|
+
});
|
|
961
|
+
} catch (err) {
|
|
962
|
+
warnProviderParseFailure("Hermes", err, opts);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
} else {
|
|
966
|
+
const home = os.homedir();
|
|
967
|
+
const defaultPath = path.join(home, ".hermes");
|
|
968
|
+
const nativeValue = process.platform === "win32" && typeof process.env.LOCALAPPDATA === "string"
|
|
969
|
+
? path.join(process.env.LOCALAPPDATA.trim(), "hermes") : defaultPath;
|
|
970
|
+
const hermesPaths = resolveInstallPaths({ nativeValue, wslDir: ".hermes" });
|
|
971
|
+
if (hermesPaths.native || hermesPaths.wsl) {
|
|
972
|
+
if (progress?.enabled) {
|
|
973
|
+
progress.start(`Parsing Hermes ${renderBar(0)} | buckets 0`);
|
|
974
|
+
}
|
|
975
|
+
try {
|
|
976
|
+
hermesResult = await multiInstallParse({
|
|
977
|
+
paths: hermesPaths,
|
|
978
|
+
parserFn: parseHermesIncremental,
|
|
979
|
+
providerName: "hermes",
|
|
980
|
+
cursors,
|
|
981
|
+
getParams: (path) => ({ hermesPath: path }),
|
|
982
|
+
queuePath,
|
|
983
|
+
onProgress: hermesOnProgress,
|
|
984
|
+
detectInstall: hermesInstallOwnsCursor,
|
|
985
|
+
});
|
|
986
|
+
} catch (err) {
|
|
987
|
+
warnProviderParseFailure("Hermes", err, opts);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
943
990
|
}
|
|
944
991
|
}
|
|
945
992
|
|
|
993
|
+
function hermesOnProgress(p) {
|
|
994
|
+
if (!progress?.enabled) return;
|
|
995
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
996
|
+
progress.update(
|
|
997
|
+
`Parsing Hermes ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} sessions | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function gooseOnProgress(p) {
|
|
1002
|
+
if (!progress?.enabled) return;
|
|
1003
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
1004
|
+
progress.update(
|
|
1005
|
+
`Parsing Goose ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} sessions | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function zedOnProgress(p) {
|
|
1010
|
+
if (!progress?.enabled) return;
|
|
1011
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
1012
|
+
progress.update(
|
|
1013
|
+
`Parsing Zed Agent ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} threads | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
946
1017
|
// ── Kiro CLI (reads ~/Library/Application Support/kiro-cli/data.sqlite3
|
|
947
1018
|
// AND live sessions under ~/.kiro/sessions/cli/{uuid}.json) ──
|
|
948
1019
|
// Runs IN PARALLEL with the Kiro IDE branch above — NOT instead of it.
|
|
@@ -1005,7 +1076,9 @@ async function cmdSync(argv) {
|
|
|
1005
1076
|
|
|
1006
1077
|
// ── Kimi Code official (@moonshot-ai/kimi-code, ~/.kimi-code) ──
|
|
1007
1078
|
let kimiCodeResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1008
|
-
const kimiCodeWireFiles = sourceAllowed("kimi-code")
|
|
1079
|
+
const kimiCodeWireFiles = sourceAllowed("kimi-code")
|
|
1080
|
+
? mergeBothFileSources({ resolveFiles: resolveKimiCodeWireFiles, env: process.env })
|
|
1081
|
+
: [];
|
|
1009
1082
|
if (kimiCodeWireFiles.length > 0) {
|
|
1010
1083
|
if (progress?.enabled) {
|
|
1011
1084
|
progress.start(`Parsing Kimi Code (official) ${renderBar(0)} | buckets 0`);
|
|
@@ -1033,7 +1106,9 @@ async function cmdSync(argv) {
|
|
|
1033
1106
|
// Tencent's CodeBuddy CLI is a Claude Code clone; no hook system, so we
|
|
1034
1107
|
// tail the per-session JSONL conversation logs incrementally on each sync.
|
|
1035
1108
|
let codebuddyResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1036
|
-
const codebuddyFiles = sourceAllowed("codebuddy")
|
|
1109
|
+
const codebuddyFiles = sourceAllowed("codebuddy")
|
|
1110
|
+
? mergeBothFileSources({ resolveFiles: resolveCodebuddyProjectFiles, env: process.env })
|
|
1111
|
+
: [];
|
|
1037
1112
|
if (codebuddyFiles.length > 0) {
|
|
1038
1113
|
if (progress?.enabled) {
|
|
1039
1114
|
progress.start(`Parsing CodeBuddy ${renderBar(0)} | buckets 0`);
|
|
@@ -1063,7 +1138,9 @@ async function cmdSync(argv) {
|
|
|
1063
1138
|
// sub-agent logs nest one level deeper, so the resolver recurses. See the
|
|
1064
1139
|
// parser comment in rollout.js for the cache-aware token math.
|
|
1065
1140
|
let workbuddyResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1066
|
-
const workbuddyFiles = sourceAllowed("workbuddy")
|
|
1141
|
+
const workbuddyFiles = sourceAllowed("workbuddy")
|
|
1142
|
+
? mergeBothFileSources({ resolveFiles: resolveWorkbuddyProjectFiles, env: process.env })
|
|
1143
|
+
: [];
|
|
1067
1144
|
if (workbuddyFiles.length > 0) {
|
|
1068
1145
|
if (progress?.enabled) {
|
|
1069
1146
|
progress.start(`Parsing WorkBuddy ${renderBar(0)} | buckets 0`);
|
|
@@ -1089,7 +1166,9 @@ async function cmdSync(argv) {
|
|
|
1089
1166
|
|
|
1090
1167
|
// ── oh-my-pi (passive ~/.omp/agent/sessions/**/*.jsonl reader) ──
|
|
1091
1168
|
let ompResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1092
|
-
const ompFiles = sourceAllowed("omp")
|
|
1169
|
+
const ompFiles = sourceAllowed("omp")
|
|
1170
|
+
? mergeBothFileSources({ resolveFiles: resolveOmpSessionFiles, env: process.env })
|
|
1171
|
+
: [];
|
|
1093
1172
|
if (ompFiles.length > 0) {
|
|
1094
1173
|
if (progress?.enabled) {
|
|
1095
1174
|
progress.start(`Parsing oh-my-pi ${renderBar(0)} | buckets 0`);
|
|
@@ -1120,7 +1199,7 @@ async function cmdSync(argv) {
|
|
|
1120
1199
|
let piResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1121
1200
|
const piFiles = !sourceAllowed("pi") || piAgentDirCollidesWithOmp(process.env)
|
|
1122
1201
|
? []
|
|
1123
|
-
: resolvePiSessionFiles
|
|
1202
|
+
: mergeBothFileSources({ resolveFiles: resolvePiSessionFiles, env: process.env });
|
|
1124
1203
|
if (piFiles.length > 0) {
|
|
1125
1204
|
if (progress?.enabled) {
|
|
1126
1205
|
progress.start(`Parsing pi ${renderBar(0)} | buckets 0`);
|
|
@@ -1146,7 +1225,9 @@ async function cmdSync(argv) {
|
|
|
1146
1225
|
|
|
1147
1226
|
// ── Craft Agents (passive ~/.craft-agent + workspaces session.jsonl reader) ──
|
|
1148
1227
|
let craftResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1149
|
-
const craftFiles = sourceAllowed("craft")
|
|
1228
|
+
const craftFiles = sourceAllowed("craft")
|
|
1229
|
+
? mergeBothFileSources({ resolveFiles: resolveCraftSessionFiles, env: process.env })
|
|
1230
|
+
: [];
|
|
1150
1231
|
if (craftFiles.length > 0) {
|
|
1151
1232
|
if (progress?.enabled) {
|
|
1152
1233
|
progress.start(`Parsing Craft ${renderBar(0)} | buckets 0`);
|
package/src/lib/browser-auth.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const http = require("node:http");
|
|
2
2
|
const crypto = require("node:crypto");
|
|
3
3
|
const cp = require("node:child_process");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
4
6
|
|
|
5
7
|
const { DEFAULT_BASE_URL } = require("./runtime-config");
|
|
6
8
|
|
|
@@ -158,8 +160,103 @@ function buildBrowserList() {
|
|
|
158
160
|
return [def, ...all.filter((b) => b !== def)];
|
|
159
161
|
}
|
|
160
162
|
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
+
function isWslSession(env = process.env) {
|
|
164
|
+
return Boolean(
|
|
165
|
+
(typeof env.WSL_DISTRO_NAME === "string" && env.WSL_DISTRO_NAME.trim()) ||
|
|
166
|
+
(typeof env.WSL_INTEROP === "string" && env.WSL_INTEROP.trim()),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function hasGraphicalSession(env = process.env) {
|
|
171
|
+
return Boolean(
|
|
172
|
+
(typeof env.DISPLAY === "string" && env.DISPLAY.trim()) ||
|
|
173
|
+
(typeof env.WAYLAND_DISPLAY === "string" && env.WAYLAND_DISPLAY.trim()),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isCommandAvailable(command, { env = process.env, platform = process.platform } = {}) {
|
|
178
|
+
if (typeof command !== "string" || !command.trim()) return false;
|
|
179
|
+
|
|
180
|
+
const pathEntries = String(env.PATH || "")
|
|
181
|
+
.split(path.delimiter)
|
|
182
|
+
.map((entry) => entry.trim())
|
|
183
|
+
.filter(Boolean);
|
|
184
|
+
const hasPathSeparator = command.includes("/") || command.includes("\\");
|
|
185
|
+
const suffixes = platform === "win32"
|
|
186
|
+
? String(env.PATHEXT || ".EXE;.CMD;.BAT;.COM")
|
|
187
|
+
.split(";")
|
|
188
|
+
.map((ext) => ext.trim())
|
|
189
|
+
.filter(Boolean)
|
|
190
|
+
: [""];
|
|
191
|
+
|
|
192
|
+
const candidates = [];
|
|
193
|
+
if (hasPathSeparator) {
|
|
194
|
+
candidates.push(command);
|
|
195
|
+
} else {
|
|
196
|
+
for (const dir of pathEntries) {
|
|
197
|
+
if (platform === "win32") {
|
|
198
|
+
candidates.push(path.join(dir, command));
|
|
199
|
+
for (const ext of suffixes) {
|
|
200
|
+
candidates.push(path.join(dir, `${command}${ext}`));
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
candidates.push(path.join(dir, command));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
for (const candidate of candidates) {
|
|
209
|
+
try {
|
|
210
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
211
|
+
return true;
|
|
212
|
+
} catch (_e) {}
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function resolveBrowserLaunchCommand(
|
|
218
|
+
url,
|
|
219
|
+
{
|
|
220
|
+
platform = process.platform,
|
|
221
|
+
env = process.env,
|
|
222
|
+
commandExists = isCommandAvailable,
|
|
223
|
+
} = {},
|
|
224
|
+
) {
|
|
225
|
+
if (platform === "win32") {
|
|
226
|
+
const escapedUrl = String(url).replace(/'/g, "''");
|
|
227
|
+
return {
|
|
228
|
+
command: "powershell.exe",
|
|
229
|
+
args: [
|
|
230
|
+
"-NoProfile",
|
|
231
|
+
"-NonInteractive",
|
|
232
|
+
"-ExecutionPolicy",
|
|
233
|
+
"Bypass",
|
|
234
|
+
"-Command",
|
|
235
|
+
`Start-Process -FilePath '${escapedUrl}'`,
|
|
236
|
+
],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (isWslSession(env) && commandExists("wslview", { env, platform })) {
|
|
241
|
+
return { command: "wslview", args: [url] };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!hasGraphicalSession(env)) return null;
|
|
245
|
+
|
|
246
|
+
const candidates = [
|
|
247
|
+
{ command: "xdg-open", args: [url] },
|
|
248
|
+
{ command: "gio", args: ["open", url] },
|
|
249
|
+
{ command: "sensible-browser", args: [url] },
|
|
250
|
+
];
|
|
251
|
+
for (const candidate of candidates) {
|
|
252
|
+
if (commandExists(candidate.command, { env, platform })) {
|
|
253
|
+
return candidate;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function openInBrowser(url, { platform = process.platform, env = process.env, spawn = cp.spawn, commandExists = isCommandAvailable } = {}) {
|
|
163
260
|
|
|
164
261
|
if (platform === "darwin") {
|
|
165
262
|
// On macOS, prefer reusing a matching tab in a supported running browser.
|
|
@@ -230,34 +327,33 @@ else
|
|
|
230
327
|
open location "${url}"
|
|
231
328
|
end if
|
|
232
329
|
`;
|
|
233
|
-
|
|
234
|
-
const child = cp.spawn("osascript", ["-e", script], { stdio: "ignore", detached: true });
|
|
235
|
-
child.unref();
|
|
236
|
-
} catch (_e) {
|
|
237
|
-
// Fallback to plain open
|
|
330
|
+
if (commandExists("osascript", { env, platform })) {
|
|
238
331
|
try {
|
|
239
|
-
const child =
|
|
332
|
+
const child = spawn("osascript", ["-e", script], { stdio: "ignore", detached: true });
|
|
240
333
|
child.unref();
|
|
334
|
+
return true;
|
|
335
|
+
} catch (_e) {}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (commandExists("open", { env, platform })) {
|
|
339
|
+
try {
|
|
340
|
+
const child = spawn("open", [url], { stdio: "ignore", detached: true });
|
|
341
|
+
child.unref();
|
|
342
|
+
return true;
|
|
241
343
|
} catch (_e2) {}
|
|
242
344
|
}
|
|
243
|
-
return;
|
|
345
|
+
return false;
|
|
244
346
|
}
|
|
245
347
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (platform === "win32") {
|
|
250
|
-
cmd = "cmd";
|
|
251
|
-
args = ["/c", "start", "", url];
|
|
252
|
-
} else {
|
|
253
|
-
cmd = "xdg-open";
|
|
254
|
-
args = [url];
|
|
255
|
-
}
|
|
348
|
+
const launch = resolveBrowserLaunchCommand(url, { platform, env, commandExists });
|
|
349
|
+
if (!launch) return false;
|
|
256
350
|
|
|
257
351
|
try {
|
|
258
|
-
const child =
|
|
352
|
+
const child = spawn(launch.command, launch.args, { stdio: "ignore", detached: true });
|
|
259
353
|
child.unref();
|
|
354
|
+
return true;
|
|
260
355
|
} catch (_e) {}
|
|
356
|
+
return false;
|
|
261
357
|
}
|
|
262
358
|
|
|
263
359
|
function resolvePostAuthRedirect({ dashboardUrl, authUrl }) {
|
|
@@ -277,5 +373,9 @@ function resolvePostAuthRedirect({ dashboardUrl, authUrl }) {
|
|
|
277
373
|
|
|
278
374
|
module.exports = {
|
|
279
375
|
beginBrowserAuth,
|
|
376
|
+
hasGraphicalSession,
|
|
377
|
+
isCommandAvailable,
|
|
378
|
+
isWslSession,
|
|
280
379
|
openInBrowser,
|
|
380
|
+
resolveBrowserLaunchCommand,
|
|
281
381
|
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const fssync = require("node:fs");
|
|
2
|
+
const wsl = require("./wsl-probe");
|
|
3
|
+
|
|
4
|
+
function resolveInstallPaths({ nativeValue, wslDir, wslValue } = {}, env = process.env, deps = {}) {
|
|
5
|
+
if (process.platform !== "win32") {
|
|
6
|
+
return { native: nativeValue ?? null, wsl: null };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const wslCandidate = wslValue !== undefined
|
|
10
|
+
? (wsl.shouldProbeWsl(env) ? wslValue : null)
|
|
11
|
+
: (wslDir && wsl.shouldProbeWsl(env) ? wsl.discoverWslHome(wslDir, { ...deps, env }) : null);
|
|
12
|
+
const nativeCandidate = wsl.shouldProbeNative(env) && nativeValue
|
|
13
|
+
? pathExists(nativeValue, deps.existsSync) : null;
|
|
14
|
+
|
|
15
|
+
return wsl.resolveAllWin32Paths({ nativeValue: nativeCandidate, wslValue: wslCandidate, env, platform: "win32" });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function pathExists(p, existsSync) {
|
|
19
|
+
try { return (existsSync || fssync.existsSync)(p) ? p : null; } catch (_e) { return null; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Migrate a flat (single-install) cursor to { native, wsl } namespaces.
|
|
23
|
+
// `activeKeys` names the namespaces seeded with a copy of the flat state; the
|
|
24
|
+
// others start empty so their install's full history backfills on first parse.
|
|
25
|
+
// Leaving a namespace empty is only safe when its install was NEVER counted
|
|
26
|
+
// under the flat cursor — the flat state holds the per-session dedup maps, and
|
|
27
|
+
// an already-counted install re-parsed without them double-counts everything.
|
|
28
|
+
// Callers that cannot prove which install the flat cursor tracked must seed
|
|
29
|
+
// ALL namespaces (the default): bounded backfill loss, never a double count.
|
|
30
|
+
function ensureNamespacedCursors(cursors, providerName, activeKeys = ["native", "wsl"]) {
|
|
31
|
+
const state = cursors[providerName] && typeof cursors[providerName] === "object" ? cursors[providerName] : {};
|
|
32
|
+
|
|
33
|
+
if (state.native !== undefined || state.wsl !== undefined) {
|
|
34
|
+
return state;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const keys = Array.isArray(activeKeys) ? activeKeys : [activeKeys];
|
|
38
|
+
cursors[providerName] = { native: {}, wsl: {} };
|
|
39
|
+
if (Object.keys(state).length > 0) {
|
|
40
|
+
for (const key of keys) {
|
|
41
|
+
cursors[providerName][key] = JSON.parse(JSON.stringify(state));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return cursors[providerName];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ensureFlatCursor(cursors, providerName, env) {
|
|
48
|
+
const state = cursors[providerName];
|
|
49
|
+
if (!state || typeof state !== "object") return;
|
|
50
|
+
if (state.native === undefined && state.wsl === undefined) return;
|
|
51
|
+
|
|
52
|
+
const mode = wsl.getWslMode(env || process.env);
|
|
53
|
+
const preferWsl = mode === "wsl-first" || mode === "wsl-only";
|
|
54
|
+
const merged = preferWsl ? { ...state.native, ...state.wsl } : { ...state.wsl, ...state.native };
|
|
55
|
+
cursors[providerName] = merged;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
resolveInstallPaths,
|
|
60
|
+
ensureNamespacedCursors,
|
|
61
|
+
ensureFlatCursor,
|
|
62
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const wsl = require("./wsl-probe");
|
|
2
|
+
const { ensureNamespacedCursors, ensureFlatCursor } = require("./install-resolver");
|
|
3
|
+
|
|
4
|
+
function emptyResult() {
|
|
5
|
+
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function multiInstallParse({ paths, parserFn, providerName, cursors, getParams, onProgress, detectInstall, ...shared }) {
|
|
9
|
+
const installKeys = Object.keys(paths).filter(k => paths[k]);
|
|
10
|
+
if (installKeys.length === 0) return emptyResult();
|
|
11
|
+
const env = shared.env || process.env;
|
|
12
|
+
|
|
13
|
+
if (installKeys.length === 1) {
|
|
14
|
+
ensureFlatCursor(cursors, providerName, env);
|
|
15
|
+
return await parserFn({
|
|
16
|
+
...getParams(paths[installKeys[0]], installKeys[0]),
|
|
17
|
+
...shared,
|
|
18
|
+
cursors,
|
|
19
|
+
onProgress,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const activeKeys = resolveMigrationSeedKeys({ paths, installKeys, providerName, cursors, detectInstall });
|
|
24
|
+
const ns = ensureNamespacedCursors(cursors, providerName, activeKeys);
|
|
25
|
+
let recordsProcessed = 0;
|
|
26
|
+
let eventsAggregated = 0;
|
|
27
|
+
let bucketsQueued = 0;
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < installKeys.length; i++) {
|
|
30
|
+
const key = installKeys[i];
|
|
31
|
+
cursors[providerName] = ns[key];
|
|
32
|
+
try {
|
|
33
|
+
const result = await parserFn({
|
|
34
|
+
...getParams(paths[key], key), ...shared, cursors,
|
|
35
|
+
onProgress: wrapProgress(onProgress, key),
|
|
36
|
+
});
|
|
37
|
+
ns[key] = cursors[providerName];
|
|
38
|
+
recordsProcessed += result.recordsProcessed || 0;
|
|
39
|
+
eventsAggregated += result.eventsAggregated || 0;
|
|
40
|
+
bucketsQueued += result.bucketsQueued || 0;
|
|
41
|
+
} catch (parseErr) {
|
|
42
|
+
cursors[providerName] = ns;
|
|
43
|
+
throw parseErr;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
cursors[providerName] = ns;
|
|
47
|
+
|
|
48
|
+
return { recordsProcessed, eventsAggregated, bucketsQueued };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Decide which namespaces the one-time flat→namespaced cursor migration seeds
|
|
52
|
+
// with the flat state. `detectInstall(installPath, flatState, installKey)` is
|
|
53
|
+
// a provider-specific probe answering "does this install's DB contain the
|
|
54
|
+
// flat cursor's session ids?". Only when EXACTLY ONE install matches do we
|
|
55
|
+
// know who owned the flat cursor — the other namespace then starts empty so
|
|
56
|
+
// its full history backfills. No probe, no match, multiple matches, or a
|
|
57
|
+
// probe error all fall back to seeding every namespace: an install re-parsed
|
|
58
|
+
// without its dedup state would double-count its entire history, and a
|
|
59
|
+
// bounded backfill gap is the cheaper failure.
|
|
60
|
+
function resolveMigrationSeedKeys({ paths, installKeys, providerName, cursors, detectInstall }) {
|
|
61
|
+
if (typeof detectInstall !== "function") return installKeys;
|
|
62
|
+
const state = cursors[providerName] && typeof cursors[providerName] === "object" ? cursors[providerName] : {};
|
|
63
|
+
const isFlat = state.native === undefined && state.wsl === undefined;
|
|
64
|
+
if (!isFlat || Object.keys(state).length === 0) return installKeys;
|
|
65
|
+
|
|
66
|
+
const hits = [];
|
|
67
|
+
for (const key of installKeys) {
|
|
68
|
+
let owns = false;
|
|
69
|
+
try {
|
|
70
|
+
owns = detectInstall(paths[key], state, key) === true;
|
|
71
|
+
} catch (_e) { }
|
|
72
|
+
if (owns) hits.push(key);
|
|
73
|
+
}
|
|
74
|
+
return hits.length === 1 ? hits : installKeys;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function wrapProgress(onProgress, installKey) {
|
|
78
|
+
if (!onProgress) return undefined;
|
|
79
|
+
return (p) => onProgress({ ...p, install: installKey });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function mergeBothFileSources({ resolveFiles, env }) {
|
|
83
|
+
const isBoth = process.platform === "win32" && wsl.getWslMode(env) === "both";
|
|
84
|
+
if (!isBoth) {
|
|
85
|
+
const files = resolveFiles(env);
|
|
86
|
+
return files;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const nativeEnv = { ...env, TOKENTRACKER_WSL_MODE: "native-only" };
|
|
90
|
+
const wslEnv = { ...env, TOKENTRACKER_WSL_MODE: "wsl-only" };
|
|
91
|
+
|
|
92
|
+
const nativeFiles = resolveFiles(nativeEnv);
|
|
93
|
+
const wslFiles = resolveFiles(wslEnv);
|
|
94
|
+
|
|
95
|
+
const seen = new Set();
|
|
96
|
+
const merged = [];
|
|
97
|
+
for (const f of [...nativeFiles, ...wslFiles]) {
|
|
98
|
+
if (!seen.has(f)) { seen.add(f); merged.push(f); }
|
|
99
|
+
}
|
|
100
|
+
return merged;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { multiInstallParse, emptyResult, mergeBothFileSources };
|