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,23 +1,35 @@
1
- const DEFAULT_BASE_URL = 'https://5tmappuk.us-east.insforge.app';
2
- const DEFAULT_DASHBOARD_URL = 'https://www.vibeusage.cc';
1
+ const DEFAULT_BASE_URL = "https://5tmappuk.us-east.insforge.app";
2
+ const DEFAULT_DASHBOARD_URL = "https://www.vibeusage.cc";
3
3
  const DEFAULT_HTTP_TIMEOUT_MS = 20_000;
4
4
 
5
5
  function resolveRuntimeConfig({ cli = {}, config = {}, env = process.env, defaults = {} } = {}) {
6
- const baseUrl = pickString(cli.baseUrl, config.baseUrl, env?.VIBEUSAGE_INSFORGE_BASE_URL, defaults.baseUrl, DEFAULT_BASE_URL);
6
+ const baseUrl = pickString(
7
+ cli.baseUrl,
8
+ config.baseUrl,
9
+ env?.VIBEUSAGE_INSFORGE_BASE_URL,
10
+ defaults.baseUrl,
11
+ DEFAULT_BASE_URL,
12
+ );
7
13
  const dashboardUrl = pickString(
8
14
  cli.dashboardUrl,
9
15
  config.dashboardUrl,
10
16
  env?.VIBEUSAGE_DASHBOARD_URL,
11
17
  defaults.dashboardUrl,
12
- DEFAULT_DASHBOARD_URL
18
+ DEFAULT_DASHBOARD_URL,
19
+ );
20
+ const deviceToken = pickString(
21
+ cli.deviceToken,
22
+ config.deviceToken,
23
+ env?.VIBEUSAGE_DEVICE_TOKEN,
24
+ defaults.deviceToken,
25
+ null,
13
26
  );
14
- const deviceToken = pickString(cli.deviceToken, config.deviceToken, env?.VIBEUSAGE_DEVICE_TOKEN, defaults.deviceToken, null);
15
27
  const httpTimeoutMs = pickHttpTimeoutMs(
16
28
  cli.httpTimeoutMs,
17
29
  config.httpTimeoutMs,
18
30
  env?.VIBEUSAGE_HTTP_TIMEOUT_MS,
19
31
  defaults.httpTimeoutMs,
20
- DEFAULT_HTTP_TIMEOUT_MS
32
+ DEFAULT_HTTP_TIMEOUT_MS,
21
33
  );
22
34
  const debug = pickBoolean(cli.debug, config.debug, env?.VIBEUSAGE_DEBUG, defaults.debug, false);
23
35
  const insforgeAnonKey = pickString(
@@ -25,15 +37,15 @@ function resolveRuntimeConfig({ cli = {}, config = {}, env = process.env, defaul
25
37
  config.insforgeAnonKey,
26
38
  env?.VIBEUSAGE_INSFORGE_ANON_KEY,
27
39
  defaults.insforgeAnonKey,
28
- ''
40
+ "",
29
41
  );
30
- if (insforgeAnonKey.value == null) insforgeAnonKey.value = '';
42
+ if (insforgeAnonKey.value == null) insforgeAnonKey.value = "";
31
43
  const autoRetryNoSpawn = pickBoolean(
32
44
  cli.autoRetryNoSpawn,
33
45
  config.autoRetryNoSpawn,
34
46
  env?.VIBEUSAGE_AUTO_RETRY_NO_SPAWN,
35
47
  defaults.autoRetryNoSpawn,
36
- false
48
+ false,
37
49
  );
38
50
 
39
51
  return {
@@ -51,8 +63,8 @@ function resolveRuntimeConfig({ cli = {}, config = {}, env = process.env, defaul
51
63
  httpTimeoutMs: httpTimeoutMs.source,
52
64
  debug: debug.source,
53
65
  insforgeAnonKey: insforgeAnonKey.source,
54
- autoRetryNoSpawn: autoRetryNoSpawn.source
55
- }
66
+ autoRetryNoSpawn: autoRetryNoSpawn.source,
67
+ },
56
68
  };
57
69
  }
58
70
 
@@ -69,37 +81,37 @@ function pickHttpTimeoutMs(...candidates) {
69
81
  }
70
82
 
71
83
  function pickValue(candidates, normalize) {
72
- const labels = ['cli', 'config', 'env', 'default', 'default'];
84
+ const labels = ["cli", "config", "env", "default", "default"];
73
85
  for (let i = 0; i < candidates.length; i += 1) {
74
86
  const value = normalize(candidates[i]);
75
87
  if (value !== undefined) {
76
- return { value, source: labels[i] || 'default' };
88
+ return { value, source: labels[i] || "default" };
77
89
  }
78
90
  }
79
- return { value: null, source: 'default' };
91
+ return { value: null, source: "default" };
80
92
  }
81
93
 
82
94
  function normalizeString(value) {
83
- if (typeof value !== 'string') return undefined;
95
+ if (typeof value !== "string") return undefined;
84
96
  const trimmed = value.trim();
85
97
  if (!trimmed) return undefined;
86
98
  return trimmed;
87
99
  }
88
100
 
89
101
  function normalizeBoolean(value) {
90
- if (typeof value === 'boolean') return value;
91
- if (typeof value === 'number') return value !== 0;
92
- if (typeof value === 'string') {
102
+ if (typeof value === "boolean") return value;
103
+ if (typeof value === "number") return value !== 0;
104
+ if (typeof value === "string") {
93
105
  const trimmed = value.trim().toLowerCase();
94
106
  if (!trimmed) return undefined;
95
- if (trimmed === '1' || trimmed === 'true') return true;
96
- if (trimmed === '0' || trimmed === 'false') return false;
107
+ if (trimmed === "1" || trimmed === "true") return true;
108
+ if (trimmed === "0" || trimmed === "false") return false;
97
109
  }
98
110
  return undefined;
99
111
  }
100
112
 
101
113
  function normalizeHttpTimeoutMs(value) {
102
- if (value == null || value === '') return undefined;
114
+ if (value == null || value === "") return undefined;
103
115
  const n = Number(value);
104
116
  if (!Number.isFinite(n)) return undefined;
105
117
  if (n <= 0) return 0;
@@ -116,5 +128,5 @@ module.exports = {
116
128
  DEFAULT_BASE_URL,
117
129
  DEFAULT_DASHBOARD_URL,
118
130
  DEFAULT_HTTP_TIMEOUT_MS,
119
- resolveRuntimeConfig
131
+ resolveRuntimeConfig,
120
132
  };
@@ -1,38 +1,40 @@
1
- const os = require('node:os');
2
- const path = require('node:path');
3
- const fs = require('node:fs');
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");
4
+ const cp = require("node:child_process");
5
5
 
6
- const { readJson } = require('./fs');
6
+ const { readJson } = require("./fs");
7
+ const { resolveTrackerPaths } = require("./tracker-paths");
8
+ const { probeOpenclawSessionPluginState } = require("./openclaw-session-plugin");
7
9
 
8
- const OPENAI_AUTH_CLAIM = 'https://api.openai.com/auth';
9
- const MACOS_SECURITY_BIN = '/usr/bin/security';
10
- const CLAUDE_CODE_KEYCHAIN_SERVICES = ['Claude Code-credentials'];
10
+ const OPENAI_AUTH_CLAIM = "https://api.openai.com/auth";
11
+ const MACOS_SECURITY_BIN = "/usr/bin/security";
12
+ const CLAUDE_CODE_KEYCHAIN_SERVICES = ["Claude Code-credentials"];
11
13
 
12
14
  function normalizeString(value) {
13
- if (typeof value !== 'string') return null;
15
+ if (typeof value !== "string") return null;
14
16
  const trimmed = value.trim();
15
17
  return trimmed.length > 0 ? trimmed : null;
16
18
  }
17
19
 
18
20
  function normalizeScalarToString(value) {
19
21
  if (value === null || value === undefined) return null;
20
- if (typeof value === 'string') return normalizeString(value);
21
- if (typeof value === 'number') {
22
+ if (typeof value === "string") return normalizeString(value);
23
+ if (typeof value === "number") {
22
24
  if (!Number.isFinite(value)) return null;
23
25
  return normalizeString(String(value));
24
26
  }
25
- if (typeof value === 'boolean') return value ? 'true' : 'false';
27
+ if (typeof value === "boolean") return value ? "true" : "false";
26
28
  return null;
27
29
  }
28
30
 
29
31
  function base64UrlDecodeToString(value) {
30
- if (typeof value !== 'string' || value.length === 0) return null;
32
+ if (typeof value !== "string" || value.length === 0) return null;
31
33
  const padLen = (4 - (value.length % 4)) % 4;
32
- const padded = value + '='.repeat(padLen);
33
- const base64 = padded.replace(/-/g, '+').replace(/_/g, '/');
34
+ const padded = value + "=".repeat(padLen);
35
+ const base64 = padded.replace(/-/g, "+").replace(/_/g, "/");
34
36
  try {
35
- return Buffer.from(base64, 'base64').toString('utf8');
37
+ return Buffer.from(base64, "base64").toString("utf8");
36
38
  } catch (_e) {
37
39
  return null;
38
40
  }
@@ -41,7 +43,7 @@ function base64UrlDecodeToString(value) {
41
43
  function decodeJwtPayload(token) {
42
44
  const jwt = normalizeString(token);
43
45
  if (!jwt) return null;
44
- const parts = jwt.split('.');
46
+ const parts = jwt.split(".");
45
47
  if (parts.length < 2) return null;
46
48
  const decoded = base64UrlDecodeToString(parts[1]);
47
49
  if (!decoded) return null;
@@ -53,9 +55,9 @@ function decodeJwtPayload(token) {
53
55
  }
54
56
 
55
57
  function extractOpenAiAuthNamespace(payload) {
56
- if (!payload || typeof payload !== 'object') return null;
58
+ if (!payload || typeof payload !== "object") return null;
57
59
  const ns = payload[OPENAI_AUTH_CLAIM];
58
- if (!ns || typeof ns !== 'object' || Array.isArray(ns)) return null;
60
+ if (!ns || typeof ns !== "object" || Array.isArray(ns)) return null;
59
61
  return ns;
60
62
  }
61
63
 
@@ -80,7 +82,7 @@ function mergeSubscription(primary, secondary) {
80
82
  planType: a.planType || b.planType || null,
81
83
  activeStart: a.activeStart || b.activeStart || null,
82
84
  activeUntil: a.activeUntil || b.activeUntil || null,
83
- lastChecked: a.lastChecked || b.lastChecked || null
85
+ lastChecked: a.lastChecked || b.lastChecked || null,
84
86
  };
85
87
  }
86
88
 
@@ -88,26 +90,26 @@ function isDisplayablePlanType(planType) {
88
90
  const normalized = normalizeString(planType);
89
91
  if (!normalized) return false;
90
92
  const v = normalized.toLowerCase();
91
- if (v === 'free' || v === 'none' || v === 'unknown') return false;
93
+ if (v === "free" || v === "none" || v === "unknown") return false;
92
94
  return true;
93
95
  }
94
96
 
95
97
  function resolveCodexHome({ home, env }) {
96
98
  const explicit = normalizeString(env?.CODEX_HOME);
97
- return explicit ? path.resolve(explicit) : path.join(home, '.codex');
99
+ return explicit ? path.resolve(explicit) : path.join(home, ".codex");
98
100
  }
99
101
 
100
102
  function resolveOpencodeDataDir({ home, env }) {
101
103
  const explicit = normalizeString(env?.XDG_DATA_HOME);
102
- const base = explicit ? path.resolve(explicit) : path.join(home, '.local', 'share');
103
- return path.join(base, 'opencode');
104
+ const base = explicit ? path.resolve(explicit) : path.join(home, ".local", "share");
105
+ return path.join(base, "opencode");
104
106
  }
105
107
 
106
108
  async function detectCodexChatgptSubscription({ home, env }) {
107
109
  const codexHome = resolveCodexHome({ home, env });
108
- const authPath = path.join(codexHome, 'auth.json');
110
+ const authPath = path.join(codexHome, "auth.json");
109
111
  const auth = await readJson(authPath);
110
- if (!auth || typeof auth !== 'object') return null;
112
+ if (!auth || typeof auth !== "object") return null;
111
113
 
112
114
  const accessPayload = decodeJwtPayload(auth?.tokens?.access_token);
113
115
  const idPayload = decodeJwtPayload(auth?.tokens?.id_token);
@@ -118,114 +120,95 @@ async function detectCodexChatgptSubscription({ home, env }) {
118
120
  if (!merged || !isDisplayablePlanType(merged.planType)) return null;
119
121
 
120
122
  return {
121
- tool: 'codex',
122
- provider: 'openai',
123
- product: 'chatgpt',
123
+ tool: "codex",
124
+ provider: "openai",
125
+ product: "chatgpt",
124
126
  planType: merged.planType,
125
127
  activeStart: merged.activeStart,
126
128
  activeUntil: merged.activeUntil,
127
- lastChecked: merged.lastChecked
129
+ lastChecked: merged.lastChecked,
128
130
  };
129
131
  }
130
132
 
131
133
  async function detectOpencodeChatgptSubscription({ home, env }) {
132
134
  const dataDir = resolveOpencodeDataDir({ home, env });
133
- const authPath = path.join(dataDir, 'auth.json');
135
+ const authPath = path.join(dataDir, "auth.json");
134
136
  const auth = await readJson(authPath);
135
- if (!auth || typeof auth !== 'object') return null;
137
+ if (!auth || typeof auth !== "object") return null;
136
138
 
137
139
  const accessPayload = decodeJwtPayload(auth?.openai?.access);
138
140
  const info = extractChatgptSubscriptionFromPayload(accessPayload);
139
141
  if (!info || !isDisplayablePlanType(info.planType)) return null;
140
142
 
141
143
  return {
142
- tool: 'opencode',
143
- provider: 'openai',
144
- product: 'chatgpt',
144
+ tool: "opencode",
145
+ provider: "openai",
146
+ product: "chatgpt",
145
147
  planType: info.planType,
146
148
  activeStart: info.activeStart,
147
149
  activeUntil: info.activeUntil,
148
- lastChecked: info.lastChecked
150
+ lastChecked: info.lastChecked,
149
151
  };
150
152
  }
151
153
 
152
- function probeMacosKeychainGenericPassword({
153
- service,
154
- securityRunner,
155
- timeoutMs
156
- } = {}) {
154
+ function probeMacosKeychainGenericPassword({ service, securityRunner, timeoutMs } = {}) {
157
155
  const svc = normalizeString(service);
158
156
  if (!svc) return false;
159
157
 
160
- const runner = typeof securityRunner === 'function' ? securityRunner : cp.spawnSync;
158
+ const runner = typeof securityRunner === "function" ? securityRunner : cp.spawnSync;
161
159
  if (runner === cp.spawnSync && !fs.existsSync(MACOS_SECURITY_BIN)) return false;
162
160
 
163
- const result = runner(
164
- MACOS_SECURITY_BIN,
165
- ['find-generic-password', '-s', svc],
166
- {
167
- stdio: 'ignore',
168
- timeout: Number.isFinite(timeoutMs) ? timeoutMs : 2000
169
- }
170
- );
161
+ const result = runner(MACOS_SECURITY_BIN, ["find-generic-password", "-s", svc], {
162
+ stdio: "ignore",
163
+ timeout: Number.isFinite(timeoutMs) ? timeoutMs : 2000,
164
+ });
171
165
 
172
166
  if (!result || result.error) return false;
173
167
  return result.status === 0;
174
168
  }
175
169
 
176
- function readMacosKeychainPassword({
177
- service,
178
- securityRunner,
179
- timeoutMs
180
- } = {}) {
170
+ function readMacosKeychainPassword({ service, securityRunner, timeoutMs } = {}) {
181
171
  const svc = normalizeString(service);
182
172
  if (!svc) return null;
183
173
 
184
- const runner = typeof securityRunner === 'function' ? securityRunner : cp.spawnSync;
174
+ const runner = typeof securityRunner === "function" ? securityRunner : cp.spawnSync;
185
175
  if (runner === cp.spawnSync && !fs.existsSync(MACOS_SECURITY_BIN)) return null;
186
176
 
187
- const result = runner(
188
- MACOS_SECURITY_BIN,
189
- ['find-generic-password', '-s', svc, '-w'],
190
- {
191
- stdio: ['ignore', 'pipe', 'ignore'],
192
- timeout: Number.isFinite(timeoutMs) ? timeoutMs : 2000,
193
- encoding: 'utf8'
194
- }
195
- );
177
+ const result = runner(MACOS_SECURITY_BIN, ["find-generic-password", "-s", svc, "-w"], {
178
+ stdio: ["ignore", "pipe", "ignore"],
179
+ timeout: Number.isFinite(timeoutMs) ? timeoutMs : 2000,
180
+ encoding: "utf8",
181
+ });
196
182
 
197
183
  if (!result || result.error) return null;
198
184
  if (result.status !== 0) return null;
199
185
 
200
186
  const stdout =
201
- typeof result.stdout === 'string'
187
+ typeof result.stdout === "string"
202
188
  ? result.stdout
203
189
  : Buffer.isBuffer(result.stdout)
204
- ? result.stdout.toString('utf8')
205
- : '';
190
+ ? result.stdout.toString("utf8")
191
+ : "";
206
192
  const trimmed = stdout.trim();
207
193
  return trimmed.length > 0 ? trimmed : null;
208
194
  }
209
195
 
210
- function detectClaudeCodeCredentialsPresence({
211
- platform,
212
- securityRunner
213
- } = {}) {
214
- if (platform !== 'darwin') return null;
196
+ function detectClaudeCodeCredentialsPresence({ platform, securityRunner } = {}) {
197
+ if (platform !== "darwin") return null;
215
198
 
216
199
  for (const service of CLAUDE_CODE_KEYCHAIN_SERVICES) {
217
200
  const present = probeMacosKeychainGenericPassword({
218
201
  service,
219
- securityRunner
202
+ securityRunner,
220
203
  });
221
204
  if (!present) continue;
222
205
 
223
206
  // Existence-only probe: do not read secrets or infer paid tier.
224
207
  return {
225
- tool: 'claude',
226
- provider: 'anthropic',
227
- product: 'credentials',
228
- planType: 'present'
208
+ tool: "claude",
209
+ provider: "anthropic",
210
+ product: "credentials",
211
+ planType: "present",
229
212
  };
230
213
  }
231
214
 
@@ -233,10 +216,10 @@ function detectClaudeCodeCredentialsPresence({
233
216
  }
234
217
 
235
218
  function extractClaudeKeychainSubscription(payload) {
236
- if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return null;
219
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
237
220
 
238
221
  const oauth = payload.claudeAiOauth;
239
- if (!oauth || typeof oauth !== 'object' || Array.isArray(oauth)) return null;
222
+ if (!oauth || typeof oauth !== "object" || Array.isArray(oauth)) return null;
240
223
 
241
224
  const subscriptionType = normalizeScalarToString(oauth.subscriptionType);
242
225
  const rateLimitTier = normalizeScalarToString(oauth.rateLimitTier);
@@ -245,16 +228,13 @@ function extractClaudeKeychainSubscription(payload) {
245
228
  return { subscriptionType, rateLimitTier };
246
229
  }
247
230
 
248
- function detectClaudeCodeSubscriptionDetails({
249
- platform,
250
- securityRunner
251
- } = {}) {
252
- if (platform !== 'darwin') return null;
231
+ function detectClaudeCodeSubscriptionDetails({ platform, securityRunner } = {}) {
232
+ if (platform !== "darwin") return null;
253
233
 
254
234
  for (const service of CLAUDE_CODE_KEYCHAIN_SERVICES) {
255
235
  const raw = readMacosKeychainPassword({
256
236
  service,
257
- securityRunner
237
+ securityRunner,
258
238
  });
259
239
  if (!raw) continue;
260
240
 
@@ -269,11 +249,11 @@ function detectClaudeCodeSubscriptionDetails({
269
249
  if (!info) continue;
270
250
 
271
251
  return {
272
- tool: 'claude',
273
- provider: 'anthropic',
274
- product: 'subscription',
252
+ tool: "claude",
253
+ provider: "anthropic",
254
+ product: "subscription",
275
255
  planType: info.subscriptionType,
276
- rateLimitTier: info.rateLimitTier
256
+ rateLimitTier: info.rateLimitTier,
277
257
  };
278
258
  }
279
259
 
@@ -286,7 +266,7 @@ async function collectLocalSubscriptions({
286
266
  platform = process.platform,
287
267
  securityRunner,
288
268
  probeKeychain = false,
289
- probeKeychainDetails = false
269
+ probeKeychainDetails = false,
290
270
  } = {}) {
291
271
  const out = [];
292
272
 
@@ -308,10 +288,32 @@ async function collectLocalSubscriptions({
308
288
  if (claude) out.push(claude);
309
289
  }
310
290
 
291
+ const openclaw = await detectOpenclawSessionIntegration({ home, env });
292
+ if (openclaw) out.push(openclaw);
293
+
311
294
  // Gemini: no stable local subscription/tier signal found yet.
312
295
  return out;
313
296
  }
314
297
 
298
+ async function detectOpenclawSessionIntegration({ home, env }) {
299
+ const { trackerDir } = await resolveTrackerPaths({ home });
300
+ let state = null;
301
+ try {
302
+ state = await probeOpenclawSessionPluginState({ home, trackerDir, env });
303
+ } catch (_err) {
304
+ return null;
305
+ }
306
+
307
+ if (!state?.configured) return null;
308
+
309
+ return {
310
+ tool: "openclaw",
311
+ provider: "openclaw",
312
+ product: "session_plugin",
313
+ planType: "enabled",
314
+ };
315
+ }
316
+
315
317
  module.exports = {
316
- collectLocalSubscriptions
318
+ collectLocalSubscriptions,
317
319
  };
@@ -1,15 +1,15 @@
1
- const os = require('node:os');
2
- const path = require('node:path');
1
+ const os = require("node:os");
2
+ const path = require("node:path");
3
3
 
4
4
  async function resolveTrackerPaths({ home = os.homedir() } = {}) {
5
- const rootDir = path.join(home, '.vibeusage');
5
+ const rootDir = path.join(home, ".vibeusage");
6
6
  return {
7
7
  rootDir,
8
- trackerDir: path.join(rootDir, 'tracker'),
9
- binDir: path.join(rootDir, 'bin')
8
+ trackerDir: path.join(rootDir, "tracker"),
9
+ binDir: path.join(rootDir, "bin"),
10
10
  };
11
11
  }
12
12
 
13
13
  module.exports = {
14
- resolveTrackerPaths
14
+ resolveTrackerPaths,
15
15
  };
@@ -6,20 +6,20 @@ const DEFAULTS = {
6
6
  maxBatchesSmall: 2,
7
7
  maxBatchesLarge: 4,
8
8
  backoffInitialMs: 60_000,
9
- backoffMaxMs: 30 * 60_000
9
+ backoffMaxMs: 30 * 60_000,
10
10
  };
11
11
 
12
12
  function normalizeState(raw) {
13
- const s = raw && typeof raw === 'object' ? raw : {};
13
+ const s = raw && typeof raw === "object" ? raw : {};
14
14
  return {
15
15
  version: 1,
16
16
  lastSuccessMs: toSafeInt(s.lastSuccessMs),
17
17
  nextAllowedAtMs: toSafeInt(s.nextAllowedAtMs),
18
18
  backoffUntilMs: toSafeInt(s.backoffUntilMs),
19
19
  backoffStep: toSafeInt(s.backoffStep),
20
- lastErrorAt: typeof s.lastErrorAt === 'string' ? s.lastErrorAt : null,
21
- lastError: typeof s.lastError === 'string' ? s.lastError : null,
22
- updatedAt: typeof s.updatedAt === 'string' ? s.updatedAt : null
20
+ lastErrorAt: typeof s.lastErrorAt === "string" ? s.lastErrorAt : null,
21
+ lastError: typeof s.lastError === "string" ? s.lastError : null,
22
+ updatedAt: typeof s.updatedAt === "string" ? s.updatedAt : null,
23
23
  };
24
24
  }
25
25
 
@@ -29,22 +29,41 @@ function decideAutoUpload({ nowMs, pendingBytes, state, config }) {
29
29
  const pending = Number(pendingBytes || 0);
30
30
 
31
31
  if (pending <= 0) {
32
- return { allowed: false, reason: 'no-pending', maxBatches: 0, batchSize: cfg.batchSize, blockedUntilMs: 0 };
32
+ return {
33
+ allowed: false,
34
+ reason: "no-pending",
35
+ maxBatches: 0,
36
+ batchSize: cfg.batchSize,
37
+ blockedUntilMs: 0,
38
+ };
33
39
  }
34
40
 
35
41
  const blockedUntilMs = Math.max(s.nextAllowedAtMs || 0, s.backoffUntilMs || 0);
36
42
  if (blockedUntilMs > 0 && nowMs < blockedUntilMs) {
37
- return { allowed: false, reason: 'throttled', maxBatches: 0, batchSize: cfg.batchSize, blockedUntilMs };
43
+ return {
44
+ allowed: false,
45
+ reason: "throttled",
46
+ maxBatches: 0,
47
+ batchSize: cfg.batchSize,
48
+ blockedUntilMs,
49
+ };
38
50
  }
39
51
 
40
52
  const maxBatches = pending >= cfg.backlogBytes ? cfg.maxBatchesLarge : cfg.maxBatchesSmall;
41
- return { allowed: true, reason: 'allowed', maxBatches, batchSize: cfg.batchSize, blockedUntilMs: 0 };
53
+ return {
54
+ allowed: true,
55
+ reason: "allowed",
56
+ maxBatches,
57
+ batchSize: cfg.batchSize,
58
+ blockedUntilMs: 0,
59
+ };
42
60
  }
43
61
 
44
62
  function recordUploadSuccess({ nowMs, state, config, randInt }) {
45
63
  const cfg = { ...DEFAULTS, ...(config || {}) };
46
64
  const s = normalizeState(state);
47
- const jitter = typeof randInt === 'function' ? randInt(0, cfg.jitterMsMax) : randomInt(0, cfg.jitterMsMax);
65
+ const jitter =
66
+ typeof randInt === "function" ? randInt(0, cfg.jitterMsMax) : randomInt(0, cfg.jitterMsMax);
48
67
  const nextAllowedAtMs = nowMs + cfg.intervalMs + jitter;
49
68
 
50
69
  return {
@@ -55,7 +74,7 @@ function recordUploadSuccess({ nowMs, state, config, randInt }) {
55
74
  backoffStep: 0,
56
75
  lastErrorAt: null,
57
76
  lastError: null,
58
- updatedAt: new Date(nowMs).toISOString()
77
+ updatedAt: new Date(nowMs).toISOString(),
59
78
  };
60
79
  }
61
80
 
@@ -83,13 +102,13 @@ function recordUploadFailure({ nowMs, state, error, config }) {
83
102
  backoffUntilMs,
84
103
  backoffStep: Math.min(20, (s.backoffStep || 0) + 1),
85
104
  lastErrorAt: new Date(nowMs).toISOString(),
86
- lastError: truncate(String(error?.message || 'upload failed'), 200),
87
- updatedAt: new Date(nowMs).toISOString()
105
+ lastError: truncate(String(error?.message || "upload failed"), 200),
106
+ updatedAt: new Date(nowMs).toISOString(),
88
107
  };
89
108
  }
90
109
 
91
110
  function parseRetryAfterMs(headerValue, nowMs = Date.now()) {
92
- if (typeof headerValue !== 'string' || headerValue.trim().length === 0) return null;
111
+ if (typeof headerValue !== "string" || headerValue.trim().length === 0) return null;
93
112
  const v = headerValue.trim();
94
113
  const seconds = Number(v);
95
114
  if (Number.isFinite(seconds) && seconds >= 0) return Math.floor(seconds * 1000);
@@ -114,9 +133,9 @@ function toSafeInt(v) {
114
133
  }
115
134
 
116
135
  function truncate(s, maxLen) {
117
- if (typeof s !== 'string') return '';
136
+ if (typeof s !== "string") return "";
118
137
  if (s.length <= maxLen) return s;
119
- return s.slice(0, maxLen - 1) + '';
138
+ return s.slice(0, maxLen - 1) + "";
120
139
  }
121
140
 
122
141
  module.exports = {
@@ -125,5 +144,5 @@ module.exports = {
125
144
  decideAutoUpload,
126
145
  recordUploadSuccess,
127
146
  recordUploadFailure,
128
- parseRetryAfterMs
147
+ parseRetryAfterMs,
129
148
  };