@ipv9/tokentracker-cli 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +464 -0
  3. package/README.ko.md +464 -0
  4. package/README.md +476 -0
  5. package/README.zh-CN.md +469 -0
  6. package/bin/tracker.js +52 -0
  7. package/dashboard/dist/app-icon.png +0 -0
  8. package/dashboard/dist/apple-touch-icon.png +0 -0
  9. package/dashboard/dist/assets/ActivityHeatmap-BxBGHY1z.js +42 -0
  10. package/dashboard/dist/assets/Card-GROxUc_6.js +1 -0
  11. package/dashboard/dist/assets/DashboardPage-BZ5VFoBk.js +19 -0
  12. package/dashboard/dist/assets/DevicePage-CQts7ivB.js +1 -0
  13. package/dashboard/dist/assets/DialogTitle-DqFsk26U.js +1 -0
  14. package/dashboard/dist/assets/FadeIn-HBBXW_3q.js +1 -0
  15. package/dashboard/dist/assets/HeaderGithubStar-COD2HfaF.js +1 -0
  16. package/dashboard/dist/assets/IpCheckPage-Cn0l8-BQ.js +15 -0
  17. package/dashboard/dist/assets/LandingPage-CmV1BMU4.js +4356 -0
  18. package/dashboard/dist/assets/LeaderboardAvatar-CiIpWZwb.js +1 -0
  19. package/dashboard/dist/assets/LeaderboardPage-BU0mlR3g.js +6 -0
  20. package/dashboard/dist/assets/LeaderboardProfileModal-C49_3Xm9.js +72 -0
  21. package/dashboard/dist/assets/LeaderboardProfilePage-Bh1n0BAM.js +1 -0
  22. package/dashboard/dist/assets/LimitsPage-fZAmwLWT.js +2 -0
  23. package/dashboard/dist/assets/LocalOnlyNotice-D3rQdA-P.js +1 -0
  24. package/dashboard/dist/assets/LoginPage-enO4XP-c.js +1 -0
  25. package/dashboard/dist/assets/PopoverPopup-pitJEpN1.js +1 -0
  26. package/dashboard/dist/assets/Select-tT9x4ntD.js +1 -0
  27. package/dashboard/dist/assets/SelectItemText-C7F7eIm0.js +1 -0
  28. package/dashboard/dist/assets/SettingsPage-pDasZNU2.js +1 -0
  29. package/dashboard/dist/assets/SkillsPage-DEu7FY-2.js +1 -0
  30. package/dashboard/dist/assets/WidgetsPage-DGcO7rbp.js +1 -0
  31. package/dashboard/dist/assets/WrappedPage-D_1ID213.js +1 -0
  32. package/dashboard/dist/assets/agent-logos-DXQVg1xy.js +1 -0
  33. package/dashboard/dist/assets/arrow-up-right-DNLo92Ba.js +1 -0
  34. package/dashboard/dist/assets/download-DbBBBpJF.js +1 -0
  35. package/dashboard/dist/assets/geist-mono-cyrillic-400-normal-BPBWmzPh.woff +0 -0
  36. package/dashboard/dist/assets/geist-mono-cyrillic-400-normal-Ce5q_31Z.woff2 +0 -0
  37. package/dashboard/dist/assets/geist-mono-cyrillic-500-normal-CJBLNVQT.woff2 +0 -0
  38. package/dashboard/dist/assets/geist-mono-cyrillic-500-normal-mNhfPmgl.woff +0 -0
  39. package/dashboard/dist/assets/geist-mono-cyrillic-700-normal-DH5Q319x.woff +0 -0
  40. package/dashboard/dist/assets/geist-mono-cyrillic-700-normal-VCNRadI3.woff2 +0 -0
  41. package/dashboard/dist/assets/geist-mono-cyrillic-900-normal-BKC3PZkM.woff +0 -0
  42. package/dashboard/dist/assets/geist-mono-cyrillic-900-normal-DdrQ8KBw.woff2 +0 -0
  43. package/dashboard/dist/assets/geist-mono-latin-400-normal-CoULgQGM.woff +0 -0
  44. package/dashboard/dist/assets/geist-mono-latin-400-normal-LC9RFr9I.woff2 +0 -0
  45. package/dashboard/dist/assets/geist-mono-latin-500-normal-D3o2eNa9.woff2 +0 -0
  46. package/dashboard/dist/assets/geist-mono-latin-500-normal-DOxI7kZ4.woff +0 -0
  47. package/dashboard/dist/assets/geist-mono-latin-700-normal-D6izGJRP.woff2 +0 -0
  48. package/dashboard/dist/assets/geist-mono-latin-700-normal-QGw08Lff.woff +0 -0
  49. package/dashboard/dist/assets/geist-mono-latin-900-normal-CmoKXrdK.woff +0 -0
  50. package/dashboard/dist/assets/geist-mono-latin-900-normal-Cu5MFKsu.woff2 +0 -0
  51. package/dashboard/dist/assets/geist-mono-latin-ext-400-normal-Cgks_Qgx.woff2 +0 -0
  52. package/dashboard/dist/assets/geist-mono-latin-ext-400-normal-CxNRRMGd.woff +0 -0
  53. package/dashboard/dist/assets/geist-mono-latin-ext-500-normal-CQcGuCNt.woff2 +0 -0
  54. package/dashboard/dist/assets/geist-mono-latin-ext-500-normal-diTenJ8L.woff +0 -0
  55. package/dashboard/dist/assets/geist-mono-latin-ext-700-normal-BX9f1BHp.woff +0 -0
  56. package/dashboard/dist/assets/geist-mono-latin-ext-700-normal-YOllDaLV.woff2 +0 -0
  57. package/dashboard/dist/assets/geist-mono-latin-ext-900-normal-Br6WNUGb.woff +0 -0
  58. package/dashboard/dist/assets/geist-mono-latin-ext-900-normal-CrmBTzU2.woff2 +0 -0
  59. package/dashboard/dist/assets/index-BeoRn2gJ.js +2 -0
  60. package/dashboard/dist/assets/info-Bh5qTz6k.js +1 -0
  61. package/dashboard/dist/assets/main-BIY3Nj6I.js +969 -0
  62. package/dashboard/dist/assets/main-G_F9Iu_x.css +1 -0
  63. package/dashboard/dist/assets/use-limits-display-prefs-B4iJ-bBc.js +1 -0
  64. package/dashboard/dist/assets/use-native-settings-DHjPFDv8.js +1 -0
  65. package/dashboard/dist/assets/use-usage-limits-odqe9OY7.js +1 -0
  66. package/dashboard/dist/assets/useCurrency-BsSZiZMP.js +1 -0
  67. package/dashboard/dist/assets/useOpenInteractionType-BQhgq4rv.js +12 -0
  68. package/dashboard/dist/brand-logos/antigravity.svg +1 -0
  69. package/dashboard/dist/brand-logos/claude-code.svg +1 -0
  70. package/dashboard/dist/brand-logos/codebuddy.svg +1 -0
  71. package/dashboard/dist/brand-logos/codex.svg +15 -0
  72. package/dashboard/dist/brand-logos/copilot.svg +1 -0
  73. package/dashboard/dist/brand-logos/cursor.svg +1 -0
  74. package/dashboard/dist/brand-logos/gemini.svg +1 -0
  75. package/dashboard/dist/brand-logos/grok.svg +1 -0
  76. package/dashboard/dist/brand-logos/hermes.svg +1 -0
  77. package/dashboard/dist/brand-logos/kilo.svg +4 -0
  78. package/dashboard/dist/brand-logos/kimi.svg +1 -0
  79. package/dashboard/dist/brand-logos/kiro.svg +1 -0
  80. package/dashboard/dist/brand-logos/openclaw.svg +22 -0
  81. package/dashboard/dist/brand-logos/opencode.svg +3 -0
  82. package/dashboard/dist/clawd/happy.svg +161 -0
  83. package/dashboard/dist/clawd/idle/collapse.svg +101 -0
  84. package/dashboard/dist/clawd/idle/doze.svg +72 -0
  85. package/dashboard/dist/clawd/idle/follow.svg +64 -0
  86. package/dashboard/dist/clawd/idle/living.svg +196 -0
  87. package/dashboard/dist/clawd/idle/look.svg +115 -0
  88. package/dashboard/dist/clawd/idle/yawn.svg +158 -0
  89. package/dashboard/dist/clawd/mini/alert.svg +129 -0
  90. package/dashboard/dist/clawd/mini/crabwalk.svg +76 -0
  91. package/dashboard/dist/clawd/mini/enter-sleep.svg +65 -0
  92. package/dashboard/dist/clawd/mini/enter.svg +115 -0
  93. package/dashboard/dist/clawd/mini/happy.svg +124 -0
  94. package/dashboard/dist/clawd/mini/idle-tight.svg +15 -0
  95. package/dashboard/dist/clawd/mini/idle.svg +83 -0
  96. package/dashboard/dist/clawd/mini/peek.svg +82 -0
  97. package/dashboard/dist/clawd/mini/sleep.svg +112 -0
  98. package/dashboard/dist/clawd/react/double.svg +108 -0
  99. package/dashboard/dist/clawd/react/drag.svg +102 -0
  100. package/dashboard/dist/clawd/react/left.svg +102 -0
  101. package/dashboard/dist/clawd/react/right.svg +102 -0
  102. package/dashboard/dist/clawd/sleep/collapse-sleep.svg +247 -0
  103. package/dashboard/dist/clawd/sleep/sleeping.svg +118 -0
  104. package/dashboard/dist/clawd/sleep/wake.svg +277 -0
  105. package/dashboard/dist/clawd/static-base.svg +21 -0
  106. package/dashboard/dist/clawd/status/disconnected.svg +136 -0
  107. package/dashboard/dist/clawd/status/error.svg +94 -0
  108. package/dashboard/dist/clawd/status/notification.svg +134 -0
  109. package/dashboard/dist/clawd/working/building.svg +231 -0
  110. package/dashboard/dist/clawd/working/carrying.svg +107 -0
  111. package/dashboard/dist/clawd/working/conducting.svg +118 -0
  112. package/dashboard/dist/clawd/working/confused.svg +113 -0
  113. package/dashboard/dist/clawd/working/debugger.svg +91 -0
  114. package/dashboard/dist/clawd/working/juggling.svg +82 -0
  115. package/dashboard/dist/clawd/working/overheated.svg +75 -0
  116. package/dashboard/dist/clawd/working/pushing.svg +125 -0
  117. package/dashboard/dist/clawd/working/sweeping.svg +79 -0
  118. package/dashboard/dist/clawd/working/thinking.svg +119 -0
  119. package/dashboard/dist/clawd/working/typing.svg +153 -0
  120. package/dashboard/dist/clawd/working/ultrathink.svg +166 -0
  121. package/dashboard/dist/clawd/working/wizard.svg +98 -0
  122. package/dashboard/dist/dashboard-dark.png +0 -0
  123. package/dashboard/dist/favicon-16.png +0 -0
  124. package/dashboard/dist/favicon-32.png +0 -0
  125. package/dashboard/dist/favicon.ico +0 -0
  126. package/dashboard/dist/feed.xml +34 -0
  127. package/dashboard/dist/icon-192.png +0 -0
  128. package/dashboard/dist/icon-512.png +0 -0
  129. package/dashboard/dist/index.html +355 -0
  130. package/dashboard/dist/llms.txt +53 -0
  131. package/dashboard/dist/neon_bg.png +0 -0
  132. package/dashboard/dist/robots.txt +7 -0
  133. package/dashboard/dist/share.html +140 -0
  134. package/dashboard/dist/sitemap.xml +27 -0
  135. package/dashboard/dist/widgets-overview.png +0 -0
  136. package/package.json +78 -0
  137. package/scripts/install-local-service.sh +140 -0
  138. package/scripts/uninstall-local-service.sh +21 -0
  139. package/src/cli.js +95 -0
  140. package/src/commands/device-login.js +161 -0
  141. package/src/commands/diagnostics.js +38 -0
  142. package/src/commands/doctor.js +96 -0
  143. package/src/commands/init.js +1195 -0
  144. package/src/commands/serve.js +351 -0
  145. package/src/commands/status.js +609 -0
  146. package/src/commands/sync.js +2285 -0
  147. package/src/commands/uninstall.js +189 -0
  148. package/src/commands/wrapped.js +150 -0
  149. package/src/lib/antigravity-paths.js +48 -0
  150. package/src/lib/browser-auth.js +281 -0
  151. package/src/lib/categorizer-utils.js +232 -0
  152. package/src/lib/claude-categorizer.js +1319 -0
  153. package/src/lib/claude-config.js +204 -0
  154. package/src/lib/cli-ui.js +179 -0
  155. package/src/lib/codex-config.js +350 -0
  156. package/src/lib/codex-context-breakdown.js +400 -0
  157. package/src/lib/codex-rollout-parser.js +623 -0
  158. package/src/lib/codex-token-refresh.js +112 -0
  159. package/src/lib/cursor-config.js +425 -0
  160. package/src/lib/debug-flags.js +7 -0
  161. package/src/lib/diagnostics.js +272 -0
  162. package/src/lib/doctor.js +338 -0
  163. package/src/lib/fs.js +91 -0
  164. package/src/lib/gemini-config.js +288 -0
  165. package/src/lib/grok-hook.js +351 -0
  166. package/src/lib/init-flow.js +42 -0
  167. package/src/lib/local-api.js +1861 -0
  168. package/src/lib/openclaw-hook.js +420 -0
  169. package/src/lib/openclaw-session-plugin.js +520 -0
  170. package/src/lib/opencode-config.js +99 -0
  171. package/src/lib/passive-mode.js +185 -0
  172. package/src/lib/pricing/curated-overrides.json +79 -0
  173. package/src/lib/pricing/index.js +143 -0
  174. package/src/lib/pricing/litellm-fetcher.js +172 -0
  175. package/src/lib/pricing/matcher.js +255 -0
  176. package/src/lib/pricing/seed-snapshot.json +1 -0
  177. package/src/lib/progress.js +76 -0
  178. package/src/lib/project-usage-purge.js +106 -0
  179. package/src/lib/prompt.js +24 -0
  180. package/src/lib/proxy-env.js +131 -0
  181. package/src/lib/rollout.js +8442 -0
  182. package/src/lib/runtime-config.js +122 -0
  183. package/src/lib/skill-usage.js +267 -0
  184. package/src/lib/skills-manager.js +1127 -0
  185. package/src/lib/source-metadata.js +48 -0
  186. package/src/lib/sqlite-reader.js +136 -0
  187. package/src/lib/static-server.js +59 -0
  188. package/src/lib/subscriptions.js +453 -0
  189. package/src/lib/tracker-paths.js +16 -0
  190. package/src/lib/upload-throttle.js +148 -0
  191. package/src/lib/usage-limits.js +1806 -0
  192. package/src/lib/wrapped-aggregator.js +225 -0
@@ -0,0 +1,609 @@
1
+ const os = require("node:os");
2
+ const path = require("node:path");
3
+ const fs = require("node:fs/promises");
4
+ const fssync = require("node:fs");
5
+
6
+ const { readJson } = require("../lib/fs");
7
+ const { readCodexNotify, readEveryCodeNotify } = require("../lib/codex-config");
8
+ const {
9
+ isClaudeHookConfigured,
10
+ buildClaudeHookCommand,
11
+ buildHookCommand,
12
+ } = require("../lib/claude-config");
13
+ const {
14
+ resolveGeminiConfigDir,
15
+ resolveGeminiSettingsPath,
16
+ isGeminiHookConfigured,
17
+ buildGeminiHookCommand,
18
+ } = require("../lib/gemini-config");
19
+ const {
20
+ resolveOpencodeConfigDir,
21
+ isOpencodePluginInstalled,
22
+ } = require("../lib/opencode-config");
23
+ const { collectLocalSubscriptions } = require("../lib/subscriptions");
24
+ const {
25
+ describeCopilotOtelStatus,
26
+ readCopilotOauthToken,
27
+ } = require("../lib/usage-limits");
28
+ const {
29
+ normalizeState: normalizeUploadState,
30
+ } = require("../lib/upload-throttle");
31
+ const { collectTrackerDiagnostics } = require("../lib/diagnostics");
32
+ const { detectPassiveProviders, isPassiveModeActive } = require("../lib/passive-mode");
33
+ const { probeOpenclawHookState } = require("../lib/openclaw-hook");
34
+ const {
35
+ probeOpenclawSessionPluginState,
36
+ } = require("../lib/openclaw-session-plugin");
37
+ const { resolveTrackerPaths } = require("../lib/tracker-paths");
38
+ const {
39
+ resolveKimiWireFiles,
40
+ resolveKimiCodeWireFiles,
41
+ resolveKiroCliDbPath,
42
+ resolveCodebuddyHome,
43
+ resolveCodebuddyProjectFiles,
44
+ resolveOmpSessionFiles,
45
+ resolveOmpAgentDir,
46
+ resolvePiSessionFiles,
47
+ resolvePiAgentDir,
48
+ piAgentDirCollidesWithOmp,
49
+ resolveCraftSessionFiles,
50
+ resolveCraftConfigDir,
51
+ resolveKilocodeTaskFiles,
52
+ resolveRoocodeTaskFiles,
53
+ resolveZedDbPath,
54
+ resolveGooseDbPath,
55
+ listDroidSettingsFiles,
56
+ resolveDroidSessionsDir,
57
+ resolveGrokBuildSessions,
58
+ } = require("../lib/rollout");
59
+ const { probeGrokHookState, resolveGrokHome } = require("../lib/grok-hook");
60
+
61
+ async function cmdStatus(argv = []) {
62
+ const opts = parseArgs(argv);
63
+ if (opts.diagnostics) {
64
+ const diagnostics = await collectTrackerDiagnostics();
65
+ process.stdout.write(JSON.stringify(diagnostics, null, 2) + "\n");
66
+ return;
67
+ }
68
+
69
+ const home = os.homedir();
70
+ const { trackerDir, binDir } = await resolveTrackerPaths({ home });
71
+ const configPath = path.join(trackerDir, "config.json");
72
+ const queuePath = path.join(trackerDir, "queue.jsonl");
73
+ const queueStatePath = path.join(trackerDir, "queue.state.json");
74
+ const cursorsPath = path.join(trackerDir, "cursors.json");
75
+ const notifySignalPath = path.join(trackerDir, "notify.signal");
76
+ const openclawSignalPath = path.join(trackerDir, "openclaw.signal");
77
+ const throttlePath = path.join(trackerDir, "sync.throttle");
78
+ const uploadThrottlePath = path.join(trackerDir, "upload.throttle.json");
79
+ const autoRetryPath = path.join(trackerDir, "auto.retry.json");
80
+ const codexHome = process.env.CODEX_HOME || path.join(home, ".codex");
81
+ const codexConfigPath = path.join(codexHome, "config.toml");
82
+ const codeHome = process.env.CODE_HOME || path.join(home, ".code");
83
+ const codeConfigPath = path.join(codeHome, "config.toml");
84
+ const claudeSettingsPath = path.join(home, ".claude", "settings.json");
85
+ const codebuddySettingsPath = path.join(
86
+ process.env.CODEBUDDY_HOME || path.join(home, ".codebuddy"),
87
+ "settings.json",
88
+ );
89
+ const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
90
+ const geminiSettingsPath = resolveGeminiSettingsPath({
91
+ configDir: geminiConfigDir,
92
+ });
93
+ const opencodeConfigDir = resolveOpencodeConfigDir({
94
+ home,
95
+ env: process.env,
96
+ });
97
+ const notifyPath = path.join(binDir, "notify.cjs");
98
+ const claudeHookCommand = buildClaudeHookCommand(notifyPath);
99
+ const codebuddyHookCommand = buildHookCommand(notifyPath, "codebuddy");
100
+ const geminiHookCommand = buildGeminiHookCommand(notifyPath);
101
+
102
+ const config = await readJson(configPath);
103
+ const cursors = await readJson(cursorsPath);
104
+ const queueState = (await readJson(queueStatePath)) || { offset: 0 };
105
+ const uploadThrottle = normalizeUploadState(
106
+ await readJson(uploadThrottlePath),
107
+ );
108
+ const autoRetry = await readJson(autoRetryPath);
109
+
110
+ const queueSize = await safeStatSize(queuePath);
111
+ const pendingBytes = Math.max(0, queueSize - (queueState.offset || 0));
112
+
113
+ const lastNotify = (await safeReadText(notifySignalPath))?.trim() || null;
114
+ const lastOpenclawSync =
115
+ (await safeReadText(openclawSignalPath))?.trim() || null;
116
+ const lastNotifySpawn = parseEpochMsToIso(
117
+ (await safeReadText(throttlePath))?.trim() || null,
118
+ );
119
+
120
+ const codexNotify = await readCodexNotify(codexConfigPath);
121
+ const notifyConfigured = Array.isArray(codexNotify) && codexNotify.length > 0;
122
+ const everyCodeNotify = await readEveryCodeNotify(codeConfigPath);
123
+ const everyCodeConfigured =
124
+ Array.isArray(everyCodeNotify) && everyCodeNotify.length > 0;
125
+ const claudeHookConfigured = await isClaudeHookConfigured({
126
+ settingsPath: claudeSettingsPath,
127
+ hookCommand: claudeHookCommand,
128
+ });
129
+ const codebuddyHookConfigured = await isClaudeHookConfigured({
130
+ settingsPath: codebuddySettingsPath,
131
+ hookCommand: codebuddyHookCommand,
132
+ });
133
+ const geminiHookConfigured = await isGeminiHookConfigured({
134
+ settingsPath: geminiSettingsPath,
135
+ hookCommand: geminiHookCommand,
136
+ });
137
+ const opencodePluginConfigured = await isOpencodePluginInstalled({
138
+ configDir: opencodeConfigDir,
139
+ });
140
+ const openclawSessionPluginState = await probeOpenclawSessionPluginState({
141
+ home,
142
+ trackerDir,
143
+ env: process.env,
144
+ });
145
+ const openclawHookState = await probeOpenclawHookState({
146
+ home,
147
+ trackerDir,
148
+ env: process.env,
149
+ });
150
+
151
+ const lastUpload = uploadThrottle.lastSuccessMs
152
+ ? parseEpochMsToIso(uploadThrottle.lastSuccessMs)
153
+ : typeof queueState.updatedAt === "string"
154
+ ? queueState.updatedAt
155
+ : null;
156
+ const nextUpload = parseEpochMsToIso(uploadThrottle.nextAllowedAtMs || null);
157
+ const backoffUntil = parseEpochMsToIso(uploadThrottle.backoffUntilMs || null);
158
+ const lastUploadError = uploadThrottle.lastError
159
+ ? `${uploadThrottle.lastErrorAt || "unknown"} ${uploadThrottle.lastError}`
160
+ : null;
161
+ const autoRetryAt = parseEpochMsToIso(autoRetry?.retryAtMs || null);
162
+ const autoRetryLine = autoRetryAt
163
+ ? `- Auto retry after: ${autoRetryAt} (${autoRetry?.reason || "scheduled"}, pending ${Number(
164
+ autoRetry?.pendingBytes || 0,
165
+ )} bytes)`
166
+ : null;
167
+
168
+ const subscriptions = await collectLocalSubscriptions({
169
+ home,
170
+ env: process.env,
171
+ probeKeychain: opts.probeKeychain,
172
+ probeKeychainDetails: opts.probeKeychainDetails,
173
+ });
174
+ const subscriptionLines =
175
+ subscriptions.length > 0 ? subscriptions.map(formatSubscriptionLine) : [];
176
+
177
+ const kimiWireFiles = resolveKimiWireFiles(process.env);
178
+ const kimiHome = process.env.KIMI_HOME || path.join(home, ".kimi");
179
+ const kimiInstalled = fssync.existsSync(path.join(kimiHome, "sessions"));
180
+
181
+ const kimiCodeWireFiles = resolveKimiCodeWireFiles(process.env);
182
+ const kimiCodeHome = process.env.KIMI_CODE_HOME || path.join(home, ".kimi-code");
183
+ const kimiCodeInstalled = fssync.existsSync(path.join(kimiCodeHome, "sessions"));
184
+
185
+ // Kiro CLI — reads from SQLite at
186
+ // ~/Library/Application Support/kiro-cli/data.sqlite3. End-user dashboards
187
+ // show CLI and IDE merged under a single "Kiro" brand; this status line
188
+ // surfaces the CLI sub-path separately for operators.
189
+ const kiroCliDbPath = resolveKiroCliDbPath(process.env);
190
+ const kiroCliInstalled = fssync.existsSync(kiroCliDbPath);
191
+
192
+ // CodeBuddy — passive scan only (no hooks). Surface the file count so
193
+ // operators can confirm jsonl logs are being discovered.
194
+ const codebuddyHome = resolveCodebuddyHome(process.env);
195
+ const codebuddyInstalled = fssync.existsSync(codebuddyHome);
196
+ const codebuddyFiles = codebuddyInstalled
197
+ ? resolveCodebuddyProjectFiles(process.env)
198
+ : [];
199
+
200
+ // oh-my-pi — passive scan only (no hooks).
201
+ const ompAgentDir = resolveOmpAgentDir(process.env);
202
+ const ompInstalled = fssync.existsSync(path.join(ompAgentDir, "sessions"));
203
+ const ompFiles = ompInstalled ? resolveOmpSessionFiles(process.env) : [];
204
+
205
+ // pi (@mariozechner/pi-coding-agent) — passive scan only (no hooks).
206
+ // Skip when its agent dir collides with omp's; sync would dedupe anyway.
207
+ const piCollides = piAgentDirCollidesWithOmp(process.env);
208
+ const piAgentDir = resolvePiAgentDir(process.env);
209
+ const piInstalled = !piCollides && fssync.existsSync(path.join(piAgentDir, "sessions"));
210
+ const piFiles = piInstalled ? resolvePiSessionFiles(process.env) : [];
211
+
212
+ // Craft Agents — passive scan only (no hooks).
213
+ const craftConfigDir = resolveCraftConfigDir(process.env);
214
+ const craftInstalled = fssync.existsSync(craftConfigDir);
215
+ const craftFiles = craftInstalled ? resolveCraftSessionFiles(process.env) : [];
216
+
217
+ // Kilo CLI (kilo.ai @kilocode/plugin) — passive scan of kilo.db.
218
+ const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
219
+ const kiloHome = process.env.KILO_HOME || path.join(xdgDataHome, "kilo");
220
+ const kiloDbPath = path.join(kiloHome, "kilo.db");
221
+ const kiloInstalled = fssync.existsSync(kiloDbPath);
222
+
223
+ // Kilo Code VS Code extension — passive scan of all VS Code-family
224
+ // globalStorage/kilocode.kilo-code/tasks/ ui_messages.json files.
225
+ const kilocodeTaskFiles = resolveKilocodeTaskFiles(process.env);
226
+ const kilocodeInstalled = kilocodeTaskFiles.length > 0;
227
+
228
+ // Roo Code VS Code extension — same Cline-derived ui_messages.json format,
229
+ // different globalStorage subdir (rooveterinaryinc.roo-cline).
230
+ const roocodeTaskFiles = resolveRoocodeTaskFiles(process.env);
231
+ const roocodeInstalled = roocodeTaskFiles.length > 0;
232
+
233
+ // Zed Agent — passive read of hosted threads.db (skips external ACP agents).
234
+ const zedDbPath = resolveZedDbPath(process.env);
235
+ const zedInstalled = fssync.existsSync(zedDbPath);
236
+
237
+ // Goose (Block) — passive cumulative-delta read of sessions.db.
238
+ const gooseDbPath = resolveGooseDbPath(process.env);
239
+ const gooseInstalled = fssync.existsSync(gooseDbPath);
240
+
241
+ // Droid (Factory CLI) — passive cumulative-delta read of *.settings.json.
242
+ const droidSessionsDir = resolveDroidSessionsDir(process.env);
243
+ const droidSettingsFiles = listDroidSettingsFiles(process.env);
244
+ const droidInstalled = droidSettingsFiles.length > 0;
245
+
246
+ // Grok Build (xAI TUI)
247
+ const grokHookState = await probeGrokHookState({ home, trackerDir, env: process.env });
248
+ const grokSessions = grokHookState.hasGrokInstall || grokHookState.sessionsDir
249
+ ? resolveGrokBuildSessions(process.env)
250
+ : [];
251
+ const grokInstalled = grokHookState.hasGrokInstall || grokSessions.length > 0;
252
+
253
+ const copilotToken = readCopilotOauthToken({ home });
254
+ const copilotOtel = describeCopilotOtelStatus({ home, env: process.env });
255
+ const copilotLines = formatCopilotLines({
256
+ token: copilotToken,
257
+ otel: copilotOtel,
258
+ });
259
+
260
+ // Detect passive-mode providers exactly once — both the JSON/light path
261
+ // and the human-readable path consume this, and each call hits 5 readdir
262
+ // syscalls (~5–10ms cold). Memoize.
263
+ const passiveProviders = detectPassiveProviders({
264
+ home,
265
+ hookStatus: {
266
+ codex_notify: notifyConfigured,
267
+ every_code_notify: everyCodeConfigured,
268
+ claude: claudeHookConfigured,
269
+ gemini: geminiHookConfigured,
270
+ opencode_plugin: opencodePluginConfigured,
271
+ openclaw_session_plugin: Boolean(openclawSessionPluginState?.configured),
272
+ codebuddy: Boolean(codebuddyHookConfigured),
273
+ grok: Boolean(grokHookState?.configured),
274
+ },
275
+ });
276
+
277
+ if (opts.json || opts.light) {
278
+ const summary = {
279
+ generated_at: new Date().toISOString(),
280
+ base_url: config?.baseUrl || null,
281
+ device_token_set: Boolean(config?.deviceToken),
282
+ queue: {
283
+ pending_bytes: pendingBytes,
284
+ size_bytes: queueSize,
285
+ offset: queueState.offset || 0,
286
+ },
287
+ last_parse: cursors?.updatedAt || null,
288
+ last_notify: lastNotify || null,
289
+ last_openclaw_sync: lastOpenclawSync || null,
290
+ last_notify_spawn: lastNotifySpawn || null,
291
+ last_upload: lastUpload || null,
292
+ next_upload_after: nextUpload || null,
293
+ backoff_until: backoffUntil || null,
294
+ last_upload_error: lastUploadError || null,
295
+ auto_retry: autoRetry || null,
296
+ hooks: {
297
+ codex_notify: notifyConfigured,
298
+ every_code_notify: everyCodeConfigured,
299
+ claude: claudeHookConfigured,
300
+ gemini: geminiHookConfigured,
301
+ opencode_plugin: opencodePluginConfigured,
302
+ openclaw_session_plugin: Boolean(openclawSessionPluginState?.configured),
303
+ openclaw_legacy: Boolean(openclawHookState?.configured),
304
+ codebuddy: codebuddyInstalled ? Boolean(codebuddyHookConfigured) : null,
305
+ grok: grokInstalled ? Boolean(grokHookState?.configured) : null,
306
+ },
307
+ providers: {
308
+ kimi_code: kimiInstalled || kimiCodeInstalled
309
+ ? { installed: true, files: kimiWireFiles.length + kimiCodeWireFiles.length }
310
+ : { installed: false },
311
+ kiro_cli: kiroCliInstalled
312
+ ? { installed: true, detail: kiroCliDbPath }
313
+ : { installed: false },
314
+ codebuddy: codebuddyInstalled
315
+ ? { installed: true, files: codebuddyFiles.length }
316
+ : { installed: false },
317
+ omp: ompInstalled
318
+ ? { installed: true, files: ompFiles.length }
319
+ : { installed: false },
320
+ pi: piInstalled
321
+ ? { installed: true, files: piFiles.length }
322
+ : { installed: false },
323
+ craft: craftInstalled
324
+ ? { installed: true, files: craftFiles.length }
325
+ : { installed: false },
326
+ kilo_cli: kiloInstalled
327
+ ? { installed: true, detail: kiloDbPath }
328
+ : { installed: false },
329
+ kilocode: kilocodeInstalled
330
+ ? { installed: true, files: kilocodeTaskFiles.length }
331
+ : { installed: false },
332
+ roocode: roocodeInstalled
333
+ ? { installed: true, files: roocodeTaskFiles.length }
334
+ : { installed: false },
335
+ zed: zedInstalled ? { installed: true, detail: zedDbPath } : { installed: false },
336
+ goose: gooseInstalled
337
+ ? { installed: true, detail: gooseDbPath }
338
+ : { installed: false },
339
+ droid: droidInstalled
340
+ ? { installed: true, files: droidSettingsFiles.length, detail: droidSessionsDir }
341
+ : { installed: false },
342
+ grok_build: grokInstalled
343
+ ? {
344
+ installed: true,
345
+ files: grokSessions.length,
346
+ detail: grokHookState.configured ? "hook installed" : "detected",
347
+ }
348
+ : { installed: false },
349
+ },
350
+ copilot: {
351
+ token_set: Boolean(copilotToken),
352
+ otel_has_files: Boolean(copilotOtel.otel_has_files),
353
+ otel_path: copilotOtel.otel_path || null,
354
+ otel_enabled: Boolean(copilotOtel.otel_enabled),
355
+ },
356
+ passive_mode: {
357
+ active: isPassiveModeActive(passiveProviders),
358
+ providers: passiveProviders,
359
+ },
360
+ subscriptions,
361
+ };
362
+
363
+ if (opts.json) {
364
+ process.stdout.write(JSON.stringify(summary, null, 2) + "\n");
365
+ return;
366
+ }
367
+ process.stdout.write(renderLightTable(summary) + "\n");
368
+ return;
369
+ }
370
+
371
+ process.stdout.write(
372
+ [
373
+ "Status:",
374
+ `- Base URL: ${config?.baseUrl || "unset"}`,
375
+ `- Device token: ${config?.deviceToken ? "set" : "unset"}`,
376
+ `- Queue: ${pendingBytes} bytes pending`,
377
+ `- Last parse: ${cursors?.updatedAt || "never"}`,
378
+ `- Last notify: ${lastNotify || "never"}`,
379
+ `- Last OpenClaw-triggered sync: ${lastOpenclawSync || "never"}`,
380
+ `- Last notify-triggered sync: ${lastNotifySpawn || "never"}`,
381
+ `- Last upload: ${lastUpload || "never"}`,
382
+ `- Next upload after: ${nextUpload || "never"}`,
383
+ `- Backoff until: ${backoffUntil || "never"}`,
384
+ lastUploadError ? `- Last upload error: ${lastUploadError}` : null,
385
+ autoRetryLine,
386
+ `- Codex notify: ${notifyConfigured ? JSON.stringify(codexNotify) : "unset"}`,
387
+ `- Every Code notify: ${everyCodeConfigured ? JSON.stringify(everyCodeNotify) : "unset"}`,
388
+ `- Claude hooks: ${claudeHookConfigured ? "set" : "unset"}`,
389
+ `- Gemini hooks: ${geminiHookConfigured ? "set" : "unset"}`,
390
+ `- Opencode plugin: ${opencodePluginConfigured ? "set" : "unset"}`,
391
+ `- OpenClaw session plugin: ${openclawSessionPluginState?.configured ? "set" : "unset"}`,
392
+ `- OpenClaw hook (legacy): ${openclawHookState?.configured ? "set" : "unset"}`,
393
+ kimiInstalled || kimiCodeInstalled
394
+ ? `- Kimi Code: passive reader (${kimiWireFiles.length + kimiCodeWireFiles.length} wire.jsonl file${(kimiWireFiles.length + kimiCodeWireFiles.length) !== 1 ? "s" : ""} found)`
395
+ : null,
396
+ kiroCliInstalled
397
+ ? `- Kiro CLI: SQLite data.sqlite3 found (tokens approximated from char lengths, merged under 'kiro' source)`
398
+ : null,
399
+ codebuddyInstalled
400
+ ? `- CodeBuddy hooks: ${codebuddyHookConfigured ? "set" : "unset"} (${codebuddyFiles.length} session jsonl file${codebuddyFiles.length !== 1 ? "s" : ""} found)`
401
+ : null,
402
+ ompInstalled
403
+ ? `- oh-my-pi: passive reader (${ompFiles.length} session jsonl file${ompFiles.length !== 1 ? "s" : ""} found)`
404
+ : null,
405
+ piInstalled
406
+ ? `- pi: passive reader (${piFiles.length} session jsonl file${piFiles.length !== 1 ? "s" : ""} found)`
407
+ : null,
408
+ craftInstalled
409
+ ? `- Craft Agents: passive reader (${craftFiles.length} session jsonl file${craftFiles.length !== 1 ? "s" : ""} found)`
410
+ : null,
411
+ kiloInstalled
412
+ ? `- Kilo CLI: passive reader (${kiloDbPath})`
413
+ : null,
414
+ kilocodeInstalled
415
+ ? `- Kilo Code (VS Code extension): passive reader (${kilocodeTaskFiles.length} task${kilocodeTaskFiles.length !== 1 ? "s" : ""} across ${new Set(kilocodeTaskFiles.map((t) => t.ide)).size} IDE${new Set(kilocodeTaskFiles.map((t) => t.ide)).size !== 1 ? "s" : ""})`
416
+ : null,
417
+ roocodeInstalled
418
+ ? `- Roo Code (VS Code extension): passive reader (${roocodeTaskFiles.length} task${roocodeTaskFiles.length !== 1 ? "s" : ""} across ${new Set(roocodeTaskFiles.map((t) => t.ide)).size} IDE${new Set(roocodeTaskFiles.map((t) => t.ide)).size !== 1 ? "s" : ""})`
419
+ : null,
420
+ zedInstalled
421
+ ? `- Zed Agent: passive reader (threads.db, hosted models only)`
422
+ : null,
423
+ gooseInstalled
424
+ ? `- Goose (Block): passive reader (sessions.db, cumulative-delta)`
425
+ : null,
426
+ droidInstalled
427
+ ? `- Droid (Factory): passive reader (${droidSettingsFiles.length} session${droidSettingsFiles.length !== 1 ? "s" : ""} in ${droidSessionsDir}, cumulative-delta)`
428
+ : null,
429
+ ...(() => {
430
+ const passive = passiveProviders.filter((p) => p.passive);
431
+ if (passive.length === 0) return [];
432
+ return [
433
+ `- Passive mode: ${passive.length} provider${passive.length !== 1 ? "s" : ""} reading logs without hooks (${passive.map((p) => `${p.name}: ${p.hook_failure_reason || "hook unset"}`).join("; ")})`,
434
+ ];
435
+ })(),
436
+ grokInstalled
437
+ ? `- Grok Build (xAI): ${grokHookState.configured ? "hook installed" : "detected"} (${grokSessions.length} session${grokSessions.length !== 1 ? "s" : ""} found, hook: ${grokHookState.configured ? "yes" : "no"})`
438
+ : null,
439
+ ...copilotLines,
440
+ ...subscriptionLines,
441
+ "",
442
+ ]
443
+ .filter(Boolean)
444
+ .join("\n"),
445
+ );
446
+ }
447
+
448
+ function formatCopilotLines({ token, otel }) {
449
+ if (!token && !otel.otel_has_files) return [];
450
+ const limitsState = token
451
+ ? "set (via GitHub OAuth)"
452
+ : "unset (no Copilot OAuth token found)";
453
+ const usageState = otel.otel_has_files
454
+ ? `set (${otel.otel_path || otel.otel_default_dir})`
455
+ : otel.otel_enabled
456
+ ? "enabled but no files yet"
457
+ : "unset (OTEL export not enabled)";
458
+ const lines = [
459
+ `- GitHub Copilot limits: ${limitsState}`,
460
+ `- GitHub Copilot usage (OTEL): ${usageState}`,
461
+ ];
462
+ if (!otel.otel_has_files) {
463
+ lines.push(
464
+ " To track Copilot token usage, add to your shell profile:",
465
+ " export COPILOT_OTEL_ENABLED=true",
466
+ " export COPILOT_OTEL_EXPORTER_TYPE=file",
467
+ ` export COPILOT_OTEL_FILE_EXPORTER_PATH="${otel.otel_default_dir}/copilot-otel-$(date +%Y%m%d).jsonl"`,
468
+ );
469
+ }
470
+ return lines;
471
+ }
472
+
473
+ function formatSubscriptionLine(entry = {}) {
474
+ const tool = String(entry.tool || "");
475
+ const provider = String(entry.provider || "");
476
+ const product = String(entry.product || "");
477
+ const planType = String(entry.planType || "");
478
+ const rateLimitTier = String(entry.rateLimitTier || "");
479
+ const toolLabel =
480
+ tool === "codex"
481
+ ? "Codex"
482
+ : tool === "opencode"
483
+ ? "OpenCode"
484
+ : tool === "claude"
485
+ ? "Claude Code"
486
+ : tool;
487
+
488
+ if (!planType) return null;
489
+
490
+ if (
491
+ tool === "claude" &&
492
+ provider === "anthropic" &&
493
+ product === "subscription"
494
+ ) {
495
+ const suffix = rateLimitTier ? ` (rate limit tier: ${rateLimitTier})` : "";
496
+ return `- ${toolLabel} subscription: ${planType}${suffix}`;
497
+ }
498
+
499
+ if (provider === "openai" && product === "chatgpt") {
500
+ return `- ${toolLabel} ChatGPT plan: ${planType}`;
501
+ }
502
+
503
+ const productLabel = product ? product.replace(/_/g, " ") : "subscription";
504
+ return `- ${toolLabel} ${productLabel}: ${planType}`;
505
+ }
506
+
507
+ function parseArgs(argv) {
508
+ const out = {
509
+ diagnostics: false,
510
+ json: false,
511
+ light: false,
512
+ noSpinner: false,
513
+ probeKeychain: false,
514
+ probeKeychainDetails: false,
515
+ };
516
+
517
+ for (let i = 0; i < argv.length; i++) {
518
+ const a = argv[i];
519
+ if (a === "--diagnostics") out.diagnostics = true;
520
+ else if (a === "--json") out.json = true;
521
+ else if (a === "--light") out.light = true;
522
+ else if (a === "--no-spinner") out.noSpinner = true;
523
+ else if (a === "--probe-keychain") out.probeKeychain = true;
524
+ else if (a === "--probe-keychain-details") {
525
+ out.probeKeychainDetails = true;
526
+ out.probeKeychain = true;
527
+ } else throw new Error(`Unknown option: ${a}`);
528
+ }
529
+
530
+ return out;
531
+ }
532
+
533
+ // Pure renderer: turn the structured summary into a fixed-width ASCII table.
534
+ // "light" output is for AI agents and CI: deterministic columns, no emoji or
535
+ // spinner side effects, easy to grep. Returns a string (caller adds trailing
536
+ // newline).
537
+ function renderLightTable(summary) {
538
+ const rows = [];
539
+ const push = (k, v) => rows.push([k, v == null || v === "" ? "—" : String(v)]);
540
+
541
+ push("Base URL", summary.base_url);
542
+ push("Device token", summary.device_token_set ? "set" : "unset");
543
+ push("Queue pending (bytes)", summary.queue.pending_bytes);
544
+ push("Queue size (bytes)", summary.queue.size_bytes);
545
+ push("Last parse", summary.last_parse);
546
+ push("Last notify", summary.last_notify);
547
+ push("Last upload", summary.last_upload);
548
+ push("Next upload after", summary.next_upload_after);
549
+ push("Backoff until", summary.backoff_until);
550
+ if (summary.last_upload_error) push("Last upload error", summary.last_upload_error);
551
+
552
+ for (const [name, state] of Object.entries(summary.hooks || {})) {
553
+ push(`Hook · ${name}`, state ? "set" : "unset");
554
+ }
555
+
556
+ for (const [name, info] of Object.entries(summary.providers || {})) {
557
+ const detail = [];
558
+ if (typeof info.installed === "boolean") detail.push(info.installed ? "installed" : "not installed");
559
+ if (typeof info.files === "number") detail.push(`${info.files} file${info.files !== 1 ? "s" : ""}`);
560
+ if (info.detail) detail.push(info.detail);
561
+ push(`Provider · ${name}`, detail.length ? detail.join(", ") : "—");
562
+ }
563
+
564
+ if (summary.passive_mode) {
565
+ push("Passive mode active", summary.passive_mode.active ? "yes" : "no");
566
+ for (const p of summary.passive_mode.providers || []) {
567
+ if (p.passive) {
568
+ push(`Passive · ${p.name}`, `hook ${p.hook_failure_reason || "missing"}, logs present`);
569
+ }
570
+ }
571
+ }
572
+
573
+ const keyWidth = Math.max(...rows.map(([k]) => k.length), 8);
574
+ const valWidth = Math.max(...rows.map(([, v]) => v.length), 8);
575
+ const sep = `+${"-".repeat(keyWidth + 2)}+${"-".repeat(valWidth + 2)}+`;
576
+ const lines = [sep, `| ${"Key".padEnd(keyWidth)} | ${"Value".padEnd(valWidth)} |`, sep];
577
+ for (const [k, v] of rows) {
578
+ lines.push(`| ${k.padEnd(keyWidth)} | ${v.padEnd(valWidth)} |`);
579
+ }
580
+ lines.push(sep);
581
+ return lines.join("\n");
582
+ }
583
+
584
+ async function safeStatSize(p) {
585
+ try {
586
+ const st = await fs.stat(p);
587
+ return st.size || 0;
588
+ } catch (_e) {
589
+ return 0;
590
+ }
591
+ }
592
+
593
+ async function safeReadText(p) {
594
+ try {
595
+ return await fs.readFile(p, "utf8");
596
+ } catch (_e) {
597
+ return null;
598
+ }
599
+ }
600
+
601
+ function parseEpochMsToIso(v) {
602
+ const ms = Number(v);
603
+ if (!Number.isFinite(ms) || ms <= 0) return null;
604
+ const d = new Date(ms);
605
+ if (Number.isNaN(d.getTime())) return null;
606
+ return d.toISOString();
607
+ }
608
+
609
+ module.exports = { cmdStatus };