@ipv9/tokentracker-cli 0.39.38 → 0.39.40

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 (80) hide show
  1. package/README.md +43 -11
  2. package/dashboard/dist/assets/{Card-DD4s0V6T.js → Card-JS4gjUZf.js} +1 -1
  3. package/dashboard/dist/assets/DashboardPage-BoXhzJb1.js +60 -0
  4. package/dashboard/dist/assets/{FadeIn-CUSBK0jm.js → FadeIn-DvLfvwys.js} +1 -1
  5. package/dashboard/dist/assets/{IpCheckPage-PHF4lPax.js → IpCheckPage-Dcfc0Uts.js} +1 -1
  6. package/dashboard/dist/assets/LimitsPage-Vz-GVdi9.js +2 -0
  7. package/dashboard/dist/assets/{LocalOnlyNotice-DCmtS_j5.js → LocalOnlyNotice-8xF5couh.js} +1 -1
  8. package/dashboard/dist/assets/PopoverPopup-7HrUzeRx.js +1 -0
  9. package/dashboard/dist/assets/Select-9XDsaO_C.js +1 -0
  10. package/dashboard/dist/assets/SelectItemText-DE5Fs9p6.js +12 -0
  11. package/dashboard/dist/assets/SettingsPage-CMnfAptE.js +1 -0
  12. package/dashboard/dist/assets/SkillsPage-CbvLHN0L.js +1 -0
  13. package/dashboard/dist/assets/WidgetsPage-CeGQlyt7.js +1 -0
  14. package/dashboard/dist/assets/WrappedPage-Bux8qLUC.js +1 -0
  15. package/dashboard/dist/assets/{arrow-up-right-CjaBQnwI.js → arrow-up-right-B7rCoZdW.js} +1 -1
  16. package/dashboard/dist/assets/{download-DLBqkSwp.js → download-CtTYpbcx.js} +1 -1
  17. package/dashboard/dist/assets/format-BuQKK6kQ.js +1 -0
  18. package/dashboard/dist/assets/limitDisplay-WwuTdS_a.js +1 -0
  19. package/dashboard/dist/assets/main-Ck4SUH4m.js +696 -0
  20. package/dashboard/dist/assets/main-ZrWkoMlr.css +1 -0
  21. package/dashboard/dist/assets/mock-data-Bvbe8tYK.js +1 -0
  22. package/dashboard/dist/assets/use-limits-display-prefs-BKWYPgeA.js +1 -0
  23. package/dashboard/dist/assets/use-native-settings-D7Nj1dXR.js +1 -0
  24. package/dashboard/dist/assets/useCurrency-BX3pvyhh.js +1 -0
  25. package/dashboard/dist/index.html +15 -27
  26. package/dashboard/dist/llms.txt +0 -2
  27. package/dashboard/dist/sitemap.xml +0 -12
  28. package/package.json +7 -4
  29. package/src/cli.js +4 -13
  30. package/src/commands/doctor.js +1 -3
  31. package/src/commands/init.js +63 -53
  32. package/src/commands/serve.js +2 -50
  33. package/src/commands/status.js +1 -55
  34. package/src/commands/sync.js +134 -227
  35. package/src/lib/{browser-auth.js → browser-open.js} +3 -149
  36. package/src/lib/claude-categorizer.js +204 -32
  37. package/src/lib/codex-context-breakdown.js +83 -7
  38. package/src/lib/codex-rollout-parser.js +52 -10
  39. package/src/lib/diagnostics.js +1 -49
  40. package/src/lib/doctor.js +12 -87
  41. package/src/lib/file-identity.js +69 -0
  42. package/src/lib/init-flow.js +6 -3
  43. package/src/lib/local-api.js +6 -364
  44. package/src/lib/pricing/curated-overrides.json +1 -1
  45. package/src/lib/pricing/seed-snapshot.json +1 -1
  46. package/src/lib/rollout.js +392 -144
  47. package/src/lib/runtime-config.js +0 -32
  48. package/src/lib/sqlite-reader.js +83 -8
  49. package/dashboard/dist/assets/ActivityHeatmap-BrehqUIb.js +0 -42
  50. package/dashboard/dist/assets/DashboardPage-CyUShBRJ.js +0 -19
  51. package/dashboard/dist/assets/DevicePage-BA74Su1l.js +0 -1
  52. package/dashboard/dist/assets/DialogTitle-DoKsz5wi.js +0 -1
  53. package/dashboard/dist/assets/InsforgeUserHeaderControls-Bk_U2dHr.js +0 -1
  54. package/dashboard/dist/assets/LandingPage-Bbeu84Iu.js +0 -4356
  55. package/dashboard/dist/assets/LeaderboardAvatar-ByqeB6bM.js +0 -1
  56. package/dashboard/dist/assets/LeaderboardPage-BeWXF00S.js +0 -6
  57. package/dashboard/dist/assets/LeaderboardProfileModal-C4yvbZd_.js +0 -72
  58. package/dashboard/dist/assets/LeaderboardProfilePage-D3cbSh5o.js +0 -1
  59. package/dashboard/dist/assets/LimitsPage-dS9C9Vdt.js +0 -2
  60. package/dashboard/dist/assets/LoginPage-Ch3UQoWj.js +0 -1
  61. package/dashboard/dist/assets/PopoverPopup-DjG0gjlA.js +0 -1
  62. package/dashboard/dist/assets/Select-HzIjWJpE.js +0 -1
  63. package/dashboard/dist/assets/SelectItemText-f0tCwtvZ.js +0 -1
  64. package/dashboard/dist/assets/SettingsPage-DJOZAlZy.js +0 -1
  65. package/dashboard/dist/assets/SkillsPage-BQcyJIdx.js +0 -1
  66. package/dashboard/dist/assets/WidgetsPage-B_vxuV09.js +0 -1
  67. package/dashboard/dist/assets/WrappedPage-CZN9Y3Vz.js +0 -1
  68. package/dashboard/dist/assets/index-BeoRn2gJ.js +0 -2
  69. package/dashboard/dist/assets/info-BAIxlHIB.js +0 -1
  70. package/dashboard/dist/assets/main-DAcSUW2t.css +0 -1
  71. package/dashboard/dist/assets/main-pi07CfeC.js +0 -1004
  72. package/dashboard/dist/assets/use-limits-display-prefs-CMfASZsh.js +0 -1
  73. package/dashboard/dist/assets/use-native-settings-CfPik-Ao.js +0 -1
  74. package/dashboard/dist/assets/use-spring-wsmJQRqb.js +0 -1
  75. package/dashboard/dist/assets/use-usage-limits-Bt0Dp6kx.js +0 -1
  76. package/dashboard/dist/assets/useCurrency-CsaAMc3j.js +0 -1
  77. package/dashboard/dist/assets/useOpenInteractionType-CPzi5Z8M.js +0 -12
  78. package/dashboard/dist/share.html +0 -140
  79. package/src/commands/device-login.js +0 -161
  80. package/src/lib/upload-throttle.js +0 -148
@@ -57,7 +57,6 @@ const {
57
57
  resolvePiAgentDir,
58
58
  piAgentDirCollidesWithOmp,
59
59
  } = require("../lib/rollout");
60
- const { resolveRuntimeConfig, DEFAULT_BASE_URL } = require("../lib/runtime-config");
61
60
  const {
62
61
  BOLD,
63
62
  DIM,
@@ -86,7 +85,6 @@ const ASCII_LOGO = [
86
85
  ].join("\n");
87
86
 
88
87
  const DIVIDER = "----------------------------------------------";
89
- const DEFAULT_DASHBOARD_URL = "https://www.tokentracker.cc";
90
88
 
91
89
  // Single source of truth for the welcome screen's provider count + sample list.
92
90
  // Keep in sync with the supported-tools table in CLAUDE.md.
@@ -120,11 +118,6 @@ async function cmdInit(argv) {
120
118
  const linkCodeStatePath = path.join(trackerDir, "link_code_state.json");
121
119
 
122
120
  const existingConfig = await readJson(configPath);
123
- const runtime = resolveRuntimeConfig({
124
- cli: { baseUrl: opts.baseUrl, dashboardUrl: opts.dashboardUrl },
125
- config: existingConfig || {},
126
- env: process.env,
127
- });
128
121
  const notifyPath = path.join(binDir, "notify.cjs");
129
122
  const appDir = path.join(trackerDir, "app");
130
123
  const trackerBinPath = path.join(appDir, "bin", "tracker.js");
@@ -156,7 +149,6 @@ async function cmdInit(argv) {
156
149
  home,
157
150
  trackerDir,
158
151
  notifyPath,
159
- runtime,
160
152
  });
161
153
  renderLocalReport({ summary: preview.summary, isDryRun: true });
162
154
  renderAccountNotLinked({ context: "dry-run" });
@@ -178,7 +170,6 @@ async function cmdInit(argv) {
178
170
  notifyPath,
179
171
  appDir,
180
172
  trackerBinPath,
181
- runtime,
182
173
  existingConfig,
183
174
  });
184
175
  } catch (err) {
@@ -280,20 +271,10 @@ function renderAccountNotLinked({ context } = {}) {
280
271
  renderLocalSuccess();
281
272
  }
282
273
 
283
- function shouldUseBrowserAuth({ deviceToken, opts }) {
284
- if (deviceToken) return false;
285
- if (opts.noAuth) return false;
286
- if (opts.linkCode) return false;
287
- if (opts.email || opts.password) return false;
288
- return true;
289
- }
290
-
291
- async function buildDryRunSummary({ opts, home, trackerDir, notifyPath, runtime }) {
292
- const deviceToken = runtime?.deviceToken || null;
293
- const pendingBrowserAuth = shouldUseBrowserAuth({ deviceToken, opts });
274
+ async function buildDryRunSummary({ opts, home, trackerDir, notifyPath }) {
294
275
  const context = buildIntegrationTargets({ home, trackerDir, notifyPath });
295
276
  const summary = await previewIntegrations({ context });
296
- return { summary, pendingBrowserAuth, deviceToken };
277
+ return { summary };
297
278
  }
298
279
 
299
280
  async function runSetup({
@@ -307,15 +288,12 @@ async function runSetup({
307
288
  notifyPath,
308
289
  appDir,
309
290
  trackerBinPath,
310
- runtime,
311
291
  existingConfig,
312
292
  }) {
313
293
  await ensureDir(trackerDir);
314
294
  await ensureDir(binDir);
315
- let deviceToken = runtime?.deviceToken || null;
316
295
  let deviceId = existingConfig?.deviceId || null;
317
296
  const installedAt = existingConfig?.installedAt || new Date().toISOString();
318
- let pendingBrowserAuth = false;
319
297
 
320
298
  await installLocalTrackerApp({ appDir });
321
299
 
@@ -323,13 +301,33 @@ async function runSetup({
323
301
  existingConfig && typeof existingConfig === "object" && !Array.isArray(existingConfig)
324
302
  ? existingConfig
325
303
  : {};
304
+ // Scrub cloud credentials and endpoints on upgrade. Spreading the previous
305
+ // config forward kept a live InsForge bearer token in config.json, and this
306
+ // branch also removed the `status` / `diagnostics` lines that used to reveal
307
+ // it — so the user is told "local-only, nothing is uploaded" while a valid
308
+ // credential sits on disk with nothing left to report or rotate it.
309
+ const {
310
+ deviceToken: _removedDeviceToken,
311
+ baseUrl: _removedBaseUrl,
312
+ dashboardUrl: _removedDashboardUrl,
313
+ ...carriedConfig
314
+ } = existingPlainConfig;
326
315
  const config = {
327
- ...existingPlainConfig,
316
+ ...carriedConfig,
328
317
  installedAt,
329
- baseUrl: opts.baseUrl || existingPlainConfig.baseUrl || DEFAULT_BASE_URL,
330
318
  };
331
- if (opts.dashboardUrl) {
332
- config.dashboardUrl = opts.dashboardUrl;
319
+
320
+ // Files written by the removed cloud paths. The only code that ever deleted
321
+ // relay-cookies.json was clearRelayCookies(), which went with the auth proxy,
322
+ // so an upgrading machine would otherwise keep a refresh token forever.
323
+ for (const stale of ["relay-cookies.json", "upload.throttle.json", "auto.retry.json"]) {
324
+ try {
325
+ await fs.unlink(path.join(trackerDir, stale));
326
+ } catch (error) {
327
+ if (error?.code !== "ENOENT") {
328
+ process.stderr.write(`[tokentracker] could not remove ${stale}: ${error.message}\n`);
329
+ }
330
+ }
333
331
  }
334
332
 
335
333
  await writeJson(configPath, config);
@@ -350,8 +348,6 @@ async function runSetup({
350
348
 
351
349
  return {
352
350
  summary,
353
- pendingBrowserAuth,
354
- deviceToken,
355
351
  deviceId,
356
352
  installedAt,
357
353
  };
@@ -803,8 +799,6 @@ function arraysEqual(a, b) {
803
799
 
804
800
  function parseArgs(argv) {
805
801
  const out = {
806
- baseUrl: null,
807
- dashboardUrl: null,
808
802
  email: null,
809
803
  password: null,
810
804
  deviceName: null,
@@ -817,13 +811,33 @@ function parseArgs(argv) {
817
811
 
818
812
  for (let i = 0; i < argv.length; i++) {
819
813
  const a = argv[i];
820
- if (a === "--base-url") out.baseUrl = argv[++i] || null;
821
- else if (a === "--dashboard-url") out.dashboardUrl = argv[++i] || null;
822
- else if (a === "--email") out.email = argv[++i] || null;
823
- else if (a === "--password") out.password = argv[++i] || null;
824
- else if (a === "--device-name") out.deviceName = argv[++i] || null;
825
- else if (a === "--link-code") out.linkCode = argv[++i] || null;
826
- else if (a === "--no-auth") out.noAuth = true;
814
+ // Cloud sign-in flags (--email/--password/--device-name/--link-code/
815
+ // --no-auth) are gone: there is no account to sign into. Accept and
816
+ // ignore --no-auth so existing scripts and hooks do not start failing
817
+ // with "Unknown option"; the others were only ever used with it.
818
+ if (a === "--no-auth") {
819
+ // Accepted and ignored: it was the "skip sign-in" opt-out, and there is
820
+ // no sign-in left to skip. Silently tolerated so existing hooks and
821
+ // scripts do not start failing with "Unknown option".
822
+ out.noAuth = true;
823
+ } else if (a === "--email" || a === "--password" || a === "--device-name") {
824
+ i += 1;
825
+ } else if (
826
+ a.startsWith("--email=") ||
827
+ a.startsWith("--password=") ||
828
+ a.startsWith("--device-name=")
829
+ ) {
830
+ // Swallow the `=` form too. Falling through raised
831
+ // `Unknown option: --password=hunter2`, and bin/tracker.js prints the
832
+ // stack — putting the secret on stderr and into any captured log.
833
+ } else if (a === "--link-code" || a.startsWith("--link-code=")) {
834
+ // Fail loudly. Unlike the others this was documented standalone, so a
835
+ // user pairing a device would otherwise see "Setup complete!" and
836
+ // believe the code was honored.
837
+ throw new Error(
838
+ "--link-code is no longer supported: TokenTracker is local-only and has no device pairing.",
839
+ );
840
+ }
827
841
  else if (a === "--no-open") out.noOpen = true;
828
842
  else if (a === "--yes") out.yes = true;
829
843
  else if (a === "--dry-run") out.dryRun = true;
@@ -883,7 +897,6 @@ const codexOriginalPath = ${JSON.stringify(originalPath)};
883
897
  const codeOriginalPath = ${JSON.stringify(path.join(trackerDir, "code_notify_original.json"))};
884
898
  const trackerBinPath = ${JSON.stringify(trackerBinPath)};
885
899
  const depsMarkerPath = path.join(trackerDir, 'app', 'bin', 'tracker.js');
886
- const configPath = path.join(trackerDir, 'config.json');
887
900
  const fallbackPkg = ${JSON.stringify(fallbackPkg)};
888
901
  const selfPath = path.resolve(__filename);
889
902
  const home = os.homedir();
@@ -919,20 +932,17 @@ if (debugEnabled) {
919
932
  }
920
933
 
921
934
  // Throttle spawn: at most once per 20 seconds.
935
+ // Parsing local logs and uploading to the cloud are two separate decisions.
936
+ // This hook makes only the first one. It runs whether or not a device token
937
+ // exists, because a local-only install still needs its queue refreshed when a
938
+ // session ends. Upload stays gated on a device token inside sync itself, so
939
+ // no credential here means local parse happens and nothing is transmitted.
922
940
  try {
923
- const throttlePath = path.join(trackerDir, 'sync.throttle');
924
- let deviceToken = process.env.TOKENTRACKER_DEVICE_TOKEN || null;
925
- if (!deviceToken) {
926
- try {
927
- const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
928
- if (cfg && typeof cfg.deviceToken === 'string') deviceToken = cfg.deviceToken;
929
- } catch (_) {}
930
- }
931
- const canSync = Boolean(deviceToken && deviceToken.length > 0);
932
- const now = Date.now();
933
- let last = 0;
934
- try { last = Number(fs.readFileSync(throttlePath, 'utf8')) || 0; } catch (_) {}
935
- if (canSync && now - last > 20_000) {
941
+ const throttlePath = path.join(trackerDir, 'sync.throttle');
942
+ const now = Date.now();
943
+ let last = 0;
944
+ try { last = Number(fs.readFileSync(throttlePath, 'utf8')) || 0; } catch (_) {}
945
+ if (now - last > 20_000) {
936
946
  try { fs.writeFileSync(throttlePath, String(now), 'utf8'); } catch (_) {}
937
947
  const hasLocalRuntime = fs.existsSync(trackerBinPath);
938
948
  const hasLocalDeps = fs.existsSync(depsMarkerPath);
@@ -2,7 +2,6 @@ const http = require("node:http");
2
2
  const os = require("node:os");
3
3
  const path = require("node:path");
4
4
  const fssync = require("node:fs");
5
- const cp = require("node:child_process");
6
5
 
7
6
  const { resolveTrackerPaths } = require("../lib/tracker-paths");
8
7
  const { createLocalApiHandler, resolveQueuePath } = require("../lib/local-api");
@@ -12,7 +11,7 @@ const {
12
11
  } = require("../lib/local-data-preflight");
13
12
  const { ensurePricingLoaded } = require("../lib/pricing");
14
13
  const { serveStaticFile } = require("../lib/static-server");
15
- const { openInBrowser } = require("../lib/browser-auth");
14
+ const { openInBrowser } = require("../lib/browser-open");
16
15
 
17
16
  const DEFAULT_PORT = 7680;
18
17
  const DEFAULT_MAX_PORT_ATTEMPTS = 20;
@@ -20,7 +19,7 @@ const NPM_PACKAGE_NAME = "@ipv9/tokentracker-cli";
20
19
  const LOCAL_BIND_HOST = "127.0.0.1";
21
20
 
22
21
  function buildPortInUseHint(port) {
23
- return `Port ${port} is still in use after cleanup. Try: npx ${NPM_PACKAGE_NAME} serve --port ${port + 1}\n`;
22
+ return `Port ${port} is unavailable. Try: npx ${NPM_PACKAGE_NAME} serve --port ${port + 1}\n`;
24
23
  }
25
24
 
26
25
  function isPortUnavailableError(error) {
@@ -155,7 +154,6 @@ async function cmdServe(argv) {
155
154
  try {
156
155
  port = await listenOnAvailablePort(server, opts.port, {
157
156
  allowFallback: !opts.portExplicit,
158
- ensurePortFreeFn: opts.portExplicit ? ensurePortFree : null,
159
157
  onRetry: (failedPort) => {
160
158
  process.stdout.write(`Port ${failedPort} unavailable, trying ${failedPort + 1}...\n`);
161
159
  },
@@ -208,47 +206,6 @@ async function cmdServe(argv) {
208
206
  await new Promise(() => {});
209
207
  }
210
208
 
211
- function findPidOnPort(port) {
212
- try {
213
- const out = cp.execFileSync("lsof", ["-ti", `tcp:${port}`], { encoding: "utf8", timeout: 5000 });
214
- const pids = out.trim().split(/\s+/).map(Number).filter((n) => Number.isFinite(n) && n > 0);
215
- return pids;
216
- } catch (_e) {
217
- return [];
218
- }
219
- }
220
-
221
- async function ensurePortFree(port) {
222
- const pids = findPidOnPort(port);
223
- if (pids.length === 0) return;
224
-
225
- // Don't kill ourselves
226
- const self = process.pid;
227
- const targets = pids.filter((p) => p !== self);
228
- if (targets.length === 0) return;
229
-
230
- process.stdout.write(`Stopping previous server on port ${port} (pid ${targets.join(", ")})...\n`);
231
- for (const pid of targets) {
232
- try {
233
- process.kill(pid, "SIGTERM");
234
- } catch (_e) {}
235
- }
236
-
237
- // Wait briefly for port to free up
238
- for (let i = 0; i < 10; i++) {
239
- await new Promise((r) => setTimeout(r, 300));
240
- if (findPidOnPort(port).length === 0) return;
241
- }
242
-
243
- // Force kill if still alive
244
- for (const pid of targets) {
245
- try {
246
- process.kill(pid, "SIGKILL");
247
- } catch (_e) {}
248
- }
249
- await new Promise((r) => setTimeout(r, 500));
250
- }
251
-
252
209
  function listenOnce(server, port, host) {
253
210
  return new Promise((resolve, reject) => {
254
211
  let settled = false;
@@ -283,7 +240,6 @@ async function listenOnAvailablePort(
283
240
  host = LOCAL_BIND_HOST,
284
241
  allowFallback = false,
285
242
  maxAttempts = DEFAULT_MAX_PORT_ATTEMPTS,
286
- ensurePortFreeFn = null,
287
243
  onRetry = null,
288
244
  } = {},
289
245
  ) {
@@ -292,10 +248,6 @@ async function listenOnAvailablePort(
292
248
  let lastError = null;
293
249
 
294
250
  for (let i = 0; i < attempts && port < 65536; i++, port++) {
295
- if (ensurePortFreeFn) {
296
- await ensurePortFreeFn(port);
297
- }
298
-
299
251
  try {
300
252
  await listenOnce(server, port, host);
301
253
  return port;
@@ -25,9 +25,6 @@ const {
25
25
  describeCopilotOtelStatus,
26
26
  readCopilotOauthToken,
27
27
  } = require("../lib/usage-limits");
28
- const {
29
- normalizeState: normalizeUploadState,
30
- } = require("../lib/upload-throttle");
31
28
  const { collectTrackerDiagnostics } = require("../lib/diagnostics");
32
29
  const { detectPassiveProviders, isPassiveModeActive } = require("../lib/passive-mode");
33
30
  const { probeOpenclawHookState } = require("../lib/openclaw-hook");
@@ -68,15 +65,11 @@ async function cmdStatus(argv = []) {
68
65
 
69
66
  const home = os.homedir();
70
67
  const { trackerDir, binDir } = await resolveTrackerPaths({ home });
71
- const configPath = path.join(trackerDir, "config.json");
72
68
  const queuePath = path.join(trackerDir, "queue.jsonl");
73
- const queueStatePath = path.join(trackerDir, "queue.state.json");
74
69
  const cursorsPath = path.join(trackerDir, "cursors.json");
75
70
  const notifySignalPath = path.join(trackerDir, "notify.signal");
76
71
  const openclawSignalPath = path.join(trackerDir, "openclaw.signal");
77
72
  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
73
  const codexHome = process.env.CODEX_HOME || path.join(home, ".codex");
81
74
  const codexConfigPath = path.join(codexHome, "config.toml");
82
75
  const codeHome = process.env.CODE_HOME || path.join(home, ".code");
@@ -99,16 +92,9 @@ async function cmdStatus(argv = []) {
99
92
  const codebuddyHookCommand = buildHookCommand(notifyPath, "codebuddy");
100
93
  const geminiHookCommand = buildGeminiHookCommand(notifyPath);
101
94
 
102
- const config = await readJson(configPath);
103
95
  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
96
 
110
97
  const queueSize = await safeStatSize(queuePath);
111
- const pendingBytes = Math.max(0, queueSize - (queueState.offset || 0));
112
98
 
113
99
  const lastNotify = (await safeReadText(notifySignalPath))?.trim() || null;
114
100
  const lastOpenclawSync =
@@ -148,23 +134,6 @@ async function cmdStatus(argv = []) {
148
134
  env: process.env,
149
135
  });
150
136
 
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
137
  const subscriptions = await collectLocalSubscriptions({
169
138
  home,
170
139
  env: process.env,
@@ -277,22 +246,13 @@ async function cmdStatus(argv = []) {
277
246
  if (opts.json || opts.light) {
278
247
  const summary = {
279
248
  generated_at: new Date().toISOString(),
280
- base_url: config?.baseUrl || null,
281
- device_token_set: Boolean(config?.deviceToken),
282
249
  queue: {
283
- pending_bytes: pendingBytes,
284
250
  size_bytes: queueSize,
285
- offset: queueState.offset || 0,
286
251
  },
287
252
  last_parse: cursors?.updatedAt || null,
288
253
  last_notify: lastNotify || null,
289
254
  last_openclaw_sync: lastOpenclawSync || null,
290
255
  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
256
  hooks: {
297
257
  codex_notify: notifyConfigured,
298
258
  every_code_notify: everyCodeConfigured,
@@ -371,18 +331,11 @@ async function cmdStatus(argv = []) {
371
331
  process.stdout.write(
372
332
  [
373
333
  "Status:",
374
- `- Base URL: ${config?.baseUrl || "unset"}`,
375
- `- Device token: ${config?.deviceToken ? "set" : "unset"}`,
376
- `- Queue: ${pendingBytes} bytes pending`,
334
+ `- Queue: ${queueSize} bytes`,
377
335
  `- Last parse: ${cursors?.updatedAt || "never"}`,
378
336
  `- Last notify: ${lastNotify || "never"}`,
379
337
  `- Last OpenClaw-triggered sync: ${lastOpenclawSync || "never"}`,
380
338
  `- 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
339
  `- Codex notify: ${notifyConfigured ? JSON.stringify(codexNotify) : "unset"}`,
387
340
  `- Every Code notify: ${everyCodeConfigured ? JSON.stringify(everyCodeNotify) : "unset"}`,
388
341
  `- Claude hooks: ${claudeHookConfigured ? "set" : "unset"}`,
@@ -538,16 +491,9 @@ function renderLightTable(summary) {
538
491
  const rows = [];
539
492
  const push = (k, v) => rows.push([k, v == null || v === "" ? "—" : String(v)]);
540
493
 
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
494
  push("Queue size (bytes)", summary.queue.size_bytes);
545
495
  push("Last parse", summary.last_parse);
546
496
  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
497
 
552
498
  for (const [name, state] of Object.entries(summary.hooks || {})) {
553
499
  push(`Hook · ${name}`, state ? "set" : "unset");