vibeusage 0.2.21 → 0.2.23

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 (40) hide show
  1. package/README.md +306 -173
  2. package/README.old.md +324 -0
  3. package/README.zh-CN.md +304 -188
  4. package/package.json +32 -32
  5. package/src/cli.js +37 -37
  6. package/src/commands/activate-if-needed.js +41 -0
  7. package/src/commands/diagnostics.js +8 -9
  8. package/src/commands/doctor.js +31 -26
  9. package/src/commands/init.js +285 -218
  10. package/src/commands/status.js +86 -83
  11. package/src/commands/sync.js +178 -130
  12. package/src/commands/uninstall.js +66 -62
  13. package/src/lib/activation-check.js +341 -0
  14. package/src/lib/browser-auth.js +52 -54
  15. package/src/lib/claude-config.js +25 -25
  16. package/src/lib/cli-ui.js +35 -35
  17. package/src/lib/codex-config.js +40 -36
  18. package/src/lib/debug-flags.js +2 -2
  19. package/src/lib/diagnostics.js +70 -57
  20. package/src/lib/doctor.js +139 -132
  21. package/src/lib/fs.js +17 -17
  22. package/src/lib/gemini-config.js +44 -40
  23. package/src/lib/init-flow.js +16 -22
  24. package/src/lib/insforge-client.js +10 -10
  25. package/src/lib/insforge.js +9 -3
  26. package/src/lib/openclaw-hook.js +89 -66
  27. package/src/lib/openclaw-session-plugin.js +116 -92
  28. package/src/lib/opencode-config.js +31 -32
  29. package/src/lib/opencode-usage-audit.js +34 -31
  30. package/src/lib/progress.js +12 -13
  31. package/src/lib/project-usage-purge.js +23 -17
  32. package/src/lib/prompt.js +8 -4
  33. package/src/lib/rollout.js +342 -241
  34. package/src/lib/runtime-config.js +34 -22
  35. package/src/lib/subscriptions.js +94 -92
  36. package/src/lib/tracker-paths.js +6 -6
  37. package/src/lib/upload-throttle.js +35 -16
  38. package/src/lib/uploader.js +33 -29
  39. package/src/lib/vibeusage-api.js +72 -56
  40. package/src/lib/vibeusage-public-repo.js +41 -24
@@ -1,9 +1,9 @@
1
- const os = require('node:os');
2
- const path = require('node:path');
3
- const fs = require('node:fs/promises');
4
- const cp = require('node:child_process');
1
+ const os = require("node:os");
2
+ const path = require("node:path");
3
+ const fs = require("node:fs/promises");
4
+ const cp = require("node:child_process");
5
5
 
6
- const { ensureDir, readJson, writeJson, openLock } = require('../lib/fs');
6
+ const { ensureDir, readJson, writeJson, openLock } = require("../lib/fs");
7
7
  const {
8
8
  listRolloutFiles,
9
9
  listClaudeProjectFiles,
@@ -13,22 +13,22 @@ const {
13
13
  parseClaudeIncremental,
14
14
  parseGeminiIncremental,
15
15
  parseOpencodeIncremental,
16
- parseOpenclawIncremental
17
- } = require('../lib/rollout');
18
- const { drainQueueToCloud } = require('../lib/uploader');
19
- const { collectLocalSubscriptions } = require('../lib/subscriptions');
20
- const { createProgress, renderBar, formatNumber, formatBytes } = require('../lib/progress');
21
- const { syncHeartbeat } = require('../lib/vibeusage-api');
16
+ parseOpenclawIncremental,
17
+ } = require("../lib/rollout");
18
+ const { drainQueueToCloud } = require("../lib/uploader");
19
+ const { collectLocalSubscriptions } = require("../lib/subscriptions");
20
+ const { createProgress, renderBar, formatNumber, formatBytes } = require("../lib/progress");
21
+ const { syncHeartbeat } = require("../lib/vibeusage-api");
22
22
  const {
23
23
  DEFAULTS: UPLOAD_DEFAULTS,
24
24
  normalizeState: normalizeUploadState,
25
25
  decideAutoUpload,
26
26
  recordUploadSuccess,
27
- recordUploadFailure
28
- } = require('../lib/upload-throttle');
29
- const { purgeProjectUsage } = require('../lib/project-usage-purge');
30
- const { resolveTrackerPaths } = require('../lib/tracker-paths');
31
- const { resolveRuntimeConfig } = require('../lib/runtime-config');
27
+ recordUploadFailure,
28
+ } = require("../lib/upload-throttle");
29
+ const { purgeProjectUsage } = require("../lib/project-usage-purge");
30
+ const { resolveTrackerPaths } = require("../lib/tracker-paths");
31
+ const { resolveRuntimeConfig } = require("../lib/runtime-config");
32
32
 
33
33
  async function cmdSync(argv) {
34
34
  const opts = parseArgs(argv);
@@ -40,42 +40,44 @@ async function cmdSync(argv) {
40
40
  await writeOpenclawSignal(trackerDir);
41
41
  }
42
42
 
43
- const lockPath = path.join(trackerDir, 'sync.lock');
43
+ const lockPath = path.join(trackerDir, "sync.lock");
44
44
  const lock = await openLock(lockPath, { quietIfLocked: opts.auto });
45
45
  if (!lock) return;
46
46
 
47
47
  let progress = null;
48
48
  try {
49
49
  progress = !opts.auto ? createProgress({ stream: process.stdout }) : null;
50
- const configPath = path.join(trackerDir, 'config.json');
51
- const cursorsPath = path.join(trackerDir, 'cursors.json');
52
- const queuePath = path.join(trackerDir, 'queue.jsonl');
53
- const queueStatePath = path.join(trackerDir, 'queue.state.json');
54
- const projectQueuePath = path.join(trackerDir, 'project.queue.jsonl');
55
- const projectQueueStatePath = path.join(trackerDir, 'project.queue.state.json');
56
- const uploadThrottlePath = path.join(trackerDir, 'upload.throttle.json');
50
+ const configPath = path.join(trackerDir, "config.json");
51
+ const cursorsPath = path.join(trackerDir, "cursors.json");
52
+ const queuePath = path.join(trackerDir, "queue.jsonl");
53
+ const queueStatePath = path.join(trackerDir, "queue.state.json");
54
+ const projectQueuePath = path.join(trackerDir, "project.queue.jsonl");
55
+ const projectQueueStatePath = path.join(trackerDir, "project.queue.state.json");
56
+ const uploadThrottlePath = path.join(trackerDir, "upload.throttle.json");
57
57
 
58
58
  const config = await readJson(configPath);
59
59
  const cursors = (await readJson(cursorsPath)) || { version: 1, files: {}, updatedAt: null };
60
60
  const uploadThrottle = normalizeUploadState(await readJson(uploadThrottlePath));
61
61
  let uploadThrottleState = uploadThrottle;
62
62
 
63
- const codexHome = process.env.CODEX_HOME || path.join(home, '.codex');
64
- const codeHome = process.env.CODE_HOME || path.join(home, '.code');
65
- const claudeProjectsDir = path.join(home, '.claude', 'projects');
66
- const geminiHome = process.env.GEMINI_HOME || path.join(home, '.gemini');
67
- const geminiTmpDir = path.join(geminiHome, 'tmp');
68
- const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, '.local', 'share');
69
- const opencodeHome = process.env.OPENCODE_HOME || path.join(xdgDataHome, 'opencode');
70
- const opencodeStorageDir = path.join(opencodeHome, 'storage');
63
+ const codexHome = process.env.CODEX_HOME || path.join(home, ".codex");
64
+ const codeHome = process.env.CODE_HOME || path.join(home, ".code");
65
+ const claudeProjectsDir = path.join(home, ".claude", "projects");
66
+ const geminiHome = process.env.GEMINI_HOME || path.join(home, ".gemini");
67
+ const geminiTmpDir = path.join(geminiHome, "tmp");
68
+ const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
69
+ const opencodeHome = process.env.OPENCODE_HOME || path.join(xdgDataHome, "opencode");
70
+ const opencodeStorageDir = path.join(opencodeHome, "storage");
71
71
 
72
72
  // OpenClaw hook integration: allow a hook to request incremental parsing for a single session jsonl.
73
73
  // We still parse all regular sources so model/source attribution stays complete (e.g. Kimi sessions).
74
- const openclawSignal = opts.fromOpenclaw ? resolveOpenclawSignal({ home, env: process.env }) : null;
74
+ const openclawSignal = opts.fromOpenclaw
75
+ ? resolveOpenclawSignal({ home, env: process.env })
76
+ : null;
75
77
 
76
78
  const sources = [
77
- { source: 'codex', sessionsDir: path.join(codexHome, 'sessions') },
78
- { source: 'every-code', sessionsDir: path.join(codeHome, 'sessions') }
79
+ { source: "codex", sessionsDir: path.join(codexHome, "sessions") },
80
+ { source: "every-code", sessionsDir: path.join(codeHome, "sessions") },
79
81
  ];
80
82
 
81
83
  const rolloutFiles = [];
@@ -89,10 +91,14 @@ async function cmdSync(argv) {
89
91
  }
90
92
  }
91
93
 
92
- const openclawFiles = openclawSignal?.sessionFile ? [{ path: openclawSignal.sessionFile, source: 'openclaw' }] : [];
94
+ const openclawFiles = openclawSignal?.sessionFile
95
+ ? [{ path: openclawSignal.sessionFile, source: "openclaw" }]
96
+ : [];
93
97
 
94
98
  if (progress?.enabled) {
95
- progress.start(`Parsing ${renderBar(0)} 0/${formatNumber(rolloutFiles.length)} files | buckets 0`);
99
+ progress.start(
100
+ `Parsing ${renderBar(0)} 0/${formatNumber(rolloutFiles.length)} files | buckets 0`,
101
+ );
96
102
  }
97
103
 
98
104
  const parseResult = await parseRolloutIncremental({
@@ -105,10 +111,10 @@ async function cmdSync(argv) {
105
111
  const pct = p.total > 0 ? p.index / p.total : 1;
106
112
  progress.update(
107
113
  `Parsing ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(
108
- p.bucketsQueued
109
- )}`
114
+ p.bucketsQueued,
115
+ )}`,
110
116
  );
111
- }
117
+ },
112
118
  });
113
119
 
114
120
  let openclawResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
@@ -119,7 +125,7 @@ async function cmdSync(argv) {
119
125
  cursors,
120
126
  queuePath,
121
127
  projectQueuePath,
122
- source: 'openclaw'
128
+ source: "openclaw",
123
129
  });
124
130
  }
125
131
 
@@ -128,7 +134,7 @@ async function cmdSync(argv) {
128
134
  signal: openclawSignal,
129
135
  cursors,
130
136
  queuePath,
131
- projectQueuePath
137
+ projectQueuePath,
132
138
  });
133
139
  openclawResult.filesProcessed += openclawFallback.filesProcessed;
134
140
  openclawResult.eventsAggregated += openclawFallback.eventsAggregated;
@@ -138,7 +144,9 @@ async function cmdSync(argv) {
138
144
  let claudeResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
139
145
  if (claudeFiles.length > 0) {
140
146
  if (progress?.enabled) {
141
- progress.start(`Parsing Claude ${renderBar(0)} 0/${formatNumber(claudeFiles.length)} files | buckets 0`);
147
+ progress.start(
148
+ `Parsing Claude ${renderBar(0)} 0/${formatNumber(claudeFiles.length)} files | buckets 0`,
149
+ );
142
150
  }
143
151
  claudeResult = await parseClaudeIncremental({
144
152
  projectFiles: claudeFiles,
@@ -150,11 +158,11 @@ async function cmdSync(argv) {
150
158
  const pct = p.total > 0 ? p.index / p.total : 1;
151
159
  progress.update(
152
160
  `Parsing Claude ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(
153
- p.bucketsQueued
154
- )}`
161
+ p.bucketsQueued,
162
+ )}`,
155
163
  );
156
164
  },
157
- source: 'claude'
165
+ source: "claude",
158
166
  });
159
167
  }
160
168
 
@@ -162,7 +170,9 @@ async function cmdSync(argv) {
162
170
  let geminiResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
163
171
  if (geminiFiles.length > 0) {
164
172
  if (progress?.enabled) {
165
- progress.start(`Parsing Gemini ${renderBar(0)} 0/${formatNumber(geminiFiles.length)} files | buckets 0`);
173
+ progress.start(
174
+ `Parsing Gemini ${renderBar(0)} 0/${formatNumber(geminiFiles.length)} files | buckets 0`,
175
+ );
166
176
  }
167
177
  geminiResult = await parseGeminiIncremental({
168
178
  sessionFiles: geminiFiles,
@@ -174,11 +184,11 @@ async function cmdSync(argv) {
174
184
  const pct = p.total > 0 ? p.index / p.total : 1;
175
185
  progress.update(
176
186
  `Parsing Gemini ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(
177
- p.bucketsQueued
178
- )}`
187
+ p.bucketsQueued,
188
+ )}`,
179
189
  );
180
190
  },
181
- source: 'gemini'
191
+ source: "gemini",
182
192
  });
183
193
  }
184
194
 
@@ -186,7 +196,9 @@ async function cmdSync(argv) {
186
196
  let opencodeResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
187
197
  if (opencodeFiles.length > 0) {
188
198
  if (progress?.enabled) {
189
- progress.start(`Parsing Opencode ${renderBar(0)} 0/${formatNumber(opencodeFiles.length)} files | buckets 0`);
199
+ progress.start(
200
+ `Parsing Opencode ${renderBar(0)} 0/${formatNumber(opencodeFiles.length)} files | buckets 0`,
201
+ );
190
202
  }
191
203
  opencodeResult = await parseOpencodeIncremental({
192
204
  messageFiles: opencodeFiles,
@@ -198,23 +210,23 @@ async function cmdSync(argv) {
198
210
  const pct = p.total > 0 ? p.index / p.total : 1;
199
211
  progress.update(
200
212
  `Parsing Opencode ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
201
- p.total
202
- )} files | buckets ${formatNumber(p.bucketsQueued)}`
213
+ p.total,
214
+ )} files | buckets ${formatNumber(p.bucketsQueued)}`,
203
215
  );
204
216
  },
205
- source: 'opencode'
217
+ source: "opencode",
206
218
  });
207
219
  }
208
220
 
209
221
  if (cursors?.projectHourly?.projects && projectQueuePath && projectQueueStatePath) {
210
222
  for (const [projectKey, meta] of Object.entries(cursors.projectHourly.projects)) {
211
- if (!meta || typeof meta !== 'object') continue;
212
- if (meta.status !== 'blocked' || !meta.purge_pending) continue;
223
+ if (!meta || typeof meta !== "object") continue;
224
+ if (meta.status !== "blocked" || !meta.purge_pending) continue;
213
225
  await purgeProjectUsage({
214
226
  projectKey,
215
227
  projectQueuePath,
216
228
  projectQueueStatePath,
217
- projectState: cursors.projectHourly
229
+ projectState: cursors.projectHourly,
218
230
  });
219
231
  meta.purge_pending = false;
220
232
  meta.purged_at = new Date().toISOString();
@@ -249,7 +261,7 @@ async function cmdSync(argv) {
249
261
  autoDecision = decideAutoUpload({
250
262
  nowMs: Date.now(),
251
263
  pendingBytes,
252
- state: uploadThrottle
264
+ state: uploadThrottle,
253
265
  });
254
266
  allowUpload = allowUpload && autoDecision.allowed;
255
267
  maxBatches = autoDecision.allowed ? autoDecision.maxBatches : 0;
@@ -261,18 +273,19 @@ async function cmdSync(argv) {
261
273
  retryAtMs: autoDecision.blockedUntilMs,
262
274
  reason,
263
275
  pendingBytes,
264
- source: 'auto-throttled',
265
- autoRetryNoSpawn: runtime.autoRetryNoSpawn
276
+ source: "auto-throttled",
277
+ autoRetryNoSpawn: runtime.autoRetryNoSpawn,
266
278
  });
267
279
  }
268
280
  }
269
281
 
270
282
  if (progress?.enabled && pendingBytes > 0 && allowUpload) {
271
283
  const totalSize = queueSize + projectQueueSize;
272
- const totalOffset = Number(beforeState.offset || 0) + Number(projectBeforeState.offset || 0);
284
+ const totalOffset =
285
+ Number(beforeState.offset || 0) + Number(projectBeforeState.offset || 0);
273
286
  const pct = totalSize > 0 ? totalOffset / totalSize : 0;
274
287
  progress.start(
275
- `Uploading ${renderBar(pct)} ${formatBytes(totalOffset)}/${formatBytes(totalSize)} | inserted 0 skipped 0`
288
+ `Uploading ${renderBar(pct)} ${formatBytes(totalOffset)}/${formatBytes(totalSize)} | inserted 0 skipped 0`,
276
289
  );
277
290
  }
278
291
 
@@ -282,7 +295,7 @@ async function cmdSync(argv) {
282
295
  home,
283
296
  env: process.env,
284
297
  probeKeychain: true,
285
- probeKeychainDetails: true
298
+ probeKeychainDetails: true,
286
299
  });
287
300
  try {
288
301
  uploadResult = await drainQueueToCloud({
@@ -300,10 +313,10 @@ async function cmdSync(argv) {
300
313
  const pct = u.queueSize > 0 ? u.offset / u.queueSize : 1;
301
314
  progress.update(
302
315
  `Uploading ${renderBar(pct)} ${formatBytes(u.offset)}/${formatBytes(u.queueSize)} | inserted ${formatNumber(
303
- u.inserted
304
- )} skipped ${formatNumber(u.skipped)}`
316
+ u.inserted,
317
+ )} skipped ${formatNumber(u.skipped)}`,
305
318
  );
306
- }
319
+ },
307
320
  });
308
321
  if (uploadResult.attempted > 0) {
309
322
  const next = recordUploadSuccess({ nowMs: Date.now(), state: uploadThrottleState });
@@ -311,7 +324,11 @@ async function cmdSync(argv) {
311
324
  await writeJson(uploadThrottlePath, next);
312
325
  }
313
326
  } catch (e) {
314
- const next = recordUploadFailure({ nowMs: Date.now(), state: uploadThrottleState, error: e });
327
+ const next = recordUploadFailure({
328
+ nowMs: Date.now(),
329
+ state: uploadThrottleState,
330
+ error: e,
331
+ });
315
332
  uploadThrottleState = next;
316
333
  await writeJson(uploadThrottlePath, next);
317
334
  if (opts.auto && pendingBytes > 0) {
@@ -320,10 +337,10 @@ async function cmdSync(argv) {
320
337
  await scheduleAutoRetry({
321
338
  trackerDir,
322
339
  retryAtMs,
323
- reason: 'backoff',
340
+ reason: "backoff",
324
341
  pendingBytes,
325
- source: 'auto-error',
326
- autoRetryNoSpawn: runtime.autoRetryNoSpawn
342
+ source: "auto-error",
343
+ autoRetryNoSpawn: runtime.autoRetryNoSpawn,
327
344
  });
328
345
  }
329
346
  }
@@ -352,10 +369,10 @@ async function cmdSync(argv) {
352
369
  await scheduleAutoRetry({
353
370
  trackerDir,
354
371
  retryAtMs,
355
- reason: 'backlog',
372
+ reason: "backlog",
356
373
  pendingBytes,
357
- source: 'auto-backlog',
358
- autoRetryNoSpawn: runtime.autoRetryNoSpawn
374
+ source: "auto-backlog",
375
+ autoRetryNoSpawn: runtime.autoRetryNoSpawn,
359
376
  });
360
377
  }
361
378
  }
@@ -365,7 +382,7 @@ async function cmdSync(argv) {
365
382
  deviceToken,
366
383
  trackerDir,
367
384
  uploadResult,
368
- pendingBytes
385
+ pendingBytes,
369
386
  });
370
387
 
371
388
  if (!opts.auto) {
@@ -383,19 +400,19 @@ async function cmdSync(argv) {
383
400
  opencodeResult.bucketsQueued;
384
401
  process.stdout.write(
385
402
  [
386
- 'Sync finished:',
403
+ "Sync finished:",
387
404
  `- Parsed files: ${totalParsed}`,
388
405
  `- New 30-min buckets queued: ${totalBuckets}`,
389
406
  deviceToken
390
407
  ? `- Uploaded: ${uploadResult.inserted} inserted, ${uploadResult.skipped} skipped`
391
- : '- Uploaded: skipped (no device token)',
408
+ : "- Uploaded: skipped (no device token)",
392
409
  deviceToken && pendingBytes > 0 && !opts.drain
393
410
  ? `- Remaining: ${formatBytes(pendingBytes)} pending (run sync again, or use --drain)`
394
411
  : null,
395
- ''
412
+ "",
396
413
  ]
397
414
  .filter(Boolean)
398
- .join('\n')
415
+ .join("\n"),
399
416
  );
400
417
  }
401
418
  } finally {
@@ -411,15 +428,15 @@ function parseArgs(argv) {
411
428
  fromNotify: false,
412
429
  fromRetry: false,
413
430
  fromOpenclaw: false,
414
- drain: false
431
+ drain: false,
415
432
  };
416
433
  for (let i = 0; i < argv.length; i++) {
417
434
  const a = argv[i];
418
- if (a === '--auto') out.auto = true;
419
- else if (a === '--from-notify') out.fromNotify = true;
420
- else if (a === '--from-retry') out.fromRetry = true;
421
- else if (a === '--from-openclaw') out.fromOpenclaw = true;
422
- else if (a === '--drain') out.drain = true;
435
+ if (a === "--auto") out.auto = true;
436
+ else if (a === "--from-notify") out.fromNotify = true;
437
+ else if (a === "--from-retry") out.fromRetry = true;
438
+ else if (a === "--from-openclaw") out.fromOpenclaw = true;
439
+ else if (a === "--drain") out.drain = true;
423
440
  else throw new Error(`Unknown option: ${a}`);
424
441
  }
425
442
  return out;
@@ -428,7 +445,7 @@ function parseArgs(argv) {
428
445
  module.exports = { cmdSync };
429
446
 
430
447
  function normalizeString(value) {
431
- if (typeof value !== 'string') return null;
448
+ if (typeof value !== "string") return null;
432
449
  const trimmed = value.trim();
433
450
  return trimmed.length > 0 ? trimmed : null;
434
451
  }
@@ -440,15 +457,16 @@ function resolveOpenclawSignal({ home, env } = {}) {
440
457
  const sessionId = normalizeString(env.VIBEUSAGE_OPENCLAW_PREV_SESSION_ID);
441
458
  if (!agentId || !sessionId) return null;
442
459
 
443
- const openclawHome = normalizeString(env.VIBEUSAGE_OPENCLAW_HOME) || path.join(home || os.homedir(), '.openclaw');
444
- const sessionFile = path.join(openclawHome, 'agents', agentId, 'sessions', `${sessionId}.jsonl`);
460
+ const openclawHome =
461
+ normalizeString(env.VIBEUSAGE_OPENCLAW_HOME) || path.join(home || os.homedir(), ".openclaw");
462
+ const sessionFile = path.join(openclawHome, "agents", agentId, "sessions", `${sessionId}.jsonl`);
445
463
 
446
464
  const prevTotals = {
447
465
  totalTokens: normalizeNonNegativeInt(env.VIBEUSAGE_OPENCLAW_PREV_TOTAL_TOKENS),
448
466
  inputTokens: normalizeNonNegativeInt(env.VIBEUSAGE_OPENCLAW_PREV_INPUT_TOKENS),
449
467
  outputTokens: normalizeNonNegativeInt(env.VIBEUSAGE_OPENCLAW_PREV_OUTPUT_TOKENS),
450
468
  model: normalizeString(env.VIBEUSAGE_OPENCLAW_PREV_MODEL),
451
- updatedAt: normalizeIsoOrEpoch(env.VIBEUSAGE_OPENCLAW_PREV_UPDATED_AT)
469
+ updatedAt: normalizeIsoOrEpoch(env.VIBEUSAGE_OPENCLAW_PREV_UPDATED_AT),
452
470
  };
453
471
 
454
472
  return {
@@ -457,40 +475,55 @@ function resolveOpenclawSignal({ home, env } = {}) {
457
475
  sessionKey: normalizeString(env.VIBEUSAGE_OPENCLAW_SESSION_KEY),
458
476
  openclawHome,
459
477
  sessionFile,
460
- prevTotals
478
+ prevTotals,
461
479
  };
462
480
  }
463
481
 
464
-
465
- async function applyOpenclawTotalsFallback({ trackerDir, signal, cursors, queuePath, projectQueuePath }) {
482
+ async function applyOpenclawTotalsFallback({
483
+ trackerDir,
484
+ signal,
485
+ cursors,
486
+ queuePath,
487
+ projectQueuePath,
488
+ }) {
466
489
  const totalTokens = Number(signal?.prevTotals?.totalTokens || 0);
467
490
  if (!trackerDir || !signal || totalTokens <= 0) {
468
491
  return { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
469
492
  }
470
493
 
471
494
  const sessionKey = `${signal.agentId}:${signal.sessionId}`;
472
- const statePath = path.join(trackerDir, 'openclaw.fallback.state.json');
473
- const fallbackFilePath = path.join(trackerDir, 'openclaw.fallback.jsonl');
495
+ const statePath = path.join(trackerDir, "openclaw.fallback.state.json");
496
+ const fallbackFilePath = path.join(trackerDir, "openclaw.fallback.jsonl");
474
497
  const state = (await readJson(statePath)) || { version: 1, sessions: {} };
475
- const sessions = state.sessions && typeof state.sessions === 'object' ? state.sessions : {};
476
- const prev = sessions[sessionKey] && typeof sessions[sessionKey] === 'object' ? sessions[sessionKey] : null;
498
+ const sessions = state.sessions && typeof state.sessions === "object" ? state.sessions : {};
499
+ const prev =
500
+ sessions[sessionKey] && typeof sessions[sessionKey] === "object" ? sessions[sessionKey] : null;
477
501
 
478
502
  const current = {
479
503
  totalTokens: normalizeNonNegativeInt(signal?.prevTotals?.totalTokens) || 0,
480
504
  inputTokens: normalizeNonNegativeInt(signal?.prevTotals?.inputTokens) || 0,
481
505
  outputTokens: normalizeNonNegativeInt(signal?.prevTotals?.outputTokens) || 0,
482
- model: normalizeString(signal?.prevTotals?.model) || 'unknown',
506
+ model: normalizeString(signal?.prevTotals?.model) || "unknown",
483
507
  updatedAt: normalizeIsoOrEpoch(signal?.prevTotals?.updatedAt) || new Date().toISOString(),
484
- seenAt: new Date().toISOString()
508
+ seenAt: new Date().toISOString(),
485
509
  };
486
510
 
487
511
  let deltaTotal = current.totalTokens;
488
512
  let deltaInput = current.inputTokens;
489
513
  let deltaOutput = current.outputTokens;
490
514
  if (prev) {
491
- deltaTotal = Math.max(0, current.totalTokens - (normalizeNonNegativeInt(prev.totalTokens) || 0));
492
- deltaInput = Math.max(0, current.inputTokens - (normalizeNonNegativeInt(prev.inputTokens) || 0));
493
- deltaOutput = Math.max(0, current.outputTokens - (normalizeNonNegativeInt(prev.outputTokens) || 0));
515
+ deltaTotal = Math.max(
516
+ 0,
517
+ current.totalTokens - (normalizeNonNegativeInt(prev.totalTokens) || 0),
518
+ );
519
+ deltaInput = Math.max(
520
+ 0,
521
+ current.inputTokens - (normalizeNonNegativeInt(prev.inputTokens) || 0),
522
+ );
523
+ deltaOutput = Math.max(
524
+ 0,
525
+ current.outputTokens - (normalizeNonNegativeInt(prev.outputTokens) || 0),
526
+ );
494
527
  }
495
528
 
496
529
  if (deltaTotal > 0 && deltaInput + deltaOutput === 0) {
@@ -508,29 +541,29 @@ async function applyOpenclawTotalsFallback({ trackerDir, signal, cursors, queueP
508
541
 
509
542
  await ensureDir(path.dirname(fallbackFilePath));
510
543
  const syntheticMessage = {
511
- type: 'message',
544
+ type: "message",
512
545
  timestamp: current.updatedAt,
513
546
  message: {
514
- role: 'assistant',
547
+ role: "assistant",
515
548
  model: current.model,
516
549
  usage: {
517
550
  input: deltaInput,
518
551
  output: deltaOutput,
519
552
  cacheRead: 0,
520
553
  cacheWrite: 0,
521
- totalTokens: deltaTotal
522
- }
523
- }
554
+ totalTokens: deltaTotal,
555
+ },
556
+ },
524
557
  };
525
- await fs.appendFile(fallbackFilePath, `${JSON.stringify(syntheticMessage)}\n`, 'utf8');
558
+ await fs.appendFile(fallbackFilePath, `${JSON.stringify(syntheticMessage)}\n`, "utf8");
526
559
  await writeJson(statePath, state);
527
560
 
528
561
  return parseOpenclawIncremental({
529
- sessionFiles: [{ path: fallbackFilePath, source: 'openclaw' }],
562
+ sessionFiles: [{ path: fallbackFilePath, source: "openclaw" }],
530
563
  cursors,
531
564
  queuePath,
532
565
  projectQueuePath,
533
- source: 'openclaw'
566
+ source: "openclaw",
534
567
  });
535
568
  }
536
569
 
@@ -541,7 +574,7 @@ function normalizeNonNegativeInt(value) {
541
574
  }
542
575
 
543
576
  function normalizeIsoOrEpoch(value) {
544
- if (typeof value === 'string') {
577
+ if (typeof value === "string") {
545
578
  const trimmed = value.trim();
546
579
  if (trimmed.length > 0 && !Number.isNaN(Date.parse(trimmed))) return trimmed;
547
580
  const numeric = Number(trimmed);
@@ -569,14 +602,20 @@ async function safeStatSize(p) {
569
602
  }
570
603
  }
571
604
 
572
- async function maybeSendHeartbeat({ baseUrl, deviceToken, trackerDir, uploadResult, pendingBytes }) {
605
+ async function maybeSendHeartbeat({
606
+ baseUrl,
607
+ deviceToken,
608
+ trackerDir,
609
+ uploadResult,
610
+ pendingBytes,
611
+ }) {
573
612
  if (!deviceToken || !uploadResult) return;
574
613
  if (pendingBytes > 0) return;
575
614
  if (Number(uploadResult.inserted || 0) !== 0) return;
576
615
 
577
- const heartbeatPath = path.join(trackerDir, 'sync.heartbeat.json');
616
+ const heartbeatPath = path.join(trackerDir, "sync.heartbeat.json");
578
617
  const heartbeatState = await readJson(heartbeatPath);
579
- const lastPingAt = Date.parse(heartbeatState?.lastPingAt || '');
618
+ const lastPingAt = Date.parse(heartbeatState?.lastPingAt || "");
580
619
  const nowMs = Date.now();
581
620
  if (Number.isFinite(lastPingAt) && nowMs - lastPingAt < HEARTBEAT_MIN_INTERVAL_MS) return;
582
621
 
@@ -584,7 +623,7 @@ async function maybeSendHeartbeat({ baseUrl, deviceToken, trackerDir, uploadResu
584
623
  await syncHeartbeat({ baseUrl, deviceToken });
585
624
  await writeJson(heartbeatPath, {
586
625
  lastPingAt: new Date(nowMs).toISOString(),
587
- minIntervalMinutes: HEARTBEAT_MIN_INTERVAL_MINUTES
626
+ minIntervalMinutes: HEARTBEAT_MIN_INTERVAL_MINUTES,
588
627
  });
589
628
  } catch (_e) {
590
629
  // best-effort heartbeat; ignore failures
@@ -592,14 +631,21 @@ async function maybeSendHeartbeat({ baseUrl, deviceToken, trackerDir, uploadResu
592
631
  }
593
632
 
594
633
  function deriveAutoSkipReason({ decision, state }) {
595
- if (!decision || decision.reason !== 'throttled') return decision?.reason || 'unknown';
634
+ if (!decision || decision.reason !== "throttled") return decision?.reason || "unknown";
596
635
  const backoffUntilMs = Number(state?.backoffUntilMs || 0);
597
636
  const nextAllowedAtMs = Number(state?.nextAllowedAtMs || 0);
598
- if (backoffUntilMs > 0 && backoffUntilMs >= nextAllowedAtMs) return 'backoff';
599
- return 'throttled';
637
+ if (backoffUntilMs > 0 && backoffUntilMs >= nextAllowedAtMs) return "backoff";
638
+ return "throttled";
600
639
  }
601
640
 
602
- async function scheduleAutoRetry({ trackerDir, retryAtMs, reason, pendingBytes, source, autoRetryNoSpawn }) {
641
+ async function scheduleAutoRetry({
642
+ trackerDir,
643
+ retryAtMs,
644
+ reason,
645
+ pendingBytes,
646
+ source,
647
+ autoRetryNoSpawn,
648
+ }) {
603
649
  const retryMs = coerceRetryMs(retryAtMs);
604
650
  if (!retryMs) return { scheduled: false, retryAtMs: 0 };
605
651
 
@@ -615,10 +661,10 @@ async function scheduleAutoRetry({ trackerDir, retryAtMs, reason, pendingBytes,
615
661
  version: 1,
616
662
  retryAtMs: retryMs,
617
663
  retryAt: new Date(retryMs).toISOString(),
618
- reason: typeof reason === 'string' && reason.length > 0 ? reason : 'throttled',
664
+ reason: typeof reason === "string" && reason.length > 0 ? reason : "throttled",
619
665
  pendingBytes: Math.max(0, Number(pendingBytes || 0)),
620
666
  scheduledAt: new Date(nowMs).toISOString(),
621
- source: typeof source === 'string' ? source : 'auto'
667
+ source: typeof source === "string" ? source : "auto",
622
668
  };
623
669
 
624
670
  await writeJson(retryPath, payload);
@@ -631,9 +677,9 @@ async function scheduleAutoRetry({ trackerDir, retryAtMs, reason, pendingBytes,
631
677
 
632
678
  spawnAutoRetryProcess({
633
679
  retryPath,
634
- trackerBinPath: path.join(trackerDir, 'app', 'bin', 'tracker.js'),
635
- fallbackPkg: 'vibeusage',
636
- delayMs
680
+ trackerBinPath: path.join(trackerDir, "app", "bin", "tracker.js"),
681
+ fallbackPkg: "vibeusage",
682
+ delayMs,
637
683
  });
638
684
  return { scheduled: true, retryAtMs: retryMs };
639
685
  }
@@ -646,17 +692,18 @@ async function clearAutoRetry(trackerDir) {
646
692
  function spawnAutoRetryProcess({ retryPath, trackerBinPath, fallbackPkg, delayMs }) {
647
693
  const script = buildAutoRetryScript({ retryPath, trackerBinPath, fallbackPkg, delayMs });
648
694
  try {
649
- const child = cp.spawn(process.execPath, ['-e', script], {
695
+ const child = cp.spawn(process.execPath, ["-e", script], {
650
696
  detached: true,
651
- stdio: 'ignore',
652
- env: process.env
697
+ stdio: "ignore",
698
+ env: process.env,
653
699
  });
654
700
  child.unref();
655
701
  } catch (_e) {}
656
702
  }
657
703
 
658
704
  function buildAutoRetryScript({ retryPath, trackerBinPath, fallbackPkg, delayMs }) {
659
- return `'use strict';\n` +
705
+ return (
706
+ `'use strict';\n` +
660
707
  `const fs = require('node:fs');\n` +
661
708
  `const cp = require('node:child_process');\n` +
662
709
  `const retryPath = ${JSON.stringify(retryPath)};\n` +
@@ -678,7 +725,8 @@ function buildAutoRetryScript({ retryPath, trackerBinPath, fallbackPkg, delayMs
678
725
  ` const child = cp.spawn(cmd[0], cmd.slice(1), { detached: true, stdio: 'ignore', env: process.env });\n` +
679
726
  ` child.unref();\n` +
680
727
  ` } catch (_) {}\n` +
681
- `}, delayMs);\n`;
728
+ `}, delayMs);\n`
729
+ );
682
730
  }
683
731
 
684
732
  function coerceRetryMs(v) {
@@ -688,9 +736,9 @@ function coerceRetryMs(v) {
688
736
  }
689
737
 
690
738
  async function writeOpenclawSignal(trackerDir) {
691
- const openclawSignalPath = path.join(trackerDir, 'openclaw.signal');
739
+ const openclawSignalPath = path.join(trackerDir, "openclaw.signal");
692
740
  try {
693
- await fs.writeFile(openclawSignalPath, new Date().toISOString(), 'utf8');
741
+ await fs.writeFile(openclawSignalPath, new Date().toISOString(), "utf8");
694
742
  } catch (_e) {
695
743
  // best-effort marker
696
744
  }
@@ -698,5 +746,5 @@ async function writeOpenclawSignal(trackerDir) {
698
746
 
699
747
  const HEARTBEAT_MIN_INTERVAL_MINUTES = 30;
700
748
  const HEARTBEAT_MIN_INTERVAL_MS = HEARTBEAT_MIN_INTERVAL_MINUTES * 60 * 1000;
701
- const AUTO_RETRY_FILENAME = 'auto.retry.json';
749
+ const AUTO_RETRY_FILENAME = "auto.retry.json";
702
750
  const AUTO_RETRY_MAX_DELAY_MS = 2 * 60 * 60 * 1000;