vibeusage 0.2.21 → 0.2.22

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 +41 -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 +182 -130
  12. package/src/commands/uninstall.js +66 -62
  13. package/src/lib/activation-check.js +290 -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,12 +1,13 @@
1
- const fs = require('node:fs/promises');
2
- const path = require('node:path');
1
+ const fs = require("node:fs/promises");
2
+ const path = require("node:path");
3
3
 
4
- const { ensureDir, readJson, writeJson } = require('./fs');
4
+ const { ensureDir, readJson, writeJson } = require("./fs");
5
5
 
6
6
  async function upsertNotify({ configPath, notifyCmd, notifyOriginalPath, configLabel }) {
7
- const originalText = await fs.readFile(configPath, 'utf8').catch(() => null);
7
+ const originalText = await fs.readFile(configPath, "utf8").catch(() => null);
8
8
  if (originalText == null) {
9
- const label = typeof configLabel === 'string' && configLabel.length > 0 ? configLabel : 'Config';
9
+ const label =
10
+ typeof configLabel === "string" && configLabel.length > 0 ? configLabel : "Config";
10
11
  throw new Error(`${label} not found: ${configPath}`);
11
12
  }
12
13
 
@@ -19,14 +20,17 @@ async function upsertNotify({ configPath, notifyCmd, notifyOriginalPath, configL
19
20
  await ensureDir(path.dirname(notifyOriginalPath));
20
21
  const existing = await readJson(notifyOriginalPath);
21
22
  if (!existing) {
22
- await writeJson(notifyOriginalPath, { notify: existingNotify, capturedAt: new Date().toISOString() });
23
+ await writeJson(notifyOriginalPath, {
24
+ notify: existingNotify,
25
+ capturedAt: new Date().toISOString(),
26
+ });
23
27
  }
24
28
  }
25
29
 
26
30
  const updated = setNotify(originalText, notifyCmd);
27
- const backupPath = `${configPath}.bak.${new Date().toISOString().replace(/[:.]/g, '-')}`;
31
+ const backupPath = `${configPath}.bak.${new Date().toISOString().replace(/[:.]/g, "-")}`;
28
32
  await fs.copyFile(configPath, backupPath);
29
- await fs.writeFile(configPath, updated, 'utf8');
33
+ await fs.writeFile(configPath, updated, "utf8");
30
34
  return { changed: true, backupPath };
31
35
  }
32
36
 
@@ -34,23 +38,23 @@ async function upsertNotify({ configPath, notifyCmd, notifyOriginalPath, configL
34
38
  }
35
39
 
36
40
  async function restoreNotify({ configPath, notifyOriginalPath, expectedNotify }) {
37
- const text = await fs.readFile(configPath, 'utf8').catch(() => null);
38
- if (text == null) return { restored: false, skippedReason: 'config-missing' };
41
+ const text = await fs.readFile(configPath, "utf8").catch(() => null);
42
+ if (text == null) return { restored: false, skippedReason: "config-missing" };
39
43
 
40
44
  const original = await readJson(notifyOriginalPath);
41
45
  const originalNotify = Array.isArray(original?.notify) ? original.notify : null;
42
46
  const currentNotify = extractNotify(text);
43
47
 
44
48
  if (!originalNotify && expectedNotify && !arraysEqual(currentNotify, expectedNotify)) {
45
- return { restored: false, skippedReason: 'no-backup-not-installed' };
49
+ return { restored: false, skippedReason: "no-backup-not-installed" };
46
50
  }
47
51
 
48
52
  const updated = originalNotify ? setNotify(text, originalNotify) : removeNotify(text);
49
- if (updated === text) return { restored: false, skippedReason: 'no-change' };
53
+ if (updated === text) return { restored: false, skippedReason: "no-change" };
50
54
 
51
- const backupPath = `${configPath}.bak.${new Date().toISOString().replace(/[:.]/g, '-')}`;
55
+ const backupPath = `${configPath}.bak.${new Date().toISOString().replace(/[:.]/g, "-")}`;
52
56
  await fs.copyFile(configPath, backupPath).catch(() => {});
53
- await fs.writeFile(configPath, updated, 'utf8');
57
+ await fs.writeFile(configPath, updated, "utf8");
54
58
  return { restored: true, skippedReason: null };
55
59
  }
56
60
 
@@ -60,7 +64,7 @@ async function loadNotifyOriginal(notifyOriginalPath) {
60
64
  }
61
65
 
62
66
  async function readNotify(configPath) {
63
- const text = await fs.readFile(configPath, 'utf8').catch(() => null);
67
+ const text = await fs.readFile(configPath, "utf8").catch(() => null);
64
68
  if (text == null) return null;
65
69
  return extractNotify(text);
66
70
  }
@@ -70,7 +74,7 @@ async function upsertCodexNotify({ codexConfigPath, notifyCmd, notifyOriginalPat
70
74
  configPath: codexConfigPath,
71
75
  notifyCmd,
72
76
  notifyOriginalPath,
73
- configLabel: 'Codex config'
77
+ configLabel: "Codex config",
74
78
  });
75
79
  }
76
80
 
@@ -78,7 +82,7 @@ async function restoreCodexNotify({ codexConfigPath, notifyOriginalPath, notifyC
78
82
  return restoreNotify({
79
83
  configPath: codexConfigPath,
80
84
  notifyOriginalPath,
81
- expectedNotify: notifyCmd
85
+ expectedNotify: notifyCmd,
82
86
  });
83
87
  }
84
88
 
@@ -95,7 +99,7 @@ async function upsertEveryCodeNotify({ codeConfigPath, notifyCmd, notifyOriginal
95
99
  configPath: codeConfigPath,
96
100
  notifyCmd,
97
101
  notifyOriginalPath,
98
- configLabel: 'Every Code config'
102
+ configLabel: "Every Code config",
99
103
  });
100
104
  }
101
105
 
@@ -103,7 +107,7 @@ async function restoreEveryCodeNotify({ codeConfigPath, notifyOriginalPath, noti
103
107
  return restoreNotify({
104
108
  configPath: codeConfigPath,
105
109
  notifyOriginalPath,
106
- expectedNotify: notifyCmd
110
+ expectedNotify: notifyCmd,
107
111
  });
108
112
  }
109
113
 
@@ -130,7 +134,7 @@ function extractNotify(text) {
130
134
  const m = line.match(/^\s*notify\s*=\s*(.*)\s*$/);
131
135
  if (!m) continue;
132
136
 
133
- const rhs = (m[1] || '').trim();
137
+ const rhs = (m[1] || "").trim();
134
138
  const literal = readTomlArrayLiteral(lines, i, rhs);
135
139
  if (!literal) continue;
136
140
 
@@ -155,7 +159,7 @@ function setNotify(text, notifyCmd) {
155
159
  replaced = true;
156
160
  }
157
161
 
158
- const rhs = (m[1] || '').trim();
162
+ const rhs = (m[1] || "").trim();
159
163
  i = findTomlArrayBlockEnd(lines, i, rhs);
160
164
  continue;
161
165
  }
@@ -169,7 +173,7 @@ function setNotify(text, notifyCmd) {
169
173
  out.splice(headerIdx, 0, notifyLine);
170
174
  }
171
175
 
172
- return out.join('\n').replace(/\n+$/, '\n');
176
+ return out.join("\n").replace(/\n+$/, "\n");
173
177
  }
174
178
 
175
179
  function removeNotify(text) {
@@ -179,24 +183,24 @@ function removeNotify(text) {
179
183
  const line = lines[i];
180
184
  const m = line.match(/^\s*notify\s*=\s*(.*)\s*$/);
181
185
  if (m) {
182
- const rhs = (m[1] || '').trim();
186
+ const rhs = (m[1] || "").trim();
183
187
  i = findTomlArrayBlockEnd(lines, i, rhs);
184
188
  continue;
185
189
  }
186
190
  out.push(line);
187
191
  }
188
- return out.join('\n').replace(/\n+$/, '\n');
192
+ return out.join("\n").replace(/\n+$/, "\n");
189
193
  }
190
194
 
191
195
  function parseTomlStringArray(rhs) {
192
196
  // Minimal parser for ["a", "b"] string arrays.
193
197
  // Assumes there are no escapes in strings (good enough for our usage).
194
- if (!rhs.startsWith('[') || !rhs.endsWith(']')) return null;
198
+ if (!rhs.startsWith("[") || !rhs.endsWith("]")) return null;
195
199
  const inner = rhs.slice(1, -1).trim();
196
200
  if (!inner) return [];
197
201
 
198
202
  const parts = [];
199
- let current = '';
203
+ let current = "";
200
204
  let inString = false;
201
205
  let quote = null;
202
206
  for (let i = 0; i < inner.length; i++) {
@@ -205,7 +209,7 @@ function parseTomlStringArray(rhs) {
205
209
  if (ch === '"' || ch === "'") {
206
210
  inString = true;
207
211
  quote = ch;
208
- current = '';
212
+ current = "";
209
213
  }
210
214
  continue;
211
215
  }
@@ -222,12 +226,12 @@ function parseTomlStringArray(rhs) {
222
226
  }
223
227
 
224
228
  function formatTomlStringArray(arr) {
225
- return `[${arr.map((s) => JSON.stringify(String(s))).join(', ')}]`;
229
+ return `[${arr.map((s) => JSON.stringify(String(s))).join(", ")}]`;
226
230
  }
227
231
 
228
232
  function readTomlArrayLiteral(lines, startIndex, rhs) {
229
233
  const first = rhs.trim();
230
- if (!first.startsWith('[')) return null;
234
+ if (!first.startsWith("[")) return null;
231
235
 
232
236
  let inString = false;
233
237
  let quote = null;
@@ -243,12 +247,12 @@ function readTomlArrayLiteral(lines, startIndex, rhs) {
243
247
  quote = ch;
244
248
  continue;
245
249
  }
246
- if (ch === '[') {
250
+ if (ch === "[") {
247
251
  depth += 1;
248
252
  sawOpen = true;
249
253
  continue;
250
254
  }
251
- if (ch === ']') {
255
+ if (ch === "]") {
252
256
  depth -= 1;
253
257
  if (sawOpen && depth === 0) return i;
254
258
  }
@@ -271,7 +275,7 @@ function readTomlArrayLiteral(lines, startIndex, rhs) {
271
275
  endPos = scanChunk(line);
272
276
  if (endPos !== -1) {
273
277
  parts.push(line.slice(0, endPos + 1));
274
- return parts.join('\n').trim();
278
+ return parts.join("\n").trim();
275
279
  }
276
280
  parts.push(line);
277
281
  }
@@ -281,7 +285,7 @@ function readTomlArrayLiteral(lines, startIndex, rhs) {
281
285
 
282
286
  function findTomlArrayBlockEnd(lines, startIndex, rhs) {
283
287
  const first = rhs.trim();
284
- if (!first.startsWith('[')) return startIndex;
288
+ if (!first.startsWith("[")) return startIndex;
285
289
 
286
290
  let inString = false;
287
291
  let quote = null;
@@ -297,12 +301,12 @@ function findTomlArrayBlockEnd(lines, startIndex, rhs) {
297
301
  quote = ch;
298
302
  continue;
299
303
  }
300
- if (ch === '[') {
304
+ if (ch === "[") {
301
305
  depth += 1;
302
306
  sawOpen = true;
303
307
  continue;
304
308
  }
305
- if (ch === ']') {
309
+ if (ch === "]") {
306
310
  depth -= 1;
307
311
  if (sawOpen && depth === 0) return true;
308
312
  }
@@ -342,5 +346,5 @@ module.exports = {
342
346
  upsertEveryCodeNotify,
343
347
  restoreEveryCodeNotify,
344
348
  loadEveryCodeNotifyOriginal,
345
- readEveryCodeNotify
349
+ readEveryCodeNotify,
346
350
  };
@@ -1,6 +1,6 @@
1
1
  function stripDebugFlag(argv, env = process.env) {
2
- const filtered = Array.isArray(argv) ? argv.filter((arg) => arg !== '--debug') : [];
3
- const debugEnv = String(env?.VIBEUSAGE_DEBUG || '') === '1';
2
+ const filtered = Array.isArray(argv) ? argv.filter((arg) => arg !== "--debug") : [];
3
+ const debugEnv = String(env?.VIBEUSAGE_DEBUG || "") === "1";
4
4
  return { argv: filtered, debug: filtered.length !== (argv || []).length || debugEnv };
5
5
  }
6
6
 
@@ -1,40 +1,40 @@
1
- const os = require('node:os');
2
- const path = require('node:path');
3
- const fs = require('node:fs/promises');
1
+ const os = require("node:os");
2
+ const path = require("node:path");
3
+ const fs = require("node:fs/promises");
4
4
 
5
- const { readJson } = require('./fs');
6
- const { readCodexNotify, readEveryCodeNotify } = require('./codex-config');
7
- const { isClaudeHookConfigured, buildClaudeHookCommand } = require('./claude-config');
5
+ const { readJson } = require("./fs");
6
+ const { readCodexNotify, readEveryCodeNotify } = require("./codex-config");
7
+ const { isClaudeHookConfigured, buildClaudeHookCommand } = require("./claude-config");
8
8
  const {
9
9
  resolveGeminiConfigDir,
10
10
  resolveGeminiSettingsPath,
11
11
  buildGeminiHookCommand,
12
- isGeminiHookConfigured
13
- } = require('./gemini-config');
14
- const { resolveOpencodeConfigDir, isOpencodePluginInstalled } = require('./opencode-config');
15
- const { normalizeState: normalizeUploadState } = require('./upload-throttle');
16
- const { probeOpenclawHookState } = require('./openclaw-hook');
17
- const { probeOpenclawSessionPluginState } = require('./openclaw-session-plugin');
18
- const { resolveTrackerPaths } = require('./tracker-paths');
12
+ isGeminiHookConfigured,
13
+ } = require("./gemini-config");
14
+ const { resolveOpencodeConfigDir, isOpencodePluginInstalled } = require("./opencode-config");
15
+ const { normalizeState: normalizeUploadState } = require("./upload-throttle");
16
+ const { probeOpenclawHookState } = require("./openclaw-hook");
17
+ const { probeOpenclawSessionPluginState } = require("./openclaw-session-plugin");
18
+ const { resolveTrackerPaths } = require("./tracker-paths");
19
19
 
20
20
  async function collectTrackerDiagnostics({
21
21
  home = os.homedir(),
22
- codexHome = process.env.CODEX_HOME || path.join(home, '.codex'),
23
- codeHome = process.env.CODE_HOME || path.join(home, '.code')
22
+ codexHome = process.env.CODEX_HOME || path.join(home, ".codex"),
23
+ codeHome = process.env.CODE_HOME || path.join(home, ".code"),
24
24
  } = {}) {
25
25
  const { trackerDir, binDir } = await resolveTrackerPaths({ home });
26
- const configPath = path.join(trackerDir, 'config.json');
27
- const queuePath = path.join(trackerDir, 'queue.jsonl');
28
- const queueStatePath = path.join(trackerDir, 'queue.state.json');
29
- const cursorsPath = path.join(trackerDir, 'cursors.json');
30
- const notifySignalPath = path.join(trackerDir, 'notify.signal');
31
- const openclawSignalPath = path.join(trackerDir, 'openclaw.signal');
32
- const throttlePath = path.join(trackerDir, 'sync.throttle');
33
- const uploadThrottlePath = path.join(trackerDir, 'upload.throttle.json');
34
- const autoRetryPath = path.join(trackerDir, 'auto.retry.json');
35
- const codexConfigPath = path.join(codexHome, 'config.toml');
36
- const codeConfigPath = path.join(codeHome, 'config.toml');
37
- const claudeConfigPath = path.join(home, '.claude', 'settings.json');
26
+ const configPath = path.join(trackerDir, "config.json");
27
+ const queuePath = path.join(trackerDir, "queue.jsonl");
28
+ const queueStatePath = path.join(trackerDir, "queue.state.json");
29
+ const cursorsPath = path.join(trackerDir, "cursors.json");
30
+ const notifySignalPath = path.join(trackerDir, "notify.signal");
31
+ const openclawSignalPath = path.join(trackerDir, "openclaw.signal");
32
+ const throttlePath = path.join(trackerDir, "sync.throttle");
33
+ const uploadThrottlePath = path.join(trackerDir, "upload.throttle.json");
34
+ const autoRetryPath = path.join(trackerDir, "auto.retry.json");
35
+ const codexConfigPath = path.join(codexHome, "config.toml");
36
+ const codeConfigPath = path.join(codeHome, "config.toml");
37
+ const claudeConfigPath = path.join(home, ".claude", "settings.json");
38
38
  const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
39
39
  const geminiSettingsPath = resolveGeminiSettingsPath({ configDir: geminiConfigDir });
40
40
  const opencodeConfigDir = resolveOpencodeConfigDir({ home, env: process.env });
@@ -58,22 +58,32 @@ async function collectTrackerDiagnostics({
58
58
  const codexNotify = notifyConfigured ? codexNotifyRaw.map((v) => redactValue(v, home)) : null;
59
59
  const everyCodeNotifyRaw = await readEveryCodeNotify(codeConfigPath);
60
60
  const everyCodeConfigured = Array.isArray(everyCodeNotifyRaw) && everyCodeNotifyRaw.length > 0;
61
- const everyCodeNotify = everyCodeConfigured ? everyCodeNotifyRaw.map((v) => redactValue(v, home)) : null;
62
- const claudeHookCommand = buildClaudeHookCommand(path.join(binDir, 'notify.cjs'));
61
+ const everyCodeNotify = everyCodeConfigured
62
+ ? everyCodeNotifyRaw.map((v) => redactValue(v, home))
63
+ : null;
64
+ const claudeHookCommand = buildClaudeHookCommand(path.join(binDir, "notify.cjs"));
63
65
  const claudeHookConfigured = await isClaudeHookConfigured({
64
66
  settingsPath: claudeConfigPath,
65
- hookCommand: claudeHookCommand
67
+ hookCommand: claudeHookCommand,
66
68
  });
67
- const geminiHookCommand = buildGeminiHookCommand(path.join(binDir, 'notify.cjs'));
69
+ const geminiHookCommand = buildGeminiHookCommand(path.join(binDir, "notify.cjs"));
68
70
  const geminiHookConfigured = await isGeminiHookConfigured({
69
71
  settingsPath: geminiSettingsPath,
70
- hookCommand: geminiHookCommand
72
+ hookCommand: geminiHookCommand,
73
+ });
74
+ const opencodePluginConfigured = await isOpencodePluginInstalled({
75
+ configDir: opencodeConfigDir,
76
+ });
77
+ const openclawSessionPluginState = await probeOpenclawSessionPluginState({
78
+ home,
79
+ trackerDir,
80
+ env: process.env,
71
81
  });
72
- const opencodePluginConfigured = await isOpencodePluginInstalled({ configDir: opencodeConfigDir });
73
- const openclawSessionPluginState = await probeOpenclawSessionPluginState({ home, trackerDir, env: process.env });
74
82
  const openclawHookState = await probeOpenclawHookState({ home, trackerDir, env: process.env });
75
83
 
76
- const lastSuccessAt = uploadThrottle.lastSuccessMs ? new Date(uploadThrottle.lastSuccessMs).toISOString() : null;
84
+ const lastSuccessAt = uploadThrottle.lastSuccessMs
85
+ ? new Date(uploadThrottle.lastSuccessMs).toISOString()
86
+ : null;
77
87
  const autoRetryAt = parseEpochMsToIso(autoRetry?.retryAtMs);
78
88
 
79
89
  return {
@@ -83,7 +93,7 @@ async function collectTrackerDiagnostics({
83
93
  env: {
84
94
  node: process.version,
85
95
  platform: process.platform,
86
- arch: process.arch
96
+ arch: process.arch,
87
97
  },
88
98
  paths: {
89
99
  tracker_dir: redactValue(trackerDir, home),
@@ -93,23 +103,26 @@ async function collectTrackerDiagnostics({
93
103
  code_config: redactValue(codeConfigPath, home),
94
104
  claude_config: redactValue(claudeConfigPath, home),
95
105
  gemini_config: redactValue(geminiSettingsPath, home),
96
- opencode_config: redactValue(opencodeConfigDir, home)
106
+ opencode_config: redactValue(opencodeConfigDir, home),
97
107
  },
98
108
  config: {
99
- base_url: typeof config?.baseUrl === 'string' ? config.baseUrl : null,
100
- device_token: config?.deviceToken ? 'set' : 'unset',
109
+ base_url: typeof config?.baseUrl === "string" ? config.baseUrl : null,
110
+ device_token: config?.deviceToken ? "set" : "unset",
101
111
  device_id: maskId(config?.deviceId),
102
- installed_at: typeof config?.installedAt === 'string' ? config.installedAt : null
112
+ installed_at: typeof config?.installedAt === "string" ? config.installedAt : null,
103
113
  },
104
114
  parse: {
105
- updated_at: typeof cursors?.updatedAt === 'string' ? cursors.updatedAt : null,
106
- file_count: cursors?.files && typeof cursors.files === 'object' ? Object.keys(cursors.files).length : null
115
+ updated_at: typeof cursors?.updatedAt === "string" ? cursors.updatedAt : null,
116
+ file_count:
117
+ cursors?.files && typeof cursors.files === "object"
118
+ ? Object.keys(cursors.files).length
119
+ : null,
107
120
  },
108
121
  queue: {
109
122
  size_bytes: queueSize,
110
123
  offset_bytes: offsetBytes,
111
124
  pending_bytes: pendingBytes,
112
- updated_at: typeof queueState.updatedAt === 'string' ? queueState.updatedAt : null
125
+ updated_at: typeof queueState.updatedAt === "string" ? queueState.updatedAt : null,
113
126
  },
114
127
  notify: {
115
128
  last_notify: lastNotify,
@@ -127,7 +140,7 @@ async function collectTrackerDiagnostics({
127
140
  openclaw_session_plugin_enabled: Boolean(openclawSessionPluginState?.enabled),
128
141
  openclaw_hook_configured: Boolean(openclawHookState?.configured),
129
142
  openclaw_hook_linked: Boolean(openclawHookState?.linked),
130
- openclaw_hook_enabled: Boolean(openclawHookState?.enabled)
143
+ openclaw_hook_enabled: Boolean(openclawHookState?.enabled),
131
144
  },
132
145
  upload: {
133
146
  last_success_at: lastSuccessAt,
@@ -136,43 +149,43 @@ async function collectTrackerDiagnostics({
136
149
  last_error: uploadThrottle.lastError
137
150
  ? {
138
151
  at: uploadThrottle.lastErrorAt || null,
139
- message: redactError(String(uploadThrottle.lastError), home)
152
+ message: redactError(String(uploadThrottle.lastError), home),
140
153
  }
141
- : null
154
+ : null,
142
155
  },
143
156
  auto_retry: autoRetryAt
144
157
  ? {
145
158
  next_retry_at: autoRetryAt,
146
- reason: typeof autoRetry?.reason === 'string' ? autoRetry.reason : null,
159
+ reason: typeof autoRetry?.reason === "string" ? autoRetry.reason : null,
147
160
  pending_bytes: Number.isFinite(Number(autoRetry?.pendingBytes))
148
161
  ? Math.max(0, Number(autoRetry.pendingBytes))
149
162
  : null,
150
- scheduled_at: typeof autoRetry?.scheduledAt === 'string' ? autoRetry.scheduledAt : null,
151
- source: typeof autoRetry?.source === 'string' ? autoRetry.source : null
163
+ scheduled_at: typeof autoRetry?.scheduledAt === "string" ? autoRetry.scheduledAt : null,
164
+ source: typeof autoRetry?.source === "string" ? autoRetry.source : null,
152
165
  }
153
- : null
166
+ : null,
154
167
  };
155
168
  }
156
169
 
157
170
  function maskId(v) {
158
- if (typeof v !== 'string') return null;
171
+ if (typeof v !== "string") return null;
159
172
  const s = v.trim();
160
173
  if (s.length < 12) return null;
161
174
  return `${s.slice(0, 8)}…${s.slice(-4)}`;
162
175
  }
163
176
 
164
177
  function redactValue(value, home) {
165
- if (typeof value !== 'string') return value;
166
- if (typeof home !== 'string' || home.length === 0) return value;
178
+ if (typeof value !== "string") return value;
179
+ if (typeof home !== "string" || home.length === 0) return value;
167
180
  const homeNorm = home.endsWith(path.sep) ? home.slice(0, -1) : home;
168
181
  return value.startsWith(homeNorm) ? `~${value.slice(homeNorm.length)}` : value;
169
182
  }
170
183
 
171
184
  function redactError(message, home) {
172
- if (typeof message !== 'string') return message;
173
- if (typeof home !== 'string' || home.length === 0) return message;
185
+ if (typeof message !== "string") return message;
186
+ if (typeof home !== "string" || home.length === 0) return message;
174
187
  const homeNorm = home.endsWith(path.sep) ? home.slice(0, -1) : home;
175
- return message.split(homeNorm).join('~');
188
+ return message.split(homeNorm).join("~");
176
189
  }
177
190
 
178
191
  async function safeStatSize(p) {
@@ -186,7 +199,7 @@ async function safeStatSize(p) {
186
199
 
187
200
  async function safeReadText(p) {
188
201
  try {
189
- return await fs.readFile(p, 'utf8');
202
+ return await fs.readFile(p, "utf8");
190
203
  } catch (_e) {
191
204
  return null;
192
205
  }