@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.
- package/LICENSE +21 -0
- package/README.ja.md +464 -0
- package/README.ko.md +464 -0
- package/README.md +476 -0
- package/README.zh-CN.md +469 -0
- package/bin/tracker.js +52 -0
- package/dashboard/dist/app-icon.png +0 -0
- package/dashboard/dist/apple-touch-icon.png +0 -0
- package/dashboard/dist/assets/ActivityHeatmap-BxBGHY1z.js +42 -0
- package/dashboard/dist/assets/Card-GROxUc_6.js +1 -0
- package/dashboard/dist/assets/DashboardPage-BZ5VFoBk.js +19 -0
- package/dashboard/dist/assets/DevicePage-CQts7ivB.js +1 -0
- package/dashboard/dist/assets/DialogTitle-DqFsk26U.js +1 -0
- package/dashboard/dist/assets/FadeIn-HBBXW_3q.js +1 -0
- package/dashboard/dist/assets/HeaderGithubStar-COD2HfaF.js +1 -0
- package/dashboard/dist/assets/IpCheckPage-Cn0l8-BQ.js +15 -0
- package/dashboard/dist/assets/LandingPage-CmV1BMU4.js +4356 -0
- package/dashboard/dist/assets/LeaderboardAvatar-CiIpWZwb.js +1 -0
- package/dashboard/dist/assets/LeaderboardPage-BU0mlR3g.js +6 -0
- package/dashboard/dist/assets/LeaderboardProfileModal-C49_3Xm9.js +72 -0
- package/dashboard/dist/assets/LeaderboardProfilePage-Bh1n0BAM.js +1 -0
- package/dashboard/dist/assets/LimitsPage-fZAmwLWT.js +2 -0
- package/dashboard/dist/assets/LocalOnlyNotice-D3rQdA-P.js +1 -0
- package/dashboard/dist/assets/LoginPage-enO4XP-c.js +1 -0
- package/dashboard/dist/assets/PopoverPopup-pitJEpN1.js +1 -0
- package/dashboard/dist/assets/Select-tT9x4ntD.js +1 -0
- package/dashboard/dist/assets/SelectItemText-C7F7eIm0.js +1 -0
- package/dashboard/dist/assets/SettingsPage-pDasZNU2.js +1 -0
- package/dashboard/dist/assets/SkillsPage-DEu7FY-2.js +1 -0
- package/dashboard/dist/assets/WidgetsPage-DGcO7rbp.js +1 -0
- package/dashboard/dist/assets/WrappedPage-D_1ID213.js +1 -0
- package/dashboard/dist/assets/agent-logos-DXQVg1xy.js +1 -0
- package/dashboard/dist/assets/arrow-up-right-DNLo92Ba.js +1 -0
- package/dashboard/dist/assets/download-DbBBBpJF.js +1 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-400-normal-BPBWmzPh.woff +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-400-normal-Ce5q_31Z.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-500-normal-CJBLNVQT.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-500-normal-mNhfPmgl.woff +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-700-normal-DH5Q319x.woff +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-700-normal-VCNRadI3.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-900-normal-BKC3PZkM.woff +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-900-normal-DdrQ8KBw.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-400-normal-CoULgQGM.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-400-normal-LC9RFr9I.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-500-normal-D3o2eNa9.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-500-normal-DOxI7kZ4.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-700-normal-D6izGJRP.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-700-normal-QGw08Lff.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-900-normal-CmoKXrdK.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-900-normal-Cu5MFKsu.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-400-normal-Cgks_Qgx.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-400-normal-CxNRRMGd.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-500-normal-CQcGuCNt.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-500-normal-diTenJ8L.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-700-normal-BX9f1BHp.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-700-normal-YOllDaLV.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-900-normal-Br6WNUGb.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-900-normal-CrmBTzU2.woff2 +0 -0
- package/dashboard/dist/assets/index-BeoRn2gJ.js +2 -0
- package/dashboard/dist/assets/info-Bh5qTz6k.js +1 -0
- package/dashboard/dist/assets/main-BIY3Nj6I.js +969 -0
- package/dashboard/dist/assets/main-G_F9Iu_x.css +1 -0
- package/dashboard/dist/assets/use-limits-display-prefs-B4iJ-bBc.js +1 -0
- package/dashboard/dist/assets/use-native-settings-DHjPFDv8.js +1 -0
- package/dashboard/dist/assets/use-usage-limits-odqe9OY7.js +1 -0
- package/dashboard/dist/assets/useCurrency-BsSZiZMP.js +1 -0
- package/dashboard/dist/assets/useOpenInteractionType-BQhgq4rv.js +12 -0
- package/dashboard/dist/brand-logos/antigravity.svg +1 -0
- package/dashboard/dist/brand-logos/claude-code.svg +1 -0
- package/dashboard/dist/brand-logos/codebuddy.svg +1 -0
- package/dashboard/dist/brand-logos/codex.svg +15 -0
- package/dashboard/dist/brand-logos/copilot.svg +1 -0
- package/dashboard/dist/brand-logos/cursor.svg +1 -0
- package/dashboard/dist/brand-logos/gemini.svg +1 -0
- package/dashboard/dist/brand-logos/grok.svg +1 -0
- package/dashboard/dist/brand-logos/hermes.svg +1 -0
- package/dashboard/dist/brand-logos/kilo.svg +4 -0
- package/dashboard/dist/brand-logos/kimi.svg +1 -0
- package/dashboard/dist/brand-logos/kiro.svg +1 -0
- package/dashboard/dist/brand-logos/openclaw.svg +22 -0
- package/dashboard/dist/brand-logos/opencode.svg +3 -0
- package/dashboard/dist/clawd/happy.svg +161 -0
- package/dashboard/dist/clawd/idle/collapse.svg +101 -0
- package/dashboard/dist/clawd/idle/doze.svg +72 -0
- package/dashboard/dist/clawd/idle/follow.svg +64 -0
- package/dashboard/dist/clawd/idle/living.svg +196 -0
- package/dashboard/dist/clawd/idle/look.svg +115 -0
- package/dashboard/dist/clawd/idle/yawn.svg +158 -0
- package/dashboard/dist/clawd/mini/alert.svg +129 -0
- package/dashboard/dist/clawd/mini/crabwalk.svg +76 -0
- package/dashboard/dist/clawd/mini/enter-sleep.svg +65 -0
- package/dashboard/dist/clawd/mini/enter.svg +115 -0
- package/dashboard/dist/clawd/mini/happy.svg +124 -0
- package/dashboard/dist/clawd/mini/idle-tight.svg +15 -0
- package/dashboard/dist/clawd/mini/idle.svg +83 -0
- package/dashboard/dist/clawd/mini/peek.svg +82 -0
- package/dashboard/dist/clawd/mini/sleep.svg +112 -0
- package/dashboard/dist/clawd/react/double.svg +108 -0
- package/dashboard/dist/clawd/react/drag.svg +102 -0
- package/dashboard/dist/clawd/react/left.svg +102 -0
- package/dashboard/dist/clawd/react/right.svg +102 -0
- package/dashboard/dist/clawd/sleep/collapse-sleep.svg +247 -0
- package/dashboard/dist/clawd/sleep/sleeping.svg +118 -0
- package/dashboard/dist/clawd/sleep/wake.svg +277 -0
- package/dashboard/dist/clawd/static-base.svg +21 -0
- package/dashboard/dist/clawd/status/disconnected.svg +136 -0
- package/dashboard/dist/clawd/status/error.svg +94 -0
- package/dashboard/dist/clawd/status/notification.svg +134 -0
- package/dashboard/dist/clawd/working/building.svg +231 -0
- package/dashboard/dist/clawd/working/carrying.svg +107 -0
- package/dashboard/dist/clawd/working/conducting.svg +118 -0
- package/dashboard/dist/clawd/working/confused.svg +113 -0
- package/dashboard/dist/clawd/working/debugger.svg +91 -0
- package/dashboard/dist/clawd/working/juggling.svg +82 -0
- package/dashboard/dist/clawd/working/overheated.svg +75 -0
- package/dashboard/dist/clawd/working/pushing.svg +125 -0
- package/dashboard/dist/clawd/working/sweeping.svg +79 -0
- package/dashboard/dist/clawd/working/thinking.svg +119 -0
- package/dashboard/dist/clawd/working/typing.svg +153 -0
- package/dashboard/dist/clawd/working/ultrathink.svg +166 -0
- package/dashboard/dist/clawd/working/wizard.svg +98 -0
- package/dashboard/dist/dashboard-dark.png +0 -0
- package/dashboard/dist/favicon-16.png +0 -0
- package/dashboard/dist/favicon-32.png +0 -0
- package/dashboard/dist/favicon.ico +0 -0
- package/dashboard/dist/feed.xml +34 -0
- package/dashboard/dist/icon-192.png +0 -0
- package/dashboard/dist/icon-512.png +0 -0
- package/dashboard/dist/index.html +355 -0
- package/dashboard/dist/llms.txt +53 -0
- package/dashboard/dist/neon_bg.png +0 -0
- package/dashboard/dist/robots.txt +7 -0
- package/dashboard/dist/share.html +140 -0
- package/dashboard/dist/sitemap.xml +27 -0
- package/dashboard/dist/widgets-overview.png +0 -0
- package/package.json +78 -0
- package/scripts/install-local-service.sh +140 -0
- package/scripts/uninstall-local-service.sh +21 -0
- package/src/cli.js +95 -0
- package/src/commands/device-login.js +161 -0
- package/src/commands/diagnostics.js +38 -0
- package/src/commands/doctor.js +96 -0
- package/src/commands/init.js +1195 -0
- package/src/commands/serve.js +351 -0
- package/src/commands/status.js +609 -0
- package/src/commands/sync.js +2285 -0
- package/src/commands/uninstall.js +189 -0
- package/src/commands/wrapped.js +150 -0
- package/src/lib/antigravity-paths.js +48 -0
- package/src/lib/browser-auth.js +281 -0
- package/src/lib/categorizer-utils.js +232 -0
- package/src/lib/claude-categorizer.js +1319 -0
- package/src/lib/claude-config.js +204 -0
- package/src/lib/cli-ui.js +179 -0
- package/src/lib/codex-config.js +350 -0
- package/src/lib/codex-context-breakdown.js +400 -0
- package/src/lib/codex-rollout-parser.js +623 -0
- package/src/lib/codex-token-refresh.js +112 -0
- package/src/lib/cursor-config.js +425 -0
- package/src/lib/debug-flags.js +7 -0
- package/src/lib/diagnostics.js +272 -0
- package/src/lib/doctor.js +338 -0
- package/src/lib/fs.js +91 -0
- package/src/lib/gemini-config.js +288 -0
- package/src/lib/grok-hook.js +351 -0
- package/src/lib/init-flow.js +42 -0
- package/src/lib/local-api.js +1861 -0
- package/src/lib/openclaw-hook.js +420 -0
- package/src/lib/openclaw-session-plugin.js +520 -0
- package/src/lib/opencode-config.js +99 -0
- package/src/lib/passive-mode.js +185 -0
- package/src/lib/pricing/curated-overrides.json +79 -0
- package/src/lib/pricing/index.js +143 -0
- package/src/lib/pricing/litellm-fetcher.js +172 -0
- package/src/lib/pricing/matcher.js +255 -0
- package/src/lib/pricing/seed-snapshot.json +1 -0
- package/src/lib/progress.js +76 -0
- package/src/lib/project-usage-purge.js +106 -0
- package/src/lib/prompt.js +24 -0
- package/src/lib/proxy-env.js +131 -0
- package/src/lib/rollout.js +8442 -0
- package/src/lib/runtime-config.js +122 -0
- package/src/lib/skill-usage.js +267 -0
- package/src/lib/skills-manager.js +1127 -0
- package/src/lib/source-metadata.js +48 -0
- package/src/lib/sqlite-reader.js +136 -0
- package/src/lib/static-server.js +59 -0
- package/src/lib/subscriptions.js +453 -0
- package/src/lib/tracker-paths.js +16 -0
- package/src/lib/upload-throttle.js +148 -0
- package/src/lib/usage-limits.js +1806 -0
- 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 };
|