tokentracker-cli 1.3.3 → 1.3.5
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 -5
- package/README.ja.md +3 -3
- package/README.ko.md +3 -3
- package/README.md +6 -5
- package/README.zh-CN.md +3 -3
- package/THIRD_PARTY_NOTICES.md +92 -0
- package/dashboard/dist/THIRD_PARTY_NOTICES.txt +92 -0
- package/dashboard/dist/assets/AchievementBadge-Dazs3pEa.js +1 -0
- package/dashboard/dist/assets/AchievementsPage-LWnxZvmw.js +2 -0
- package/dashboard/dist/assets/{AchievementsSection-CDUUEfIi.js → AchievementsSection-BLqczmU9.js} +1 -1
- package/dashboard/dist/assets/BadgeDetailModal-B_n4aj2S.js +1 -0
- package/dashboard/dist/assets/{BotAnimated-Dx_vvTtC.js → BotAnimated-KDWI_YcZ.js} +1 -1
- package/dashboard/dist/assets/Card-BYoxd4OC.js +1 -0
- package/dashboard/dist/assets/CloudCheckoutPage-BR2_B03P.js +1 -0
- package/dashboard/dist/assets/CloudPage-DL5RkJKK.js +1 -0
- package/dashboard/dist/assets/CloudUsageExport-C_DqZKo1.js +4 -0
- package/dashboard/dist/assets/CommandPalette-DkCaIQI4.js +1 -0
- package/dashboard/dist/assets/CommunityStatsModal-pX9OePi4.js +1 -0
- package/dashboard/dist/assets/{ConfirmModal-BQ-xB1S_.js → ConfirmModal-tkgZwz4u.js} +1 -1
- package/dashboard/dist/assets/Controls-MTUhGWy8.js +1 -0
- package/dashboard/dist/assets/DashboardPage-SOx-A7lH.js +1 -0
- package/dashboard/dist/assets/DateRangePopover-B-BMOm3O.js +1 -0
- package/dashboard/dist/assets/{DevicePage-Dg6XkIaY.js → DevicePage-DSK9lm7v.js} +1 -1
- package/dashboard/dist/assets/DialogClose-uVUNu4rg.js +1 -0
- package/dashboard/dist/assets/DialogDescription-DBqGGWNj.js +1 -0
- package/dashboard/dist/assets/DialogTitle-B-VifiA3.js +1 -0
- package/dashboard/dist/assets/{FadeIn-ChVf45WW.js → FadeIn-B6lrj4uo.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-B740_G7a.js → HeaderGithubStar-Cx5zOhXa.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-BzP5FudR.js → HoverTooltip-DY1ENf69.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BuZH3F14.js → IpCheckPage-D6TEq64d.js} +1 -1
- package/dashboard/dist/assets/LandingPage-OdZUfq--.js +2 -0
- package/dashboard/dist/assets/LeaderboardPage-DCK5xcAt.js +2 -0
- package/dashboard/dist/assets/LeaderboardProBadge-CBXJ1u_m.js +1 -0
- package/dashboard/dist/assets/{LeaderboardProfileModal-OUZvO6u-.js → LeaderboardProfileModal-Ccdzb7QK.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-bHI1oTEK.js → LeaderboardProfilePage-CFXlZ0fL.js} +2 -2
- package/dashboard/dist/assets/LimitsPage-5krjm-g9.js +9 -0
- package/dashboard/dist/assets/{LocalOnlyNotice-BaEq6qN7.js → LocalOnlyNotice-Rk1B8PaZ.js} +1 -1
- package/dashboard/dist/assets/LoginCard-DTTIm8vk.js +1 -0
- package/dashboard/dist/assets/LoginModal-BNcDdjfr.js +1 -0
- package/dashboard/dist/assets/LoginPage-CQgXP7Ee.js +1 -0
- package/dashboard/dist/assets/{PetPage-BPBsUHyL.js → PetPage-Cw_kAPRs.js} +1 -1
- package/dashboard/dist/assets/PopoverPopup-NYiGE8A2.js +1 -0
- package/dashboard/dist/assets/{ProviderIcon-BshO2uER.js → ProviderIcon-Bp7iFJ_m.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-RTFTD8Dr.js → ResetPasswordPage-Cf9kAWDW.js} +1 -1
- package/dashboard/dist/assets/Select-kIcvQFAo.js +1 -0
- package/dashboard/dist/assets/SelectItemText-BpIsAg3b.js +1 -0
- package/dashboard/dist/assets/{SelfHostPage-BlwTt8XM.js → SelfHostPage-Ch2vvSwI.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-BkIcJpYK.js → ServiceStatusPage-hoYBvGbu.js} +1 -1
- package/dashboard/dist/assets/SessionsPage-BzRB6q-Z.js +3 -0
- package/dashboard/dist/assets/SessionsPage-GV2fuNmd.css +1 -0
- package/dashboard/dist/assets/SettingsPage-DRl2uX-i.js +2 -0
- package/dashboard/dist/assets/SkillsPage-CkqThTZL.js +1 -0
- package/dashboard/dist/assets/{TokenGalaxy-DiXUpXKF.js → TokenGalaxy-eLG-UIZY.js} +1 -1
- package/dashboard/dist/assets/TooltipPopup-bnMJyilQ.js +1 -0
- package/dashboard/dist/assets/TrendMonitor-X7-aGzIv.js +19 -0
- package/dashboard/dist/assets/WidgetsPage-CGznIzrW.js +1 -0
- package/dashboard/dist/assets/{WrappedPage-ClEo-RmR.js → WrappedPage-p31-uUoF.js} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-2g_4s3ot.js +45 -0
- package/dashboard/dist/assets/__vite-browser-external-LpFw4zXh.css +1 -0
- package/dashboard/dist/assets/agent-logos-2TVT3fIE.js +1 -0
- package/dashboard/dist/assets/arrow-left-DL0hAp_7.js +1 -0
- package/dashboard/dist/assets/{arrow-right-CwBz5dwb.js → arrow-right-DFCpnV_9.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-jPmRN562.js → arrow-up-right-DLvfE0Wu.js} +1 -1
- package/dashboard/dist/assets/calendar-days-B0aDoKSG.js +1 -0
- package/dashboard/dist/assets/{check-CKwHc4Hv.js → check-C_cfrxiz.js} +1 -1
- package/dashboard/dist/assets/chevron-down-Ck8ml43e.js +1 -0
- package/dashboard/dist/assets/cloud-checkout-PYnwNfdH.js +1 -0
- package/dashboard/dist/assets/composite-INW-ELWX.js +1 -0
- package/dashboard/dist/assets/download-B7uokeIo.js +1 -0
- package/dashboard/dist/assets/{external-link-B5yXxdPy.js → external-link-CI3crg4K.js} +1 -1
- package/dashboard/dist/assets/{flame-CKjAluj_.js → flame-DCzvFKV1.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-mk49THtv.js → format-tokens-CSTEYK2m.js} +1 -1
- package/dashboard/dist/assets/gift-dEHDFMc6.js +1 -0
- package/dashboard/dist/assets/{host-mode-efKlSYjX.js → host-mode-r2SW9Mdv.js} +1 -1
- package/dashboard/dist/assets/{icons-ZB1ZthjH.js → icons-q-3zvEkh.js} +1 -1
- package/dashboard/dist/assets/{index-BImBG3eS.js → index-BV7qwUw4.js} +1 -1
- package/dashboard/dist/assets/{index-BgdM7kBl.js → index-BbrsgHf4.js} +1 -1
- package/dashboard/dist/assets/{index-DweUMAUD.js → index-CyHYj9XI.js} +1 -1
- package/dashboard/dist/assets/info-BBdY9hV4.js +1 -0
- package/dashboard/dist/assets/{link-2-Dn2soiyd.js → link-2-CLPePfXs.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-CNT630zn.js → loader-circle-ETrjzfmn.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-WPx0bc2K.js → modifiers.esm-DHqnp2ol.js} +1 -1
- package/dashboard/dist/assets/module-BNnaKM__.js +3 -0
- package/dashboard/dist/assets/monitor-smartphone-D1kdzrdb.js +1 -0
- package/dashboard/dist/assets/play-Cxp-ronD.js +1 -0
- package/dashboard/dist/assets/plus-4n94VzDK.js +1 -0
- package/dashboard/dist/assets/provider-display-CRlbeO7t.js +42 -0
- package/dashboard/dist/assets/{react-B7SkZTgg.js → react-DlHjGQFN.js} +1 -1
- package/dashboard/dist/assets/safePolygon-CVZ27a2_.js +1 -0
- package/dashboard/dist/assets/{search-Ol3OZBm4.js → search-BDPr56_h.js} +1 -1
- package/dashboard/dist/assets/{server-DFCbznC7.js → server-Bm7fHvVa.js} +1 -1
- package/dashboard/dist/assets/{shield-check-YcmLwVZ6.js → shield-check-DtT8ACzI.js} +1 -1
- package/dashboard/dist/assets/{skills-api-C-9vK884.js → skills-api-Dc1-2qu6.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-CKC9NuR-.js → sortable.esm-B-qN-Xk2.js} +1 -1
- package/dashboard/dist/assets/store-gHeBU8dQ.js +1 -0
- package/dashboard/dist/assets/{terminal-B3_fCLFs.js → terminal-D4VZbvhg.js} +1 -1
- package/dashboard/dist/assets/{trash-CZGfgbFa.js → trash-BZtLAFQy.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-CW-qkYn7.js → use-community-stats-BdqndrbB.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-DHv7vfFE.js → use-latest-request-guard-DhGg1cDS.js} +1 -1
- package/dashboard/dist/assets/use-limits-display-prefs-D3HOcRFv.js +1 -0
- package/dashboard/dist/assets/{use-native-settings-CWiLqfZh.js → use-native-settings-BniKwA_n.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-Df8pw-WJ.js → use-ordered-list-b09H7EHq.js} +1 -1
- package/dashboard/dist/assets/use-pet-settings-CPZdlq_e.js +2 -0
- package/dashboard/dist/assets/use-reduced-motion-D1gcKJUo.js +1 -0
- package/dashboard/dist/assets/{use-transform-BHwEAfmO.js → use-transform-CIdxjmI8.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-eJlU65yY.js +1 -0
- package/dashboard/dist/assets/{useCurrency-pwClnCZ5.js → useCurrency-CM22CmAq.js} +1 -1
- package/dashboard/dist/assets/usePositioner-B3l2ulTw.js +1 -0
- package/dashboard/dist/assets/{useTokenFormat-BMvVAe6V.js → useTokenFormat-D7nzgxKw.js} +1 -1
- package/dashboard/dist/assets/zap-Daxct1WV.js +1 -0
- package/dashboard/dist/index.html +14 -12
- package/dashboard/dist/ip-check.html +2 -2
- package/dashboard/dist/leaderboard.html +2 -2
- package/dashboard/dist/llms.txt +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +3 -2
- package/src/commands/init.js +43 -1
- package/src/commands/status.js +27 -0
- package/src/commands/sync.js +54 -4
- package/src/commands/uninstall.js +19 -0
- package/src/lib/claude-config.js +47 -15
- package/src/lib/codex-rollout-parser.js +11 -3
- package/src/lib/command-runner.js +15 -1
- package/src/lib/cursor-config.js +2 -1
- package/src/lib/gemini-config.js +3 -2
- package/src/lib/local-api.js +13 -3
- package/src/lib/notify-command.js +38 -0
- package/src/lib/openclaw-session-plugin.js +47 -14
- package/src/lib/passive-mode.js +12 -0
- package/src/lib/pricing/curated-overrides.json +42 -5
- package/src/lib/pricing/matcher.js +14 -5
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +150 -28
- package/src/lib/session-analytics.js +108 -9
- package/src/lib/usage-limits.js +183 -20
- package/src/lib/zcode-limits.js +218 -18
- package/src/lib/zcode-reset-credits.js +66 -0
- package/dashboard/dist/assets/AchievementBadge-BaqdyZaV.js +0 -1
- package/dashboard/dist/assets/AchievementsPage-B4Ju0guv.js +0 -2
- package/dashboard/dist/assets/BadgeDetailModal-B-XMJAvL.js +0 -1
- package/dashboard/dist/assets/Card-D-5mABVt.js +0 -1
- package/dashboard/dist/assets/CloudCheckoutPage-BELoh56h.js +0 -1
- package/dashboard/dist/assets/CloudPage-CfBruNxe.js +0 -1
- package/dashboard/dist/assets/CloudUsageExport-BoFU7xCT.js +0 -4
- package/dashboard/dist/assets/CommandPalette-BmmKZWn-.js +0 -1
- package/dashboard/dist/assets/CommunityStatsModal-BF77zVhN.js +0 -1
- package/dashboard/dist/assets/Controls-BnTeX7T4.js +0 -1
- package/dashboard/dist/assets/DashboardPage-BWulHa5J.js +0 -1
- package/dashboard/dist/assets/DialogClose-DAyilsH-.js +0 -1
- package/dashboard/dist/assets/DialogDescription-j4XnRy6y.js +0 -1
- package/dashboard/dist/assets/DialogTitle-B1iNGD2S.js +0 -1
- package/dashboard/dist/assets/LandingPage-Dtgq_caz.js +0 -2
- package/dashboard/dist/assets/LeaderboardPage-Di1kEg2O.js +0 -2
- package/dashboard/dist/assets/LeaderboardProBadge-h8FfR0au.js +0 -1
- package/dashboard/dist/assets/LimitsPage-DKojB73_.js +0 -8
- package/dashboard/dist/assets/LoginCard-Dn5Xy4If.js +0 -1
- package/dashboard/dist/assets/LoginModal-C5h-5GOA.js +0 -1
- package/dashboard/dist/assets/LoginPage-BpX8Tgjj.js +0 -1
- package/dashboard/dist/assets/PopoverPopup-CVTiEVHs.js +0 -1
- package/dashboard/dist/assets/Select-CtTceC4T.js +0 -1
- package/dashboard/dist/assets/SelectItemText-C7xi8iGp.js +0 -1
- package/dashboard/dist/assets/SessionsPage-BngTWN7Q.css +0 -1
- package/dashboard/dist/assets/SessionsPage-okHobk_L.js +0 -3
- package/dashboard/dist/assets/SettingsPage-C-cc6wcF.js +0 -2
- package/dashboard/dist/assets/SkillsPage-D2LlKKJN.js +0 -1
- package/dashboard/dist/assets/TrendMonitor-C88S-5aa.js +0 -19
- package/dashboard/dist/assets/WidgetsPage-WsEFPNwp.js +0 -1
- package/dashboard/dist/assets/__vite-browser-external-DFRX9dp-.css +0 -1
- package/dashboard/dist/assets/__vite-browser-external-DUJeOWgd.js +0 -45
- package/dashboard/dist/assets/agent-logos-BM9jlReI.js +0 -1
- package/dashboard/dist/assets/arrow-left-BEM-HVmC.js +0 -1
- package/dashboard/dist/assets/cloud-checkout-nYNHDFoR.js +0 -1
- package/dashboard/dist/assets/composite-B4Z3F65o.js +0 -1
- package/dashboard/dist/assets/download-CJUnX1Bs.js +0 -1
- package/dashboard/dist/assets/info-CyDY7NYM.js +0 -1
- package/dashboard/dist/assets/module-WSx8viXK.js +0 -3
- package/dashboard/dist/assets/monitor-smartphone-C-qC0RAt.js +0 -1
- package/dashboard/dist/assets/plus-Cx82jb7o.js +0 -1
- package/dashboard/dist/assets/provider-display-CodMmiRb.js +0 -42
- package/dashboard/dist/assets/safePolygon-CKpREa4K.js +0 -1
- package/dashboard/dist/assets/store-C9yWYHfg.js +0 -1
- package/dashboard/dist/assets/use-limits-display-prefs-NZ-AAYD5.js +0 -1
- package/dashboard/dist/assets/use-pet-settings-Bg2CDprr.js +0 -2
- package/dashboard/dist/assets/use-reduced-motion-iDZEUyua.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-CSxFFn3_.js +0 -1
- package/dashboard/dist/assets/usePositioner-B0j1orGj.js +0 -1
- package/dashboard/dist/assets/zap-DvX2aYx5.js +0 -1
package/src/commands/init.js
CHANGED
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
chmod600IfPossible,
|
|
14
14
|
} = require("../lib/fs");
|
|
15
15
|
const { resolveMimoNativeDbPath } = require("../lib/install-resolver");
|
|
16
|
+
const { expandHome } = require("../lib/scan-roots");
|
|
16
17
|
const { prompt, promptHidden } = require("../lib/prompt");
|
|
17
18
|
const {
|
|
18
19
|
upsertCodexNotify,
|
|
@@ -130,6 +131,7 @@ const SUPPORTED_PROVIDERS = [
|
|
|
130
131
|
"Kimi Code",
|
|
131
132
|
"CodeBuddy",
|
|
132
133
|
"WorkBuddy",
|
|
134
|
+
"WorkBuddy AI",
|
|
133
135
|
"Grok Build",
|
|
134
136
|
"oh-my-pi",
|
|
135
137
|
"OmO",
|
|
@@ -578,6 +580,10 @@ async function repairRuntimeIntegrations({
|
|
|
578
580
|
settingsPath: context.workbuddySettingsPath,
|
|
579
581
|
hookCommand: context.workbuddyHookCommand,
|
|
580
582
|
})],
|
|
583
|
+
["workbuddy-ai", context.workbuddyAiDir, () => upsertClaudeHook({
|
|
584
|
+
settingsPath: context.workbuddyAiSettingsPath,
|
|
585
|
+
hookCommand: context.workbuddyAiHookCommand,
|
|
586
|
+
})],
|
|
581
587
|
];
|
|
582
588
|
for (const [key, configDir, repair] of hookRepairs) {
|
|
583
589
|
if (await isDir(configDir)) await attempt(key, repair);
|
|
@@ -622,6 +628,12 @@ function buildIntegrationTargets({ home, trackerDir, notifyPath }) {
|
|
|
622
628
|
const workbuddyDir = process.env.WORKBUDDY_HOME || path.join(home, ".workbuddy");
|
|
623
629
|
const workbuddySettingsPath = path.join(workbuddyDir, "settings.json");
|
|
624
630
|
const workbuddyHookCommand = buildHookCommand(notifyPath, "workbuddy");
|
|
631
|
+
// WorkBuddy AI is the international build of the very same CLI. It keeps its
|
|
632
|
+
// data in a sibling home (~/.workbuddy-ai), so it needs its own hook install
|
|
633
|
+
// and its own --source token to be attributed separately from the CN build.
|
|
634
|
+
const workbuddyAiDir = expandHome(process.env.WORKBUDDY_AI_HOME, home) || path.join(home, ".workbuddy-ai");
|
|
635
|
+
const workbuddyAiSettingsPath = path.join(workbuddyAiDir, "settings.json");
|
|
636
|
+
const workbuddyAiHookCommand = buildHookCommand(notifyPath, "workbuddy-ai");
|
|
625
637
|
const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
|
|
626
638
|
const geminiSettingsPath = resolveGeminiSettingsPath({ configDir: geminiConfigDir });
|
|
627
639
|
const geminiHookCommand = buildGeminiHookCommand(notifyPath);
|
|
@@ -647,6 +659,9 @@ function buildIntegrationTargets({ home, trackerDir, notifyPath }) {
|
|
|
647
659
|
workbuddyDir,
|
|
648
660
|
workbuddySettingsPath,
|
|
649
661
|
workbuddyHookCommand,
|
|
662
|
+
workbuddyAiDir,
|
|
663
|
+
workbuddyAiSettingsPath,
|
|
664
|
+
workbuddyAiHookCommand,
|
|
650
665
|
geminiConfigDir,
|
|
651
666
|
geminiSettingsPath,
|
|
652
667
|
geminiHookCommand,
|
|
@@ -1034,6 +1049,18 @@ async function applyIntegrationSetup({
|
|
|
1034
1049
|
summary.push({ label: "WorkBuddy", status: "skipped", detail: "Config not found" });
|
|
1035
1050
|
}
|
|
1036
1051
|
|
|
1052
|
+
// WorkBuddy AI: international build, same hook schema, sibling home.
|
|
1053
|
+
const workbuddyAiDirExists = await isDir(context.workbuddyAiDir);
|
|
1054
|
+
if (workbuddyAiDirExists) {
|
|
1055
|
+
await upsertClaudeHook({
|
|
1056
|
+
settingsPath: context.workbuddyAiSettingsPath,
|
|
1057
|
+
hookCommand: context.workbuddyAiHookCommand,
|
|
1058
|
+
});
|
|
1059
|
+
summary.push({ label: "WorkBuddy AI", status: "installed", detail: "Hooks installed" });
|
|
1060
|
+
} else {
|
|
1061
|
+
summary.push({ label: "WorkBuddy AI", status: "skipped", detail: "Config not found" });
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1037
1064
|
const openclawBefore = await probeOpenclawSessionPluginState({
|
|
1038
1065
|
home,
|
|
1039
1066
|
trackerDir,
|
|
@@ -1187,6 +1214,21 @@ async function previewIntegrations({ context }) {
|
|
|
1187
1214
|
summary.push({ label: "WorkBuddy", status: "skipped", detail: "Config not found" });
|
|
1188
1215
|
}
|
|
1189
1216
|
|
|
1217
|
+
const workbuddyAiDirExists = await isDir(context.workbuddyAiDir);
|
|
1218
|
+
if (workbuddyAiDirExists) {
|
|
1219
|
+
const configured = await isClaudeHookConfigured({
|
|
1220
|
+
settingsPath: context.workbuddyAiSettingsPath,
|
|
1221
|
+
hookCommand: context.workbuddyAiHookCommand,
|
|
1222
|
+
});
|
|
1223
|
+
summary.push({
|
|
1224
|
+
label: "WorkBuddy AI",
|
|
1225
|
+
status: "installed",
|
|
1226
|
+
detail: configured ? "Hooks already installed" : "Will install hooks",
|
|
1227
|
+
});
|
|
1228
|
+
} else {
|
|
1229
|
+
summary.push({ label: "WorkBuddy AI", status: "skipped", detail: "Config not found" });
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1190
1232
|
const geminiConfigExists = await isDir(context.geminiConfigDir);
|
|
1191
1233
|
if (geminiConfigExists) {
|
|
1192
1234
|
const configured = await isGeminiHookConfigured({
|
|
@@ -1491,7 +1533,7 @@ try {
|
|
|
1491
1533
|
? codeOriginalPath
|
|
1492
1534
|
: source === 'acode'
|
|
1493
1535
|
? acodeOriginalPath
|
|
1494
|
-
: source === 'claude' || source === 'opencode' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy'
|
|
1536
|
+
: source === 'claude' || source === 'opencode' || source === 'gemini' || source === 'codebuddy' || source === 'workbuddy' || source === 'workbuddy-ai'
|
|
1495
1537
|
? null
|
|
1496
1538
|
: codexOriginalPath;
|
|
1497
1539
|
if (originalPath) {
|
package/src/commands/status.js
CHANGED
|
@@ -221,6 +221,11 @@ async function cmdStatus(argv = []) {
|
|
|
221
221
|
resolveWorkbuddyHome(process.env) || path.join(home, ".workbuddy"),
|
|
222
222
|
"settings.json",
|
|
223
223
|
);
|
|
224
|
+
// WorkBuddy AI — international build, sibling home (~/.workbuddy-ai).
|
|
225
|
+
const workbuddyAiSettingsPath = path.join(
|
|
226
|
+
resolveWorkbuddyHome(process.env, "workbuddy-ai") || path.join(home, ".workbuddy-ai"),
|
|
227
|
+
"settings.json",
|
|
228
|
+
);
|
|
224
229
|
const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
|
|
225
230
|
const geminiSettingsPath = resolveGeminiSettingsPath({
|
|
226
231
|
configDir: geminiConfigDir,
|
|
@@ -235,6 +240,7 @@ async function cmdStatus(argv = []) {
|
|
|
235
240
|
const claudeHookCommand = buildClaudeHookCommand(notifyPath);
|
|
236
241
|
const codebuddyHookCommand = buildHookCommand(notifyPath, "codebuddy");
|
|
237
242
|
const workbuddyHookCommand = buildHookCommand(notifyPath, "workbuddy");
|
|
243
|
+
const workbuddyAiHookCommand = buildHookCommand(notifyPath, "workbuddy-ai");
|
|
238
244
|
const geminiHookCommand = buildGeminiHookCommand(notifyPath);
|
|
239
245
|
|
|
240
246
|
const config = await readJson(configPath);
|
|
@@ -308,6 +314,10 @@ async function cmdStatus(argv = []) {
|
|
|
308
314
|
settingsPath: workbuddySettingsPath,
|
|
309
315
|
hookCommand: workbuddyHookCommand,
|
|
310
316
|
});
|
|
317
|
+
const workbuddyAiHookConfigured = await isClaudeHookConfigured({
|
|
318
|
+
settingsPath: workbuddyAiSettingsPath,
|
|
319
|
+
hookCommand: workbuddyAiHookCommand,
|
|
320
|
+
});
|
|
311
321
|
const geminiHookConfigured = await isGeminiHookConfigured({
|
|
312
322
|
settingsPath: geminiSettingsPath,
|
|
313
323
|
hookCommand: geminiHookCommand,
|
|
@@ -493,6 +503,15 @@ async function cmdStatus(argv = []) {
|
|
|
493
503
|
? fssync.existsSync(path.join(workbuddyHome, "workbuddy.db"))
|
|
494
504
|
: false;
|
|
495
505
|
|
|
506
|
+
const workbuddyAiHome = resolveWorkbuddyHome(process.env, "workbuddy-ai");
|
|
507
|
+
const workbuddyAiInstalled = Boolean(workbuddyAiHome && fssync.existsSync(workbuddyAiHome));
|
|
508
|
+
const workbuddyAiFiles = workbuddyAiInstalled
|
|
509
|
+
? resolveWorkbuddyProjectFiles(process.env, "workbuddy-ai")
|
|
510
|
+
: [];
|
|
511
|
+
const workbuddyAiDbExists = workbuddyAiInstalled
|
|
512
|
+
? fssync.existsSync(path.join(workbuddyAiHome, "workbuddy.db"))
|
|
513
|
+
: false;
|
|
514
|
+
|
|
496
515
|
// oh-my-pi — passive scan + optional notify extension.
|
|
497
516
|
const ompAgentDir = resolveOmpAgentDir(process.env);
|
|
498
517
|
const ompInstalled = Boolean(ompAgentDir) && fssync.existsSync(path.join(ompAgentDir, "sessions"));
|
|
@@ -1003,6 +1022,7 @@ async function cmdStatus(argv = []) {
|
|
|
1003
1022
|
openclaw_session_plugin: Boolean(openclawSessionPluginState?.configured),
|
|
1004
1023
|
codebuddy: Boolean(codebuddyHookConfigured),
|
|
1005
1024
|
workbuddy: Boolean(workbuddyHookConfigured),
|
|
1025
|
+
"workbuddy-ai": Boolean(workbuddyAiHookConfigured),
|
|
1006
1026
|
grok: Boolean(grokHookState?.configured),
|
|
1007
1027
|
},
|
|
1008
1028
|
});
|
|
@@ -1046,6 +1066,7 @@ async function cmdStatus(argv = []) {
|
|
|
1046
1066
|
openclaw_legacy: Boolean(openclawHookState?.configured),
|
|
1047
1067
|
codebuddy: codebuddyInstalled ? Boolean(codebuddyHookConfigured) : null,
|
|
1048
1068
|
workbuddy: workbuddyInstalled ? Boolean(workbuddyHookConfigured) : null,
|
|
1069
|
+
"workbuddy-ai": workbuddyAiInstalled ? Boolean(workbuddyAiHookConfigured) : null,
|
|
1049
1070
|
grok: grokInstalled ? Boolean(grokHookState?.configured) : null,
|
|
1050
1071
|
},
|
|
1051
1072
|
providers: {
|
|
@@ -1077,6 +1098,9 @@ async function cmdStatus(argv = []) {
|
|
|
1077
1098
|
workbuddy: workbuddyInstalled
|
|
1078
1099
|
? { installed: true, files: workbuddyFiles.length }
|
|
1079
1100
|
: { installed: false },
|
|
1101
|
+
workbuddy_ai: workbuddyAiInstalled
|
|
1102
|
+
? { installed: true, files: workbuddyAiFiles.length }
|
|
1103
|
+
: { installed: false },
|
|
1080
1104
|
omp: ompInstalled || ompHookState.ompPresent
|
|
1081
1105
|
? {
|
|
1082
1106
|
installed: true,
|
|
@@ -1275,6 +1299,9 @@ async function cmdStatus(argv = []) {
|
|
|
1275
1299
|
workbuddyInstalled
|
|
1276
1300
|
? `- WorkBuddy hooks: ${workbuddyHookConfigured ? "set" : "unset"} (${workbuddyFiles.length} session jsonl file${workbuddyFiles.length !== 1 ? "s" : ""} found, SQLite DB ${workbuddyDbExists ? "found" : "not found"})`
|
|
1277
1301
|
: null,
|
|
1302
|
+
workbuddyAiInstalled
|
|
1303
|
+
? `- WorkBuddy AI hooks: ${workbuddyAiHookConfigured ? "set" : "unset"} (${workbuddyAiFiles.length} session jsonl file${workbuddyAiFiles.length !== 1 ? "s" : ""} found, SQLite DB ${workbuddyAiDbExists ? "found" : "not found"})`
|
|
1304
|
+
: null,
|
|
1278
1305
|
ompInstalled || ompHookState.ompPresent
|
|
1279
1306
|
? `- oh-my-pi: passive reader (${ompFiles.length} session jsonl file${ompFiles.length !== 1 ? "s" : ""} found${ompHookState.configured ? ", notify extension: yes" : ", notify extension: no"})`
|
|
1280
1307
|
: null,
|
package/src/commands/sync.js
CHANGED
|
@@ -354,6 +354,7 @@ const AUTO_SYNC_SOURCES = new Set([
|
|
|
354
354
|
"trae",
|
|
355
355
|
"unsloth",
|
|
356
356
|
"workbuddy",
|
|
357
|
+
"workbuddy-ai",
|
|
357
358
|
"zcode",
|
|
358
359
|
"zed",
|
|
359
360
|
]);
|
|
@@ -984,8 +985,9 @@ async function cmdSync(argv, context = {}) {
|
|
|
984
985
|
// Plugin-triggered sync points at one specific session file; a normal full
|
|
985
986
|
// sync also passively scans every on-disk OpenClaw transcript so usage is
|
|
986
987
|
// captured even when the session plugin never fires (issue #264). The scan
|
|
987
|
-
// is gated to full syncs
|
|
988
|
-
//
|
|
988
|
+
// is gated to full syncs except for gateway lifecycle signals. A scoped
|
|
989
|
+
// agent_end reads only its transcript or its session in the agent database,
|
|
990
|
+
// and the ordinary background tick stays cheap. The
|
|
989
991
|
// event-identity dedup makes the plugin and passive paths idempotent, so
|
|
990
992
|
// overlap on the same file is safe.
|
|
991
993
|
//
|
|
@@ -999,12 +1001,22 @@ async function cmdSync(argv, context = {}) {
|
|
|
999
1001
|
path: openclawSignal.sessionFile,
|
|
1000
1002
|
source: "openclaw",
|
|
1001
1003
|
});
|
|
1004
|
+
for (const dbPath of [
|
|
1005
|
+
path.join(openclawSignal.openclawHome, "agents", openclawSignal.agentId, "agent", "openclaw-agent.sqlite"),
|
|
1006
|
+
path.join(openclawSignal.openclawHome, "agents", openclawSignal.agentId, "openclaw-agent.sqlite"),
|
|
1007
|
+
]) {
|
|
1008
|
+
if (!(await fs.stat(dbPath).catch(() => null))?.isFile()) continue;
|
|
1009
|
+
openclawSessionFiles.set(openclawCursorKey(dbPath), {
|
|
1010
|
+
path: dbPath, source: "openclaw", sessionId: openclawSignal.sessionId,
|
|
1011
|
+
});
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1002
1014
|
}
|
|
1003
|
-
if (isFullSourceScan && sourceAllowed("openclaw")) {
|
|
1015
|
+
if ((isFullSourceScan || (opts.fromOpenclaw && !openclawSignal)) && sourceAllowed("openclaw")) {
|
|
1004
1016
|
try {
|
|
1005
1017
|
for (const f of await resolveOpenclawSessionFiles(process.env)) {
|
|
1006
1018
|
const key = openclawCursorKey(f);
|
|
1007
|
-
if (!openclawSessionFiles.has(key)) {
|
|
1019
|
+
if (!openclawSessionFiles.has(key) || f.endsWith(".sqlite")) {
|
|
1008
1020
|
openclawSessionFiles.set(key, { path: f, source: "openclaw" });
|
|
1009
1021
|
}
|
|
1010
1022
|
}
|
|
@@ -2597,6 +2609,42 @@ async function cmdSync(argv, context = {}) {
|
|
|
2597
2609
|
}
|
|
2598
2610
|
}
|
|
2599
2611
|
|
|
2612
|
+
// ── WorkBuddy AI — international build (~/.workbuddy-ai/projects/**/*.jsonl) ──
|
|
2613
|
+
// Same Claude-fork family and byte-identical JSONL as the CN build above;
|
|
2614
|
+
// only the home directory differs, so it reuses the WorkBuddy parser and
|
|
2615
|
+
// gets its own source key (and cursor namespace) so the two builds stay
|
|
2616
|
+
// separable instead of merging into one provider row.
|
|
2617
|
+
let workbuddyAiResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
2618
|
+
const workbuddyAiFiles = sourceAllowed("workbuddy-ai")
|
|
2619
|
+
? mergeBothFileSources({
|
|
2620
|
+
resolveFiles: (resolveEnv) => resolveWorkbuddyProjectFiles(resolveEnv, "workbuddy-ai"),
|
|
2621
|
+
env: process.env,
|
|
2622
|
+
})
|
|
2623
|
+
: [];
|
|
2624
|
+
if (sourceAllowed("workbuddy-ai")) {
|
|
2625
|
+
if (progress?.enabled) {
|
|
2626
|
+
progress.start(`Parsing WorkBuddy AI ${renderBar(0)} | buckets 0`);
|
|
2627
|
+
}
|
|
2628
|
+
try {
|
|
2629
|
+
workbuddyAiResult = await parseWorkbuddyIncremental({
|
|
2630
|
+
projectFiles: workbuddyAiFiles,
|
|
2631
|
+
cursors,
|
|
2632
|
+
queuePath,
|
|
2633
|
+
env: process.env,
|
|
2634
|
+
source: "workbuddy-ai",
|
|
2635
|
+
onProgress: (p) => {
|
|
2636
|
+
if (!progress?.enabled) return;
|
|
2637
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
2638
|
+
progress.update(
|
|
2639
|
+
`Parsing WorkBuddy AI ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
2640
|
+
);
|
|
2641
|
+
},
|
|
2642
|
+
});
|
|
2643
|
+
} catch (err) {
|
|
2644
|
+
warnProviderParseFailure("WorkBuddy AI", err, opts);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2600
2648
|
// ── oh-my-pi (passive ~/.omp/agent/sessions/**/*.jsonl reader) ──
|
|
2601
2649
|
// Task-subagent transcripts (nested below the cwd level) are scanned too,
|
|
2602
2650
|
// so their usage counts toward the omp totals.
|
|
@@ -3256,6 +3304,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
3256
3304
|
kimiCodeResult.recordsProcessed +
|
|
3257
3305
|
codebuddyResult.recordsProcessed +
|
|
3258
3306
|
workbuddyResult.recordsProcessed +
|
|
3307
|
+
workbuddyAiResult.recordsProcessed +
|
|
3259
3308
|
ompResult.recordsProcessed +
|
|
3260
3309
|
omoResult.recordsProcessed +
|
|
3261
3310
|
piResult.recordsProcessed +
|
|
@@ -3300,6 +3349,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
3300
3349
|
kimiCodeResult.bucketsQueued +
|
|
3301
3350
|
codebuddyResult.bucketsQueued +
|
|
3302
3351
|
workbuddyResult.bucketsQueued +
|
|
3352
|
+
workbuddyAiResult.bucketsQueued +
|
|
3303
3353
|
ompResult.bucketsQueued +
|
|
3304
3354
|
omoResult.bucketsQueued +
|
|
3305
3355
|
piResult.bucketsQueued +
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const os = require("node:os");
|
|
2
2
|
const path = require("node:path");
|
|
3
3
|
const fs = require("node:fs/promises");
|
|
4
|
+
const { expandHome } = require("../lib/scan-roots");
|
|
4
5
|
|
|
5
6
|
const {
|
|
6
7
|
restoreCodexNotify,
|
|
@@ -44,6 +45,9 @@ async function cmdUninstall(argv) {
|
|
|
44
45
|
const codebuddySettingsPath = path.join(codebuddyDir, "settings.json");
|
|
45
46
|
const workbuddyDir = process.env.WORKBUDDY_HOME || path.join(home, ".workbuddy");
|
|
46
47
|
const workbuddySettingsPath = path.join(workbuddyDir, "settings.json");
|
|
48
|
+
// WorkBuddy AI — international build, sibling home (~/.workbuddy-ai).
|
|
49
|
+
const workbuddyAiDir = expandHome(process.env.WORKBUDDY_AI_HOME, home) || path.join(home, ".workbuddy-ai");
|
|
50
|
+
const workbuddyAiSettingsPath = path.join(workbuddyAiDir, "settings.json");
|
|
47
51
|
const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
|
|
48
52
|
const geminiSettingsPath = resolveGeminiSettingsPath({ configDir: geminiConfigDir });
|
|
49
53
|
const opencodeConfigDir = resolveOpencodeConfigDir({ home, env: process.env });
|
|
@@ -57,6 +61,7 @@ async function cmdUninstall(argv) {
|
|
|
57
61
|
const claudeHookCommand = buildClaudeHookCommand(notifyPath);
|
|
58
62
|
const codebuddyHookCommand = buildHookCommand(notifyPath, "codebuddy");
|
|
59
63
|
const workbuddyHookCommand = buildHookCommand(notifyPath, "workbuddy");
|
|
64
|
+
const workbuddyAiHookCommand = buildHookCommand(notifyPath, "workbuddy-ai");
|
|
60
65
|
const geminiHookCommand = buildGeminiHookCommand(notifyPath);
|
|
61
66
|
|
|
62
67
|
const codexConfigExists = await isFile(codexConfigPath);
|
|
@@ -65,6 +70,7 @@ async function cmdUninstall(argv) {
|
|
|
65
70
|
const claudeConfigExists = await isFile(claudeSettingsPath);
|
|
66
71
|
const codebuddyConfigExists = await isFile(codebuddySettingsPath);
|
|
67
72
|
const workbuddyConfigExists = await isFile(workbuddySettingsPath);
|
|
73
|
+
const workbuddyAiConfigExists = await isFile(workbuddyAiSettingsPath);
|
|
68
74
|
const geminiConfigExists = await isDir(geminiConfigDir);
|
|
69
75
|
const opencodeConfigExists = await isDir(opencodeConfigDir);
|
|
70
76
|
const codexRestore = codexConfigExists
|
|
@@ -106,6 +112,12 @@ async function cmdUninstall(argv) {
|
|
|
106
112
|
hookCommand: workbuddyHookCommand,
|
|
107
113
|
})
|
|
108
114
|
: { removed: false, skippedReason: "config-missing" };
|
|
115
|
+
const workbuddyAiRemove = workbuddyAiConfigExists
|
|
116
|
+
? await removeClaudeHook({
|
|
117
|
+
settingsPath: workbuddyAiSettingsPath,
|
|
118
|
+
hookCommand: workbuddyAiHookCommand,
|
|
119
|
+
})
|
|
120
|
+
: { removed: false, skippedReason: "config-missing" };
|
|
109
121
|
const geminiRemove = geminiConfigExists
|
|
110
122
|
? await removeGeminiHook({ settingsPath: geminiSettingsPath, hookCommand: geminiHookCommand })
|
|
111
123
|
: { removed: false, skippedReason: "config-missing" };
|
|
@@ -191,6 +203,13 @@ async function cmdUninstall(argv) {
|
|
|
191
203
|
? "- WorkBuddy hooks: no change"
|
|
192
204
|
: "- WorkBuddy hooks: skipped"
|
|
193
205
|
: "- WorkBuddy hooks: skipped (settings.json not found)",
|
|
206
|
+
workbuddyAiConfigExists
|
|
207
|
+
? workbuddyAiRemove?.removed
|
|
208
|
+
? `- WorkBuddy AI hooks removed: ${workbuddyAiSettingsPath}`
|
|
209
|
+
: workbuddyAiRemove?.skippedReason === "hook-missing"
|
|
210
|
+
? "- WorkBuddy AI hooks: no change"
|
|
211
|
+
: "- WorkBuddy AI hooks: skipped"
|
|
212
|
+
: "- WorkBuddy AI hooks: skipped (settings.json not found)",
|
|
194
213
|
geminiConfigExists
|
|
195
214
|
? geminiRemove?.removed
|
|
196
215
|
? `- Gemini hooks removed: ${geminiSettingsPath}`
|
package/src/lib/claude-config.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require("node:fs/promises");
|
|
|
2
2
|
const path = require("node:path");
|
|
3
3
|
|
|
4
4
|
const { ensureDir, readJson, writeJson } = require("./fs");
|
|
5
|
+
const { buildNotifyCommand, notifyIdentity } = require("./notify-command");
|
|
5
6
|
|
|
6
7
|
const DEFAULT_EVENT = "SessionEnd";
|
|
7
8
|
const CLAUDE_USAGE_EVENTS = ["Stop", DEFAULT_EVENT];
|
|
@@ -23,10 +24,10 @@ async function upsertClaudeHooks({ settingsPath, hookCommand, events }) {
|
|
|
23
24
|
|
|
24
25
|
for (const event of normalizeEventList(events)) {
|
|
25
26
|
const entries = normalizeEntries(hooks[event]);
|
|
26
|
-
const normalized = normalizeEntriesForCommand(entries, hookCommand);
|
|
27
|
+
const normalized = normalizeEntriesForCommand(entries, hookCommand, event);
|
|
27
28
|
let nextEntries = normalized.entries;
|
|
28
29
|
if (!hasHook(nextEntries, hookCommand)) {
|
|
29
|
-
nextEntries = nextEntries.concat([{ hooks: [{ type: "command", command: hookCommand }] }]);
|
|
30
|
+
nextEntries = nextEntries.concat([{ hooks: [{ type: "command", command: hookCommand, ...hookShell(hookCommand) }] }]);
|
|
30
31
|
changed = true;
|
|
31
32
|
}
|
|
32
33
|
if (normalized.changed) changed = true;
|
|
@@ -106,14 +107,14 @@ async function areClaudeHooksConfigured({ settingsPath, hookCommand, events }) {
|
|
|
106
107
|
// Generic Session-hook command builder. CodeBuddy CLI is a Claude-Code fork
|
|
107
108
|
// and uses the exact same settings.json hook schema, so this function works
|
|
108
109
|
// for any source that accepts the `node notify.cjs --source=<name>` contract.
|
|
109
|
-
function buildHookCommand(notifyPath, source) {
|
|
110
|
+
function buildHookCommand(notifyPath, source, options) {
|
|
110
111
|
const cmd = typeof notifyPath === "string" ? notifyPath : "";
|
|
111
112
|
const src = typeof source === "string" && source ? source : "claude";
|
|
112
|
-
return
|
|
113
|
+
return buildNotifyCommand(cmd, src, options);
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
function buildClaudeHookCommand(notifyPath) {
|
|
116
|
-
return buildHookCommand(notifyPath, "claude");
|
|
116
|
+
function buildClaudeHookCommand(notifyPath, options) {
|
|
117
|
+
return buildHookCommand(notifyPath, "claude", options);
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
function normalizeSettings(raw) {
|
|
@@ -140,12 +141,15 @@ function normalizeCommand(cmd) {
|
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
function hasHook(entries, hookCommand) {
|
|
144
|
+
const shell = hookShell(hookCommand).shell;
|
|
145
|
+
const configured = (hook) => normalizeCommand(hook?.command) === normalizeCommand(hookCommand) &&
|
|
146
|
+
(!shell || (hook.shell === shell && !Object.hasOwn(hook, "args")));
|
|
143
147
|
for (const entry of entries) {
|
|
144
148
|
if (!entry || typeof entry !== "object") continue;
|
|
145
|
-
if (entry.command &&
|
|
149
|
+
if (entry.command && configured(entry)) return true;
|
|
146
150
|
const hooks = Array.isArray(entry.hooks) ? entry.hooks : [];
|
|
147
151
|
for (const hook of hooks) {
|
|
148
|
-
if (hook &&
|
|
152
|
+
if (hook && configured(hook)) return true;
|
|
149
153
|
}
|
|
150
154
|
}
|
|
151
155
|
return false;
|
|
@@ -169,14 +173,25 @@ function stripHookFromEntry(entry, hookCommand) {
|
|
|
169
173
|
return { entry: { ...entry, hooks: nextHooks }, removed: true };
|
|
170
174
|
}
|
|
171
175
|
|
|
172
|
-
function
|
|
176
|
+
function hookShell(command) {
|
|
177
|
+
return typeof command === "string" && command.trimStart().startsWith("& '")
|
|
178
|
+
? { shell: "powershell" } : {};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function normalizeEntriesForCommand(entries, hookCommand, event) {
|
|
182
|
+
const shell = hookShell(hookCommand);
|
|
183
|
+
const repair = (hook) => {
|
|
184
|
+
const next = { ...hook, type: "command", command: hookCommand, ...shell };
|
|
185
|
+
if (shell.shell) delete next.args;
|
|
186
|
+
return next;
|
|
187
|
+
};
|
|
173
188
|
let changed = false;
|
|
174
189
|
const nextEntries = entries.map((entry) => {
|
|
175
190
|
if (!entry || typeof entry !== "object") return entry;
|
|
176
191
|
if (entry.command && commandsEqual(entry.command, hookCommand)) {
|
|
177
|
-
if (entry.type !== "command") {
|
|
192
|
+
if (entry.type !== "command" || entry.command !== hookCommand || (shell.shell && (entry.shell !== shell.shell || Object.hasOwn(entry, "args")))) {
|
|
178
193
|
changed = true;
|
|
179
|
-
return
|
|
194
|
+
return repair(entry);
|
|
180
195
|
}
|
|
181
196
|
return entry;
|
|
182
197
|
}
|
|
@@ -184,9 +199,9 @@ function normalizeEntriesForCommand(entries, hookCommand) {
|
|
|
184
199
|
let hooksChanged = false;
|
|
185
200
|
const nextHooks = entry.hooks.map((hook) => {
|
|
186
201
|
if (hook && commandsEqual(hook.command, hookCommand)) {
|
|
187
|
-
if (hook.type !== "command") {
|
|
202
|
+
if (hook.type !== "command" || hook.command !== hookCommand || (shell.shell && (hook.shell !== shell.shell || Object.hasOwn(hook, "args")))) {
|
|
188
203
|
hooksChanged = true;
|
|
189
|
-
return
|
|
204
|
+
return repair(hook);
|
|
190
205
|
}
|
|
191
206
|
}
|
|
192
207
|
return hook;
|
|
@@ -195,13 +210,30 @@ function normalizeEntriesForCommand(entries, hookCommand) {
|
|
|
195
210
|
changed = true;
|
|
196
211
|
return { ...entry, hooks: nextHooks };
|
|
197
212
|
});
|
|
198
|
-
|
|
213
|
+
const seen = new Set();
|
|
214
|
+
const deduplicated = nextEntries.map((entry) => {
|
|
215
|
+
const matcher = entry?.matcher;
|
|
216
|
+
const scope = JSON.stringify(event === "Stop" || matcher == null || ["", "*", ".*"].includes(matcher) ? null : matcher);
|
|
217
|
+
const keep = (hook) => {
|
|
218
|
+
if (!commandsEqual(hook?.command, hookCommand)) return true;
|
|
219
|
+
if (seen.has(scope)) { changed = true; return false; }
|
|
220
|
+
seen.add(scope);
|
|
221
|
+
return true;
|
|
222
|
+
};
|
|
223
|
+
if (entry?.command) return keep(entry) ? entry : null;
|
|
224
|
+
if (!Array.isArray(entry?.hooks)) return entry;
|
|
225
|
+
const hooks = entry.hooks.filter(keep);
|
|
226
|
+
if (!hooks.length) return null;
|
|
227
|
+
return hooks.length === entry.hooks.length ? entry : { ...entry, hooks };
|
|
228
|
+
}).filter(Boolean);
|
|
229
|
+
return { entries: deduplicated, changed };
|
|
199
230
|
}
|
|
200
231
|
|
|
201
232
|
function commandsEqual(a, b) {
|
|
202
233
|
const left = normalizeCommand(a);
|
|
203
234
|
const right = normalizeCommand(b);
|
|
204
|
-
return Boolean(left && right && left === right
|
|
235
|
+
return Boolean(left && right && (left === right ||
|
|
236
|
+
(notifyIdentity(left) && notifyIdentity(left) === notifyIdentity(right))));
|
|
205
237
|
}
|
|
206
238
|
|
|
207
239
|
function quoteArg(value) {
|
|
@@ -713,6 +713,8 @@ async function parseCodexRolloutFile(filePath, {
|
|
|
713
713
|
};
|
|
714
714
|
byModel.set(effectiveModel, row);
|
|
715
715
|
}
|
|
716
|
+
const subsetKeys = Object.keys(row).filter((key) => /^(?:long_context_|priority_)|^(?:usage_events|rerouted_usage_events)$/.test(key));
|
|
717
|
+
const before = Object.fromEntries(subsetKeys.map((key) => [key, row[key]]));
|
|
716
718
|
addInto(row, delta);
|
|
717
719
|
row.usage_events += 1;
|
|
718
720
|
if (attribution.selectedModel) {
|
|
@@ -758,6 +760,10 @@ async function parseCodexRolloutFile(filePath, {
|
|
|
758
760
|
row.priority_long_context_reasoning_output_tokens += delta.reasoning_output_tokens;
|
|
759
761
|
}
|
|
760
762
|
}
|
|
763
|
+
return { ...delta, ...Object.fromEntries(subsetKeys.map((key) => [key, row[key] - before[key]])),
|
|
764
|
+
selected_models: attribution.selectedModel ? [attribution.selectedModel] : [],
|
|
765
|
+
reroute_reasons: attribution.rerouteReason ? [attribution.rerouteReason] : [],
|
|
766
|
+
model_attribution: attribution.rerouted ? "effective" : "selected" };
|
|
761
767
|
}
|
|
762
768
|
|
|
763
769
|
function ensureTool(name) {
|
|
@@ -927,7 +933,9 @@ async function parseCodexRolloutFile(filePath, {
|
|
|
927
933
|
if (compactionResponseIds.has(record.responseId)) return;
|
|
928
934
|
compactionResponseIds.add(record.responseId);
|
|
929
935
|
if (!record.inRequestedRange) return;
|
|
930
|
-
recordModelUsage(record.delta, record.rawUsage, record.attribution);
|
|
936
|
+
const pricedDelta = recordModelUsage(record.delta, record.rawUsage, record.attribution);
|
|
937
|
+
if (typeof onUsage === "function") onUsage({ timestamp: record.timestamp,
|
|
938
|
+
delta: pricedDelta || record.delta, rawUsage: record.rawUsage, model: record.attribution.model, origin: "compaction" });
|
|
931
939
|
attributeTurn(record.delta);
|
|
932
940
|
}
|
|
933
941
|
|
|
@@ -1098,8 +1106,8 @@ async function parseCodexRolloutFile(filePath, {
|
|
|
1098
1106
|
attributeTurn(null);
|
|
1099
1107
|
} else {
|
|
1100
1108
|
if (seenTokenEvents && delta?.total_tokens > 0) seenTokenEvents.add(eventKey);
|
|
1101
|
-
recordModelUsage(delta, lastUsage || rawDelta);
|
|
1102
|
-
if (typeof onUsage === "function") onUsage({ timestamp: ts, delta, rawUsage: lastUsage || rawDelta, model: attributedModel });
|
|
1109
|
+
const pricedDelta = recordModelUsage(delta, lastUsage || rawDelta);
|
|
1110
|
+
if (typeof onUsage === "function") onUsage({ timestamp: ts, delta: pricedDelta || delta, rawUsage: lastUsage || rawDelta, model: attributedModel });
|
|
1103
1111
|
attributeTurn(delta);
|
|
1104
1112
|
}
|
|
1105
1113
|
} else {
|
|
@@ -15,6 +15,7 @@ const fs = require("node:fs");
|
|
|
15
15
|
const os = require("node:os");
|
|
16
16
|
const path = require("node:path");
|
|
17
17
|
|
|
18
|
+
/** Run a command without a shell by default, optionally piping options.input to stdin. */
|
|
18
19
|
function runCommand(commandRunner, command, args, options = {}) {
|
|
19
20
|
const merged = {
|
|
20
21
|
encoding: "utf8",
|
|
@@ -29,6 +30,7 @@ function runCommand(commandRunner, command, args, options = {}) {
|
|
|
29
30
|
const {
|
|
30
31
|
timeout,
|
|
31
32
|
maxBuffer,
|
|
33
|
+
input,
|
|
32
34
|
completeWhen,
|
|
33
35
|
completionGraceMs = 250,
|
|
34
36
|
killProcessGroup = false,
|
|
@@ -64,7 +66,7 @@ function runCommand(commandRunner, command, args, options = {}) {
|
|
|
64
66
|
...spawnOptions,
|
|
65
67
|
detached: useProcessGroup || spawnOptions.detached,
|
|
66
68
|
shell: useShell,
|
|
67
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
69
|
+
stdio: [input === undefined ? "ignore" : "pipe", "pipe", "pipe"],
|
|
68
70
|
});
|
|
69
71
|
} catch (error) {
|
|
70
72
|
resolve({ status: null, stdout: "", stderr: "", error });
|
|
@@ -168,6 +170,18 @@ function runCommand(commandRunner, command, args, options = {}) {
|
|
|
168
170
|
if (Number.isFinite(timeout) && timeout > 0) {
|
|
169
171
|
timer = setTimeout(() => stopChild({ timeoutExpired: true }), timeout);
|
|
170
172
|
}
|
|
173
|
+
if (input !== undefined) {
|
|
174
|
+
child.stdin?.on("error", (error) => {
|
|
175
|
+
signalChild("SIGKILL");
|
|
176
|
+
settle({ error });
|
|
177
|
+
});
|
|
178
|
+
try {
|
|
179
|
+
child.stdin?.end(input);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
signalChild("SIGKILL");
|
|
182
|
+
settle({ error });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
171
185
|
});
|
|
172
186
|
}
|
|
173
187
|
|
package/src/lib/cursor-config.js
CHANGED
|
@@ -15,7 +15,8 @@ function resolveCursorPaths({ home, platform = process.platform, env = process.e
|
|
|
15
15
|
appDir = pathForPlatform.join(h, "Library", "Application Support", "Cursor");
|
|
16
16
|
} else if (platform === "win32") {
|
|
17
17
|
const appData =
|
|
18
|
-
(
|
|
18
|
+
((!home || h === os.homedir() || env !== process.env) &&
|
|
19
|
+
typeof env.APPDATA === "string" && env.APPDATA.trim()) ||
|
|
19
20
|
pathForPlatform.join(h, "AppData", "Roaming");
|
|
20
21
|
appDir = pathForPlatform.join(appData, "Cursor");
|
|
21
22
|
} else {
|
package/src/lib/gemini-config.js
CHANGED
|
@@ -3,6 +3,7 @@ const path = require("node:path");
|
|
|
3
3
|
const fs = require("node:fs/promises");
|
|
4
4
|
|
|
5
5
|
const { ensureDir, readJson, writeJson } = require("./fs");
|
|
6
|
+
const { buildNotifyCommand } = require("./notify-command");
|
|
6
7
|
|
|
7
8
|
const DEFAULT_EVENT = "SessionEnd";
|
|
8
9
|
const DEFAULT_HOOK_NAME = "tokentracker";
|
|
@@ -99,9 +100,9 @@ async function isGeminiHookConfigured({
|
|
|
99
100
|
return hasHook(entries, { hookCommand, hookName });
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
function buildGeminiHookCommand(notifyPath) {
|
|
103
|
+
function buildGeminiHookCommand(notifyPath, options) {
|
|
103
104
|
const cmd = typeof notifyPath === "string" ? notifyPath : "";
|
|
104
|
-
return
|
|
105
|
+
return buildNotifyCommand(cmd, "gemini", options);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
function buildHookEntry({ hookCommand, hookName, matcher }) {
|
package/src/lib/local-api.js
CHANGED
|
@@ -1056,7 +1056,17 @@ function scanClaudeProjects(projectMap) {
|
|
|
1056
1056
|
// ---------------------------------------------------------------------------
|
|
1057
1057
|
|
|
1058
1058
|
function json(res, data, status) {
|
|
1059
|
-
|
|
1059
|
+
// Usage and cost payloads change on every sync, and cost is re-priced on
|
|
1060
|
+
// every read (pricing is applied per request, never stored in the queue) —
|
|
1061
|
+
// so a cached response can pin stale costs on screen indefinitely. WebView2
|
|
1062
|
+
// caches responses that carry no freshness directive, and the dashboard's
|
|
1063
|
+
// local fetch path omits `cache: "no-store"` (only the cloud path sets it).
|
|
1064
|
+
// Endpoints that care already set this header by hand; do it once here so
|
|
1065
|
+
// every JSON response is covered.
|
|
1066
|
+
res.writeHead(status || 200, {
|
|
1067
|
+
"Content-Type": "application/json",
|
|
1068
|
+
"Cache-Control": "no-store",
|
|
1069
|
+
});
|
|
1060
1070
|
res.end(JSON.stringify(data));
|
|
1061
1071
|
}
|
|
1062
1072
|
|
|
@@ -2610,7 +2620,7 @@ function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir
|
|
|
2610
2620
|
const sessions = await buildSessionAnalytics({ force: refresh });
|
|
2611
2621
|
const wantsCsv = url.searchParams.get("format") === "csv";
|
|
2612
2622
|
const includeSessions = wantsCsv || ["1", "true"].includes(url.searchParams.get("include_sessions"));
|
|
2613
|
-
const result = summarizeSessions(sessions, { from, to, includeSessions });
|
|
2623
|
+
const result = summarizeSessions(sessions, { from, to, includeSessions, ...getTimeZoneContext(url) });
|
|
2614
2624
|
if (wantsCsv) {
|
|
2615
2625
|
const content = sessionsToCsv(result.sessions);
|
|
2616
2626
|
res.statusCode = 200;
|
|
@@ -2645,7 +2655,7 @@ function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir
|
|
|
2645
2655
|
try {
|
|
2646
2656
|
const { buildSessionAnalytics, listSessionsForBrowser } = require("./session-analytics");
|
|
2647
2657
|
const sessions = await buildSessionAnalytics({ force: refresh });
|
|
2648
|
-
const result = listSessionsForBrowser(sessions, { from, to, limit });
|
|
2658
|
+
const result = listSessionsForBrowser(sessions, { from, to, limit, ...getTimeZoneContext(url) });
|
|
2649
2659
|
json(res, { from, to, ...result });
|
|
2650
2660
|
} catch (error) {
|
|
2651
2661
|
// Node fs errors embed the absolute path ("EACCES ... open '/Users/…'").
|