tokentracker-cli 0.87.6 → 0.87.8

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.
Files changed (115) hide show
  1. package/LICENSE +1 -1
  2. package/README.de.md +32 -32
  3. package/README.ja.md +31 -31
  4. package/README.ko.md +31 -31
  5. package/README.md +37 -36
  6. package/README.zh-CN.md +34 -33
  7. package/dashboard/dist/assets/{AchievementBadge-BRh6I-jK.js → AchievementBadge-CpxX02OK.js} +1 -1
  8. package/dashboard/dist/assets/{AchievementsPage-BsAJOqK8.js → AchievementsPage-CSms8IjI.js} +2 -2
  9. package/dashboard/dist/assets/{AchievementsSection-DebeB3vT.js → AchievementsSection-Sm43tnkh.js} +1 -1
  10. package/dashboard/dist/assets/{BadgeDetailModal-DniVJwrw.js → BadgeDetailModal-cSNqMLsK.js} +1 -1
  11. package/dashboard/dist/assets/{Card-DvKbmZEl.js → Card-wNwNRVSb.js} +1 -1
  12. package/dashboard/dist/assets/{ClawdAnimated-DCyXpvgK.js → ClawdAnimated-S8sIS2Ca.js} +1 -1
  13. package/dashboard/dist/assets/{CommandPalette-BadO9q-D.js → CommandPalette-DVeiwbPM.js} +1 -1
  14. package/dashboard/dist/assets/{CommunityStatsModal-DzBeGuLk.js → CommunityStatsModal-g5P4j3um.js} +1 -1
  15. package/dashboard/dist/assets/{ConfirmModal-BLppThU-.js → ConfirmModal-2P2D5iIp.js} +1 -1
  16. package/dashboard/dist/assets/{Controls-BBVOO6-V.js → Controls-CCaotoQP.js} +1 -1
  17. package/dashboard/dist/assets/{DashboardPage-DgV2-5pv.js → DashboardPage-DxPGTJpe.js} +1 -1
  18. package/dashboard/dist/assets/{DevicePage-BZ8Z3veC.js → DevicePage-_nLwNash.js} +1 -1
  19. package/dashboard/dist/assets/{DialogClose-Be7JwC-H.js → DialogClose-D0giFwO3.js} +1 -1
  20. package/dashboard/dist/assets/DialogDescription-8veCvaUb.js +1 -0
  21. package/dashboard/dist/assets/{DialogTitle-Bgr5ftrn.js → DialogTitle-JS_04eUC.js} +1 -1
  22. package/dashboard/dist/assets/{FadeIn-Cc5Grbxc.js → FadeIn-tFzTWDkC.js} +1 -1
  23. package/dashboard/dist/assets/HeaderGithubStar-B2EUJUTH.js +1 -0
  24. package/dashboard/dist/assets/{HoverTooltip-BwRvWZqo.js → HoverTooltip-BJzHksPT.js} +1 -1
  25. package/dashboard/dist/assets/{IpCheckPage-Ce4dWLI1.js → IpCheckPage-B8mA0mut.js} +1 -1
  26. package/dashboard/dist/assets/{LandingPage-CuDfsomP.js → LandingPage-DmJZDKr9.js} +2 -2
  27. package/dashboard/dist/assets/{LeaderboardAvatar-D50UCqhG.js → LeaderboardAvatar-CDeRQUCg.js} +1 -1
  28. package/dashboard/dist/assets/{LeaderboardPage-fXYjgVOH.js → LeaderboardPage-Dkgm89kY.js} +2 -2
  29. package/dashboard/dist/assets/{LeaderboardProfileModal-DiIM1-8L.js → LeaderboardProfileModal-B6_kuR1z.js} +3 -3
  30. package/dashboard/dist/assets/{LeaderboardProfilePage-UzcVaHbK.js → LeaderboardProfilePage-BRs8sJBV.js} +2 -2
  31. package/dashboard/dist/assets/LimitsPage-DZ1s5NWV.js +4 -0
  32. package/dashboard/dist/assets/LocalOnlyNotice-Cu_hSnux.js +1 -0
  33. package/dashboard/dist/assets/{LoginCard-e4xbbFLq.js → LoginCard-yDZbGsHR.js} +1 -1
  34. package/dashboard/dist/assets/{LoginModal-CLxWc3FD.js → LoginModal-DEvzCUfQ.js} +1 -1
  35. package/dashboard/dist/assets/{LoginPage-sf34MK6Q.js → LoginPage-DBySF9do.js} +1 -1
  36. package/dashboard/dist/assets/{PetPage-BA_BAFKG.js → PetPage-DnzH9t-r.js} +1 -1
  37. package/dashboard/dist/assets/{PopoverPopup-BwFAXu-Z.js → PopoverPopup-DgFsEMax.js} +1 -1
  38. package/dashboard/dist/assets/{ProviderIcon-MRSlzNzV.js → ProviderIcon-BEixe-xP.js} +1 -1
  39. package/dashboard/dist/assets/{ResetPasswordPage-DJAq2p4v.js → ResetPasswordPage-excKqsCL.js} +1 -1
  40. package/dashboard/dist/assets/{SegmentedControl-LWEqN03-.js → SegmentedControl-DDFkH64o.js} +1 -1
  41. package/dashboard/dist/assets/{Select-tkoxDrUN.js → Select-ChEPIuJf.js} +1 -1
  42. package/dashboard/dist/assets/{SelectItemText-bBnvuF48.js → SelectItemText--b7ISnJm.js} +1 -1
  43. package/dashboard/dist/assets/{ServiceStatusPage-DDGNQfQ1.js → ServiceStatusPage-DtAY-Qnn.js} +1 -1
  44. package/dashboard/dist/assets/{SessionsPage-x7-I3BLn.js → SessionsPage-CKFglvuj.js} +1 -1
  45. package/dashboard/dist/assets/{SettingsPage-ColZX8Yi.js → SettingsPage-s0605qWg.js} +1 -1
  46. package/dashboard/dist/assets/{SkillsPage-DELWPebQ.js → SkillsPage-DTsbCucq.js} +1 -1
  47. package/dashboard/dist/assets/{TokenGalaxy-Bz8Cto8i.js → TokenGalaxy-C_M7q8S4.js} +1 -1
  48. package/dashboard/dist/assets/{ToolbarRootContext-BvOVtmfA.js → ToolbarRootContext-CXQwpea-.js} +1 -1
  49. package/dashboard/dist/assets/{TrendMonitor-DuNGP2Rf.js → TrendMonitor-1Yhlplf0.js} +1 -1
  50. package/dashboard/dist/assets/{WidgetsPage-CRGlX9WP.js → WidgetsPage-DC6pmtld.js} +1 -1
  51. package/dashboard/dist/assets/{WrappedPage-DQ3hLeL1.js → WrappedPage-DixoqR13.js} +1 -1
  52. package/dashboard/dist/assets/{__vite-browser-external-CGdlUam8.js → __vite-browser-external-5aLUeBkM.js} +6 -4
  53. package/dashboard/dist/assets/{agent-logos-FfNeyajM.js → agent-logos-DILTbPLg.js} +1 -1
  54. package/dashboard/dist/assets/{arrow-up-right-BEQHH_0c.js → arrow-up-right-DgIDgjVW.js} +1 -1
  55. package/dashboard/dist/assets/{check-Bl_xgq6c.js → check-Bz6l09VH.js} +1 -1
  56. package/dashboard/dist/assets/{chevron-right-YO2OTiWy.js → chevron-right-EvGQXbI3.js} +1 -1
  57. package/dashboard/dist/assets/{copy-DtRgnD2D.js → copy-DbsB0OVy.js} +1 -1
  58. package/dashboard/dist/assets/{download-BiCrU4Wh.js → download-R8nUvXYh.js} +1 -1
  59. package/dashboard/dist/assets/{external-link-CVogXP9K.js → external-link-x0oNzGeq.js} +1 -1
  60. package/dashboard/dist/assets/{flame-GFlQvi3d.js → flame-Bo4IdG6k.js} +1 -1
  61. package/dashboard/dist/assets/{format-tokens-BZrnF3-R.js → format-tokens-BSGC1-Gz.js} +1 -1
  62. package/dashboard/dist/assets/{icons-CzzsWeR-.js → icons-D0Okncmo.js} +1 -1
  63. package/dashboard/dist/assets/{index-mrU077Y8.js → index-BbOkC6wf.js} +1 -1
  64. package/dashboard/dist/assets/{index-CyOwnTlp.js → index-Bvvmei-I.js} +1 -1
  65. package/dashboard/dist/assets/{index-B1S00F5t.js → index-D6F8eNr_.js} +1 -1
  66. package/dashboard/dist/assets/{info-CL-k0W8L.js → info-DXXKNpLG.js} +1 -1
  67. package/dashboard/dist/assets/{limits-providers-DGKlD0uY.js → limits-providers-DYa4fdsE.js} +1 -1
  68. package/dashboard/dist/assets/{link-2-DOsRG9tT.js → link-2-C9mxEnT6.js} +1 -1
  69. package/dashboard/dist/assets/{loader-circle-Da-W_cCG.js → loader-circle-Y8V8Mq9L.js} +1 -1
  70. package/dashboard/dist/assets/{maximize-2-CPp2Asek.js → maximize-2-QuP69Xcg.js} +1 -1
  71. package/dashboard/dist/assets/{modifiers.esm-B0cihd2s.js → modifiers.esm-B3SleRKs.js} +1 -1
  72. package/dashboard/dist/assets/{monitor-smartphone-CLMr7OcT.js → monitor-smartphone-B2LslYi7.js} +1 -1
  73. package/dashboard/dist/assets/{provider-display-h1GGY5sy.js → provider-display-DzBwrDZp.js} +2 -2
  74. package/dashboard/dist/assets/{react-BXj1W2tW.js → react-BooKG84W.js} +1 -1
  75. package/dashboard/dist/assets/{search-DTwQkbLa.js → search-zdYuHmL-.js} +1 -1
  76. package/dashboard/dist/assets/{skills-api-B7_r_5CR.js → skills-api-C2IkKfwE.js} +1 -1
  77. package/dashboard/dist/assets/{sortable.esm-Ba4_NOAf.js → sortable.esm-BHvl5vG5.js} +1 -1
  78. package/dashboard/dist/assets/{store-CZZj0fmU.js → store-BBP4jx0R.js} +1 -1
  79. package/dashboard/dist/assets/{terminal-Dd0uhXcN.js → terminal-C0nhFNQ0.js} +1 -1
  80. package/dashboard/dist/assets/{trash-2-CyBqbulx.js → trash-2-DNVSqbxA.js} +1 -1
  81. package/dashboard/dist/assets/{use-community-stats-D657285K.js → use-community-stats-BK070d1s.js} +1 -1
  82. package/dashboard/dist/assets/{use-limits-display-prefs-z0wBtxb1.js → use-limits-display-prefs-BXOUxblO.js} +1 -1
  83. package/dashboard/dist/assets/{use-ordered-list-CiIuzdVF.js → use-ordered-list-0SVHZHaW.js} +1 -1
  84. package/dashboard/dist/assets/use-reduced-motion-abZbtoVS.js +1 -0
  85. package/dashboard/dist/assets/{use-session-efficiency-pref-BiHWvNJQ.js → use-session-efficiency-pref-eaUpwuLK.js} +1 -1
  86. package/dashboard/dist/assets/{use-transform-DcbCiXLq.js → use-transform-Bhar2U4w.js} +1 -1
  87. package/dashboard/dist/assets/{use-usage-limits-tUq-_GSd.js → use-usage-limits-BGGCMVzi.js} +1 -1
  88. package/dashboard/dist/assets/{useCurrency-BlOEzUvV.js → useCurrency-gIJVmq3w.js} +1 -1
  89. package/dashboard/dist/assets/{useScrollLock-CFKQBf15.js → useScrollLock-BhFzeRtR.js} +1 -1
  90. package/dashboard/dist/assets/{useTokenFormat-ggxahHK0.js → useTokenFormat-BZ8WT-A6.js} +1 -1
  91. package/dashboard/dist/assets/{zap-De7YrxAq.js → zap-Dvc_Jt1t.js} +1 -1
  92. package/dashboard/dist/brand-logos/omp.svg +33 -0
  93. package/dashboard/dist/brand-logos/pi.svg +16 -0
  94. package/dashboard/dist/index.html +7 -7
  95. package/dashboard/dist/ip-check.html +1 -1
  96. package/dashboard/dist/leaderboard.html +1 -1
  97. package/dashboard/dist/llms.txt +4 -4
  98. package/dashboard/dist/share.html +2 -2
  99. package/package.json +3 -3
  100. package/src/commands/init.js +49 -8
  101. package/src/commands/status.js +12 -5
  102. package/src/commands/sync.js +1 -0
  103. package/src/commands/uninstall.js +36 -1
  104. package/src/lib/grok-hook.js +11 -2
  105. package/src/lib/omp-hook.js +589 -0
  106. package/src/lib/opencode-go-limits.js +59 -30
  107. package/src/lib/pricing/seed-snapshot.json +1 -1
  108. package/src/lib/rollout.js +283 -68
  109. package/src/lib/star-cta.js +1 -1
  110. package/src/lib/usage-limits.js +3 -3
  111. package/dashboard/dist/assets/DialogDescription-fYPOWV-6.js +0 -1
  112. package/dashboard/dist/assets/HeaderGithubStar-BN1WZPnH.js +0 -1
  113. package/dashboard/dist/assets/LimitsPage-Clg8P5-M.js +0 -4
  114. package/dashboard/dist/assets/LocalOnlyNotice-rdNf4GvF.js +0 -1
  115. package/dashboard/dist/assets/use-reduced-motion-DXlYG36v.js +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokentracker-cli",
3
- "version": "0.87.6",
3
+ "version": "0.87.8",
4
4
  "description": "Local-first AI coding token usage and cost tracker for 29 tools, with a dashboard, desktop pet, widgets, achievements, and native macOS/Windows apps.",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -54,11 +54,11 @@
54
54
  },
55
55
  "repository": {
56
56
  "type": "git",
57
- "url": "git+https://github.com/mm7894215/TokenTracker.git"
57
+ "url": "git+https://github.com/xiufengsun/TokenTracker.git"
58
58
  },
59
59
  "homepage": "https://www.tokentracker.cc",
60
60
  "bugs": {
61
- "url": "https://github.com/mm7894215/TokenTracker/issues"
61
+ "url": "https://github.com/xiufengsun/TokenTracker/issues"
62
62
  },
63
63
  "keywords": [
64
64
  "token",
@@ -56,6 +56,10 @@ const {
56
56
  removeGrokHook,
57
57
  GROK_HOOK_FILENAME
58
58
  } = require("../lib/grok-hook");
59
+ const {
60
+ upsertOmpHook,
61
+ probeOmpHookState,
62
+ } = require("../lib/omp-hook");
59
63
  const { resolveTrackerPaths } = require("../lib/tracker-paths");
60
64
  const {
61
65
  resolveOmpAgentDir,
@@ -361,6 +365,7 @@ async function runSetup({
361
365
  trackerDir,
362
366
  notifyPath,
363
367
  notifyOriginalPath,
368
+ dryRun: Boolean(opts.dryRun),
364
369
  });
365
370
 
366
371
  return {
@@ -551,7 +556,13 @@ function buildIntegrationTargets({ home, trackerDir, notifyPath }) {
551
556
  };
552
557
  }
553
558
 
554
- async function applyIntegrationSetup({ home, trackerDir, notifyPath, notifyOriginalPath }) {
559
+ async function applyIntegrationSetup({
560
+ home,
561
+ trackerDir,
562
+ notifyPath,
563
+ notifyOriginalPath,
564
+ dryRun = false,
565
+ }) {
555
566
  const context = buildIntegrationTargets({ home, trackerDir, notifyPath });
556
567
  context.notifyOriginalPath = notifyOriginalPath;
557
568
 
@@ -642,15 +653,44 @@ async function applyIntegrationSetup({ home, trackerDir, notifyPath, notifyOrigi
642
653
  }
643
654
  }
644
655
 
645
- // oh-my-pi: passive reader no hook installation needed.
646
- // TokenTracker reads ~/.omp/agent/sessions/**/*.jsonl directly.
656
+ // oh-my-pi: passive session scan always works; also install an optional
657
+ // notify extension so turn_end triggers sync --source omp near-real-time.
647
658
  {
648
- // resolveOmpAgentDir returns null on Windows when ~/.omp doesn't exist (the
649
- // win32 path resolver only yields a dir it can see) — null means "not
650
- // installed", so skip rather than path.join(null, …) and crash.
659
+ const fssyncLocal = require("node:fs");
651
660
  const ompAgentDir = resolveOmpAgentDir(process.env);
652
- if (ompAgentDir && fssync.existsSync(path.join(ompAgentDir, "sessions"))) {
653
- summary.push({ label: "oh-my-pi", status: "detected", detail: "Passive reader (no hook needed)" });
661
+ const ompSessions = ompAgentDir && fssyncLocal.existsSync(path.join(ompAgentDir, "sessions"));
662
+ if (ompAgentDir && (ompSessions || fssyncLocal.existsSync(ompAgentDir))) {
663
+ if (dryRun) {
664
+ const probe = await probeOmpHookState({ home, trackerDir, env: process.env });
665
+ summary.push({
666
+ label: "oh-my-pi",
667
+ status: "detected",
668
+ detail: probe.configured
669
+ ? "Notify extension already installed (passive scan still runs)"
670
+ : "Will install notify extension + keep passive scan",
671
+ });
672
+ } else {
673
+ const result = await upsertOmpHook({ home, trackerDir, env: process.env });
674
+ if (result.written) {
675
+ summary.push({
676
+ label: "oh-my-pi",
677
+ status: "installed",
678
+ detail: `Notify extension → ${result.extensionPath} (passive scan still runs)`,
679
+ });
680
+ } else if (result.skippedReason === "unmanaged-extension-present") {
681
+ summary.push({
682
+ label: "oh-my-pi",
683
+ status: "skipped",
684
+ detail: "Existing unmanaged tokentracker-notify extension left untouched",
685
+ });
686
+ } else {
687
+ summary.push({
688
+ label: "oh-my-pi",
689
+ status: "detected",
690
+ detail: "Passive reader (notify extension not written)",
691
+ });
692
+ }
693
+ }
654
694
  }
655
695
  }
656
696
 
@@ -1904,6 +1944,7 @@ module.exports = {
1904
1944
  installLocalTrackerApp,
1905
1945
  repairCodexNotifyIntegration,
1906
1946
  repairRuntimeIntegrations,
1947
+ applyIntegrationSetup,
1907
1948
  };
1908
1949
 
1909
1950
  async function probeFile(p) {
@@ -84,6 +84,7 @@ const wsl = require("../lib/wsl-probe");
84
84
  const { getWslMode, isInvalidWslMode, shouldProbeWsl, discoverWslHome } = wsl;
85
85
  const { resolveInstallPaths } = require("../lib/install-resolver");
86
86
  const { probeGrokHookState, resolveGrokHome } = require("../lib/grok-hook");
87
+ const { probeOmpHookState } = require("../lib/omp-hook");
87
88
 
88
89
  function formatResolvedPaths(paths, filename) {
89
90
  const active = [];
@@ -359,10 +360,11 @@ async function cmdStatus(argv = []) {
359
360
  ? fssync.existsSync(path.join(workbuddyHome, "workbuddy.db"))
360
361
  : false;
361
362
 
362
- // oh-my-pi — passive scan only (no hooks).
363
+ // oh-my-pi — passive scan + optional notify extension.
363
364
  const ompAgentDir = resolveOmpAgentDir(process.env);
364
365
  const ompInstalled = Boolean(ompAgentDir) && fssync.existsSync(path.join(ompAgentDir, "sessions"));
365
366
  const ompFiles = ompInstalled ? resolveOmpSessionFiles(process.env) : [];
367
+ const ompHookState = await probeOmpHookState({ home, trackerDir, env: process.env });
366
368
 
367
369
  // pi (@mariozechner/pi-coding-agent) — passive scan only (no hooks).
368
370
  // Skip when its agent dir collides with omp's; sync would dedupe anyway.
@@ -771,8 +773,13 @@ async function cmdStatus(argv = []) {
771
773
  workbuddy: workbuddyInstalled
772
774
  ? { installed: true, files: workbuddyFiles.length }
773
775
  : { installed: false },
774
- omp: ompInstalled
775
- ? { installed: true, files: ompFiles.length }
776
+ omp: ompInstalled || ompHookState.ompPresent
777
+ ? {
778
+ installed: true,
779
+ files: ompFiles.length,
780
+ notify_extension: Boolean(ompHookState.configured),
781
+ notify_extension_path: ompHookState.extensionPath || null,
782
+ }
776
783
  : { installed: false },
777
784
  pi: piInstalled
778
785
  ? { installed: true, files: piFiles.length }
@@ -895,8 +902,8 @@ async function cmdStatus(argv = []) {
895
902
  workbuddyInstalled
896
903
  ? `- WorkBuddy hooks: ${workbuddyHookConfigured ? "set" : "unset"} (${workbuddyFiles.length} session jsonl file${workbuddyFiles.length !== 1 ? "s" : ""} found, SQLite DB ${workbuddyDbExists ? "found" : "not found"})`
897
904
  : null,
898
- ompInstalled
899
- ? `- oh-my-pi: passive reader (${ompFiles.length} session jsonl file${ompFiles.length !== 1 ? "s" : ""} found)`
905
+ ompInstalled || ompHookState.ompPresent
906
+ ? `- oh-my-pi: passive reader (${ompFiles.length} session jsonl file${ompFiles.length !== 1 ? "s" : ""} found${ompHookState.configured ? ", notify extension: yes" : ", notify extension: no"})`
900
907
  : null,
901
908
  piInstalled
902
909
  ? `- pi: passive reader (${piFiles.length} session jsonl file${piFiles.length !== 1 ? "s" : ""} found)`
@@ -1377,6 +1377,7 @@ async function cmdSync(argv, context = {}) {
1377
1377
  settingsFiles: droidSettingsFiles,
1378
1378
  cursors,
1379
1379
  queuePath,
1380
+ projectQueuePath,
1380
1381
  // Full-scan sync: drop cursor entries for any session whose
1381
1382
  // settings.json has disappeared off disk so cursors.droid stays
1382
1383
  // bounded by the actual on-disk session count.
@@ -24,6 +24,7 @@ const { resolveOpencodeConfigDir, removeOpencodePlugin } = require("../lib/openc
24
24
  const { removeOpenclawHookConfig } = require("../lib/openclaw-hook");
25
25
  const { removeOpenclawSessionPluginConfig } = require("../lib/openclaw-session-plugin");
26
26
  const { removeGrokHook } = require("../lib/grok-hook");
27
+ const { removeOmpHook } = require("../lib/omp-hook");
27
28
  const { resolveTrackerPaths } = require("../lib/tracker-paths");
28
29
 
29
30
  async function cmdUninstall(argv) {
@@ -104,6 +105,7 @@ async function cmdUninstall(argv) {
104
105
  });
105
106
  const openclawHookRemove = await removeOpenclawHookConfig({ home, trackerDir, env: process.env });
106
107
  const grokHookRemove = await removeGrokHook({ home, trackerDir, env: process.env });
108
+ const ompHookRemove = await removeOmpHook({ home, trackerDir, env: process.env });
107
109
 
108
110
  // Remove installed notify handler.
109
111
  await fs.unlink(notifyPath).catch(() => {});
@@ -195,6 +197,7 @@ async function cmdUninstall(argv) {
195
197
  grokHookRemove?.removed
196
198
  ? `- Grok Build hook removed: ${grokHookRemove.hookPath}`
197
199
  : "- Grok Build hook: no change",
200
+ formatOmpHookRemoveLine(ompHookRemove),
198
201
  opts.purge ? `- Purged: ${path.join(home, ".tokentracker")}` : "- Purge: skipped (use --purge)",
199
202
  ...(machineIdSeedKept
200
203
  ? [`- Kept: ${machineIdSeedPath} (cloud device identity — a reinstall reuses the same device; delete it to fully reset)`]
@@ -204,6 +207,38 @@ async function cmdUninstall(argv) {
204
207
  );
205
208
  }
206
209
 
210
+ function formatOmpHookRemoveLine(ompHookRemove) {
211
+ if (ompHookRemove?.removed) {
212
+ return `- oh-my-pi notify extension removed: ${ompHookRemove.extensionPath}`;
213
+ }
214
+ const reason = ompHookRemove?.skippedReason;
215
+ const residualPath = ompHookRemove?.stagedPath || ompHookRemove?.extensionPath;
216
+ const residual = residualPath
217
+ ? ` (left in place: ${residualPath})`
218
+ : "";
219
+ if (reason === "unmanaged") {
220
+ return `- oh-my-pi notify extension: skipped (unmanaged file)${residual}`;
221
+ }
222
+ if (reason === "unlink-failed") {
223
+ const detail = ompHookRemove?.error ? `: ${ompHookRemove.error}` : "";
224
+ return `- oh-my-pi notify extension: failed to remove${detail}${residual}`;
225
+ }
226
+ if (reason === "extension-read-failed") {
227
+ const detail = ompHookRemove?.error ? `: ${ompHookRemove.error}` : "";
228
+ return `- oh-my-pi notify extension: failed to read${detail}${residual}`;
229
+ }
230
+ if (reason === "identity-changed") {
231
+ return `- oh-my-pi notify extension: skipped (file changed during uninstall)${residual}`;
232
+ }
233
+ if (reason === "omp-agent-dir-unresolved") {
234
+ return "- oh-my-pi notify extension: skipped (omp agent dir unresolved)";
235
+ }
236
+ if (reason === "missing") {
237
+ return "- oh-my-pi notify extension: no change";
238
+ }
239
+ return "- oh-my-pi notify extension: no change";
240
+ }
241
+
207
242
  function parseArgs(argv) {
208
243
  const out = { purge: false };
209
244
  for (let i = 0; i < argv.length; i++) {
@@ -214,7 +249,7 @@ function parseArgs(argv) {
214
249
  return out;
215
250
  }
216
251
 
217
- module.exports = { cmdUninstall };
252
+ module.exports = { cmdUninstall, formatOmpHookRemoveLine };
218
253
 
219
254
  async function isFile(p) {
220
255
  try {
@@ -31,10 +31,15 @@ function resolveLegacyTrackerBinDir(trackerDir) {
31
31
  return path.join(trackerDir, "bin");
32
32
  }
33
33
 
34
- function buildGrokSessionEndHookJson({ notifyGrokHandlerPath }) {
34
+ function buildGrokSessionEndHookJson(
35
+ { notifyGrokHandlerPath },
36
+ { platform = process.platform, execPath = process.execPath } = {},
37
+ ) {
35
38
  // The command runs our dedicated handler.
36
39
  // We pass the session id and cwd via environment variables that Grok already sets.
37
- const cmd = `/usr/bin/env node ${shellQuote(notifyGrokHandlerPath)}`;
40
+ const cmd = platform === "win32"
41
+ ? `${windowsCommandQuote(execPath)} ${windowsCommandQuote(notifyGrokHandlerPath)}`
42
+ : `/usr/bin/env node ${shellQuote(notifyGrokHandlerPath)}`;
38
43
  return {
39
44
  hooks: {
40
45
  SessionEnd: [
@@ -56,6 +61,10 @@ function shellQuote(value) {
56
61
  return `'${String(value).replace(/'/g, "'\\''")}'`;
57
62
  }
58
63
 
64
+ function windowsCommandQuote(value) {
65
+ return `"${String(value).replace(/"/g, '""')}"`;
66
+ }
67
+
59
68
  async function ensureGrokHookFiles({
60
69
  grokHooksDir,
61
70
  trackerDir,