vibeusage 0.2.12 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- # 🟢 VIBESCORE
3
+ # 🟢 VIBEUSAGE
4
4
 
5
5
  **QUANTIFY YOUR AI OUTPUT**
6
6
  _Real-time AI Analytics for Codex CLI_
@@ -81,6 +81,19 @@ npx --yes vibeusage sync
81
81
  npx --yes vibeusage status
82
82
  ````
83
83
 
84
+ ### Doctor
85
+
86
+ ```bash
87
+ # Run health checks
88
+ npx --yes vibeusage doctor
89
+
90
+ # Emit JSON report (and write to file)
91
+ npx --yes vibeusage doctor --json --out doctor.json
92
+
93
+ # Override base URL (diagnostics only)
94
+ npx --yes vibeusage doctor --base-url https://example.invalid
95
+ ```
96
+
84
97
  ### Sources
85
98
 
86
99
  - Codex CLI logs: `~/.codex/sessions/**/rollout-*.jsonl` (override with `CODEX_HOME`)
@@ -89,10 +102,9 @@ npx --yes vibeusage status
89
102
 
90
103
  ## 🔧 Environment Variables
91
104
 
92
- - `VIBESCORE_HTTP_TIMEOUT_MS`: CLI HTTP timeout in ms (default `20000`, `0` disables, clamped to `1000..120000`).
93
- - `VITE_VIBESCORE_HTTP_TIMEOUT_MS`: Dashboard request timeout in ms (default `15000`, `0` disables, clamped to `1000..30000`).
105
+ - `VIBEUSAGE_HTTP_TIMEOUT_MS`: CLI HTTP timeout in ms (default `20000`, `0` disables, clamped to `1000..120000`).
106
+ - `VITE_VIBEUSAGE_HTTP_TIMEOUT_MS`: Dashboard request timeout in ms (default `15000`, `0` disables, clamped to `1000..30000`).
94
107
  - `VIBEUSAGE_ROLLUP_ENABLED`: Currently ignored; rollup aggregation is disabled in code until the daily rollup table is deployed.
95
- - `VIBESCORE_ROLLUP_ENABLED`: Legacy alias for `VIBEUSAGE_ROLLUP_ENABLED` (ignored).
96
108
  - `GEMINI_HOME`: Override Gemini CLI home (defaults to `~/.gemini`).
97
109
 
98
110
  ## 🧰 Troubleshooting
package/README.zh-CN.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- # 🟢 VIBESCORE
3
+ # 🟢 VIBEUSAGE
4
4
 
5
5
  **量化你的 AI 产出**
6
6
  _Codex CLI 实时 AI 分析工具_
@@ -81,6 +81,19 @@ npx --yes vibeusage sync
81
81
  npx --yes vibeusage status
82
82
  ````
83
83
 
84
+ ### Doctor
85
+
86
+ ```bash
87
+ # 运行健康检查
88
+ npx --yes vibeusage doctor
89
+
90
+ # 输出 JSON 报告(同时写文件)
91
+ npx --yes vibeusage doctor --json --out doctor.json
92
+
93
+ # 覆盖 base URL(仅诊断)
94
+ npx --yes vibeusage doctor --base-url https://example.invalid
95
+ ```
96
+
84
97
  ### 日志来源
85
98
 
86
99
  - Codex CLI 日志:`~/.codex/sessions/**/rollout-*.jsonl`(可用 `CODEX_HOME` 覆盖)
@@ -89,10 +102,9 @@ npx --yes vibeusage status
89
102
 
90
103
  ## 🔧 环境变量
91
104
 
92
- - `VIBESCORE_HTTP_TIMEOUT_MS`:CLI 请求超时(毫秒,默认 `20000`,`0` 表示关闭,范围 `1000..120000`)。
93
- - `VITE_VIBESCORE_HTTP_TIMEOUT_MS`:Dashboard 请求超时(毫秒,默认 `15000`,`0` 表示关闭,范围 `1000..30000`)。
105
+ - `VIBEUSAGE_HTTP_TIMEOUT_MS`:CLI 请求超时(毫秒,默认 `20000`,`0` 表示关闭,范围 `1000..120000`)。
106
+ - `VITE_VIBEUSAGE_HTTP_TIMEOUT_MS`:Dashboard 请求超时(毫秒,默认 `15000`,`0` 表示关闭,范围 `1000..30000`)。
94
107
  - `VIBEUSAGE_ROLLUP_ENABLED`:当前被忽略,rollup 聚合在代码层禁用,等待 rollup 表部署完成后再恢复。
95
- - `VIBESCORE_ROLLUP_ENABLED`:`VIBEUSAGE_ROLLUP_ENABLED` 的兼容别名(同样无效)。
96
108
  - `GEMINI_HOME`:覆盖 Gemini CLI 的 home(默认 `~/.gemini`)。
97
109
 
98
110
  ## 🧰 常见问题
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeusage",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "Codex CLI token usage tracker (macOS-first, notify-driven).",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -22,13 +22,13 @@
22
22
  "architecture:canvas": "node scripts/ops/architecture-canvas.cjs",
23
23
  "architecture:canvas:focus": "node scripts/ops/architecture-canvas.cjs --focus",
24
24
  "architecture:canvas:list-modules": "node scripts/ops/architecture-canvas.cjs --list-modules",
25
- "validate:guardrails": "node scripts/validate-architecture-guardrails.cjs"
25
+ "validate:guardrails": "node scripts/validate-architecture-guardrails.cjs",
26
+ "validate:insforge2-db": "node scripts/ops/insforge2-db-validate.cjs"
26
27
  },
27
28
  "bin": {
28
29
  "tracker": "bin/tracker.js",
29
30
  "vibeusage": "bin/tracker.js",
30
- "vibeusage-tracker": "bin/tracker.js",
31
- "vibescore-tracker": "bin/tracker.js"
31
+ "vibeusage-tracker": "bin/tracker.js"
32
32
  },
33
33
  "files": [
34
34
  "bin/",
package/src/cli.js CHANGED
@@ -2,6 +2,7 @@ const { cmdInit } = require('./commands/init');
2
2
  const { cmdSync } = require('./commands/sync');
3
3
  const { cmdStatus } = require('./commands/status');
4
4
  const { cmdDiagnostics } = require('./commands/diagnostics');
5
+ const { cmdDoctor } = require('./commands/doctor');
5
6
  const { cmdUninstall } = require('./commands/uninstall');
6
7
 
7
8
  async function run(argv) {
@@ -25,6 +26,9 @@ async function run(argv) {
25
26
  case 'diagnostics':
26
27
  await cmdDiagnostics(rest);
27
28
  return;
29
+ case 'doctor':
30
+ await cmdDoctor(rest);
31
+ return;
28
32
  case 'uninstall':
29
33
  await cmdUninstall(rest);
30
34
  return;
@@ -44,6 +48,7 @@ function printHelp() {
44
48
  ' npx vibeusage [--debug] sync [--auto] [--drain]',
45
49
  ' npx vibeusage [--debug] status',
46
50
  ' npx vibeusage [--debug] diagnostics [--out diagnostics.json]',
51
+ ' npx vibeusage [--debug] doctor [--json] [--out doctor.json] [--base-url <url>]',
47
52
  ' npx vibeusage [--debug] uninstall [--purge]',
48
53
  '',
49
54
  'Notes:',
@@ -0,0 +1,91 @@
1
+ const os = require('node:os');
2
+ const path = require('node:path');
3
+
4
+ const { readJsonStrict, writeFileAtomic, chmod600IfPossible } = require('../lib/fs');
5
+ const { resolveTrackerPaths } = require('../lib/tracker-paths');
6
+ const { collectTrackerDiagnostics } = require('../lib/diagnostics');
7
+ const { resolveRuntimeConfig } = require('../lib/runtime-config');
8
+ const { buildDoctorReport } = require('../lib/doctor');
9
+
10
+ async function cmdDoctor(argv = []) {
11
+ const opts = parseArgs(argv);
12
+ const home = os.homedir();
13
+ const { trackerDir } = await resolveTrackerPaths({ home });
14
+ const configPath = path.join(trackerDir, 'config.json');
15
+
16
+ const configStatus = await readJsonStrict(configPath);
17
+ const config = configStatus.status === 'ok' && isPlainObject(configStatus.value) ? configStatus.value : {};
18
+ const runtime = resolveRuntimeConfig({ cli: { baseUrl: opts.baseUrl }, config, env: process.env });
19
+ const diagnostics = await collectTrackerDiagnostics({ home });
20
+ const cliPath = process.argv[1] ? path.resolve(process.argv[1]) : null;
21
+
22
+ const report = await buildDoctorReport({
23
+ runtime,
24
+ diagnostics,
25
+ fetch: globalThis.fetch,
26
+ paths: {
27
+ trackerDir,
28
+ configPath,
29
+ cliPath
30
+ }
31
+ });
32
+
33
+ const jsonOutput = opts.json || Boolean(opts.out);
34
+ const payload = JSON.stringify(report, null, jsonOutput ? 2 : 0) + '\n';
35
+
36
+ if (opts.out) {
37
+ const outPath = path.resolve(process.cwd(), opts.out);
38
+ await writeFileAtomic(outPath, payload);
39
+ await chmod600IfPossible(outPath);
40
+ process.stderr.write(`Wrote doctor report to: ${outPath}\n`);
41
+ }
42
+
43
+ if (jsonOutput) {
44
+ process.stdout.write(payload);
45
+ } else {
46
+ process.stdout.write(renderHumanReport(report));
47
+ }
48
+
49
+ if (report.summary.critical > 0) {
50
+ process.exitCode = 1;
51
+ }
52
+ }
53
+
54
+ function parseArgs(argv) {
55
+ const out = { json: false, out: null, baseUrl: null };
56
+ for (let i = 0; i < argv.length; i += 1) {
57
+ const arg = argv[i];
58
+ if (arg === '--json') out.json = true;
59
+ else if (arg === '--out') out.out = argv[++i] || null;
60
+ else if (arg === '--base-url') out.baseUrl = argv[++i] || null;
61
+ else throw new Error(`Unknown option: ${arg}`);
62
+ }
63
+ return out;
64
+ }
65
+
66
+ function renderHumanReport(report) {
67
+ const lines = [];
68
+ lines.push('Doctor report');
69
+ lines.push('');
70
+ for (const check of report.checks || []) {
71
+ lines.push(formatCheckLine(check));
72
+ }
73
+ lines.push('');
74
+ lines.push(
75
+ `Summary: ok ${report.summary.ok} | warn ${report.summary.warn} | fail ${report.summary.fail} | critical ${report.summary.critical}`
76
+ );
77
+ lines.push('');
78
+ return lines.join('\n');
79
+ }
80
+
81
+ function formatCheckLine(check = {}) {
82
+ const status = String(check.status || 'unknown').toUpperCase();
83
+ const detail = check.detail ? ` - ${check.detail}` : '';
84
+ return `- [${status}] ${check.id || 'unknown'}${detail}`;
85
+ }
86
+
87
+ function isPlainObject(value) {
88
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
89
+ }
90
+
91
+ module.exports = { cmdDoctor };
@@ -29,6 +29,7 @@ const {
29
29
  issueDeviceTokenWithLinkCode
30
30
  } = require('../lib/insforge');
31
31
  const { resolveTrackerPaths } = require('../lib/tracker-paths');
32
+ const { resolveRuntimeConfig } = require('../lib/runtime-config');
32
33
  const {
33
34
  BOLD,
34
35
  DIM,
@@ -57,20 +58,20 @@ async function cmdInit(argv) {
57
58
  const opts = parseArgs(argv);
58
59
  const home = os.homedir();
59
60
 
60
- const { rootDir, trackerDir, binDir } = await resolveTrackerPaths({ home, migrate: true });
61
+ const { rootDir, trackerDir, binDir } = await resolveTrackerPaths({ home });
61
62
 
62
63
  const configPath = path.join(trackerDir, 'config.json');
63
64
  const notifyOriginalPath = path.join(trackerDir, 'codex_notify_original.json');
64
65
  const linkCodeStatePath = path.join(trackerDir, 'link_code_state.json');
65
66
 
66
- const baseUrl = opts.baseUrl ||
67
- process.env.VIBEUSAGE_INSFORGE_BASE_URL ||
68
- process.env.VIBESCORE_INSFORGE_BASE_URL ||
69
- 'https://5tmappuk.us-east.insforge.app';
70
- let dashboardUrl = opts.dashboardUrl ||
71
- process.env.VIBEUSAGE_DASHBOARD_URL ||
72
- process.env.VIBESCORE_DASHBOARD_URL ||
73
- DEFAULT_DASHBOARD_URL;
67
+ const existingConfig = await readJson(configPath);
68
+ const runtime = resolveRuntimeConfig({
69
+ cli: { baseUrl: opts.baseUrl, dashboardUrl: opts.dashboardUrl },
70
+ config: existingConfig || {},
71
+ env: process.env
72
+ });
73
+ const baseUrl = runtime.baseUrl;
74
+ let dashboardUrl = runtime.dashboardUrl || DEFAULT_DASHBOARD_URL;
74
75
  const notifyPath = path.join(binDir, 'notify.cjs');
75
76
  const appDir = path.join(trackerDir, 'app');
76
77
  const trackerBinPath = path.join(appDir, 'bin', 'tracker.js');
@@ -99,8 +100,8 @@ async function cmdInit(argv) {
99
100
  opts,
100
101
  home,
101
102
  trackerDir,
102
- configPath,
103
- notifyPath
103
+ notifyPath,
104
+ runtime
104
105
  });
105
106
  renderLocalReport({ summary: preview.summary, isDryRun: true });
106
107
  if (preview.pendingBrowserAuth) {
@@ -126,7 +127,9 @@ async function cmdInit(argv) {
126
127
  linkCodeStatePath,
127
128
  notifyPath,
128
129
  appDir,
129
- trackerBinPath
130
+ trackerBinPath,
131
+ runtime,
132
+ existingConfig
130
133
  });
131
134
  } catch (err) {
132
135
  spinner.stop();
@@ -208,10 +211,8 @@ function shouldUseBrowserAuth({ deviceToken, opts }) {
208
211
  return true;
209
212
  }
210
213
 
211
- async function buildDryRunSummary({ opts, home, trackerDir, configPath, notifyPath }) {
212
- const existingConfig = await readJson(configPath);
213
- const deviceTokenFromEnv = process.env.VIBEUSAGE_DEVICE_TOKEN || process.env.VIBESCORE_DEVICE_TOKEN || null;
214
- const deviceToken = deviceTokenFromEnv || existingConfig?.deviceToken || null;
214
+ async function buildDryRunSummary({ opts, home, trackerDir, notifyPath, runtime }) {
215
+ const deviceToken = runtime?.deviceToken || null;
215
216
  const pendingBrowserAuth = shouldUseBrowserAuth({ deviceToken, opts });
216
217
  const context = buildIntegrationTargets({ home, trackerDir, notifyPath });
217
218
  const summary = await previewIntegrations({ context });
@@ -229,15 +230,13 @@ async function runSetup({
229
230
  linkCodeStatePath,
230
231
  notifyPath,
231
232
  appDir,
232
- trackerBinPath
233
+ trackerBinPath,
234
+ runtime,
235
+ existingConfig
233
236
  }) {
234
237
  await ensureDir(trackerDir);
235
238
  await ensureDir(binDir);
236
-
237
- const existingConfig = await readJson(configPath);
238
- const deviceTokenFromEnv = process.env.VIBEUSAGE_DEVICE_TOKEN || process.env.VIBESCORE_DEVICE_TOKEN || null;
239
-
240
- let deviceToken = deviceTokenFromEnv || existingConfig?.deviceToken || null;
239
+ let deviceToken = runtime?.deviceToken || null;
241
240
  let deviceId = existingConfig?.deviceId || null;
242
241
  const installedAt = existingConfig?.installedAt || new Date().toISOString();
243
242
  let pendingBrowserAuth = false;
@@ -610,7 +609,7 @@ try {
610
609
  // Throttle spawn: at most once per 20 seconds.
611
610
  try {
612
611
  const throttlePath = path.join(trackerDir, 'sync.throttle');
613
- let deviceToken = process.env.VIBEUSAGE_DEVICE_TOKEN || process.env.VIBESCORE_DEVICE_TOKEN || null;
612
+ let deviceToken = process.env.VIBEUSAGE_DEVICE_TOKEN || null;
614
613
  if (!deviceToken) {
615
614
  try {
616
615
  const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
@@ -764,5 +763,5 @@ async function copyRuntimeDependencies({ from, to }) {
764
763
  }
765
764
 
766
765
  function isDebugEnabled() {
767
- return process.env.VIBEUSAGE_DEBUG === '1' || process.env.VIBESCORE_DEBUG === '1';
766
+ return process.env.VIBEUSAGE_DEBUG === '1';
768
767
  }
@@ -25,7 +25,7 @@ async function cmdStatus(argv = []) {
25
25
  }
26
26
 
27
27
  const home = os.homedir();
28
- const { trackerDir, binDir } = await resolveTrackerPaths({ home, migrate: true });
28
+ const { trackerDir, binDir } = await resolveTrackerPaths({ home });
29
29
  const configPath = path.join(trackerDir, 'config.json');
30
30
  const queuePath = path.join(trackerDir, 'queue.jsonl');
31
31
  const queueStatePath = path.join(trackerDir, 'queue.state.json');
@@ -16,7 +16,7 @@ const {
16
16
  } = require('../lib/rollout');
17
17
  const { drainQueueToCloud } = require('../lib/uploader');
18
18
  const { createProgress, renderBar, formatNumber, formatBytes } = require('../lib/progress');
19
- const { syncHeartbeat } = require('../lib/vibescore-api');
19
+ const { syncHeartbeat } = require('../lib/vibeusage-api');
20
20
  const {
21
21
  DEFAULTS: UPLOAD_DEFAULTS,
22
22
  normalizeState: normalizeUploadState,
@@ -25,11 +25,12 @@ const {
25
25
  recordUploadFailure
26
26
  } = require('../lib/upload-throttle');
27
27
  const { resolveTrackerPaths } = require('../lib/tracker-paths');
28
+ const { resolveRuntimeConfig } = require('../lib/runtime-config');
28
29
 
29
30
  async function cmdSync(argv) {
30
31
  const opts = parseArgs(argv);
31
32
  const home = os.homedir();
32
- const { trackerDir } = await resolveTrackerPaths({ home, migrate: true });
33
+ const { trackerDir } = await resolveTrackerPaths({ home });
33
34
 
34
35
  await ensureDir(trackerDir);
35
36
 
@@ -169,11 +170,9 @@ async function cmdSync(argv) {
169
170
 
170
171
  progress?.stop();
171
172
 
172
- const deviceToken = config?.deviceToken || process.env.VIBEUSAGE_DEVICE_TOKEN || process.env.VIBESCORE_DEVICE_TOKEN || null;
173
- const baseUrl = config?.baseUrl ||
174
- process.env.VIBEUSAGE_INSFORGE_BASE_URL ||
175
- process.env.VIBESCORE_INSFORGE_BASE_URL ||
176
- 'https://5tmappuk.us-east.insforge.app';
173
+ const runtime = resolveRuntimeConfig({ config: config || {}, env: process.env });
174
+ const deviceToken = runtime.deviceToken;
175
+ const baseUrl = runtime.baseUrl;
177
176
 
178
177
  let uploadResult = null;
179
178
  let uploadAttempted = false;
@@ -202,7 +201,8 @@ async function cmdSync(argv) {
202
201
  retryAtMs: autoDecision.blockedUntilMs,
203
202
  reason,
204
203
  pendingBytes,
205
- source: 'auto-throttled'
204
+ source: 'auto-throttled',
205
+ autoRetryNoSpawn: runtime.autoRetryNoSpawn
206
206
  });
207
207
  }
208
208
  }
@@ -251,7 +251,8 @@ async function cmdSync(argv) {
251
251
  retryAtMs,
252
252
  reason: 'backoff',
253
253
  pendingBytes,
254
- source: 'auto-error'
254
+ source: 'auto-error',
255
+ autoRetryNoSpawn: runtime.autoRetryNoSpawn
255
256
  });
256
257
  }
257
258
  }
@@ -278,7 +279,8 @@ async function cmdSync(argv) {
278
279
  retryAtMs,
279
280
  reason: 'backlog',
280
281
  pendingBytes,
281
- source: 'auto-backlog'
282
+ source: 'auto-backlog',
283
+ autoRetryNoSpawn: runtime.autoRetryNoSpawn
282
284
  });
283
285
  }
284
286
  }
@@ -385,7 +387,7 @@ function deriveAutoSkipReason({ decision, state }) {
385
387
  return 'throttled';
386
388
  }
387
389
 
388
- async function scheduleAutoRetry({ trackerDir, retryAtMs, reason, pendingBytes, source }) {
390
+ async function scheduleAutoRetry({ trackerDir, retryAtMs, reason, pendingBytes, source, autoRetryNoSpawn }) {
389
391
  const retryMs = coerceRetryMs(retryAtMs);
390
392
  if (!retryMs) return { scheduled: false, retryAtMs: 0 };
391
393
 
@@ -411,7 +413,7 @@ async function scheduleAutoRetry({ trackerDir, retryAtMs, reason, pendingBytes,
411
413
 
412
414
  const delayMs = Math.min(AUTO_RETRY_MAX_DELAY_MS, Math.max(0, retryMs - nowMs));
413
415
  if (delayMs <= 0) return { scheduled: false, retryAtMs: retryMs };
414
- if (process.env.VIBEUSAGE_AUTO_RETRY_NO_SPAWN === '1' || process.env.VIBESCORE_AUTO_RETRY_NO_SPAWN === '1') {
416
+ if (autoRetryNoSpawn) {
415
417
  return { scheduled: false, retryAtMs: retryMs };
416
418
  }
417
419
 
@@ -16,10 +16,7 @@ const { resolveTrackerPaths } = require('../lib/tracker-paths');
16
16
  async function cmdUninstall(argv) {
17
17
  const opts = parseArgs(argv);
18
18
  const home = os.homedir();
19
- const { trackerDir, binDir, legacyRootDir } = await resolveTrackerPaths({
20
- home,
21
- migrate: false
22
- });
19
+ const { trackerDir, binDir } = await resolveTrackerPaths({ home });
23
20
  const codexHome = process.env.CODEX_HOME || path.join(home, '.codex');
24
21
  const codexConfigPath = path.join(codexHome, 'config.toml');
25
22
  const codeHome = process.env.CODE_HOME || path.join(home, '.code');
@@ -73,7 +70,6 @@ async function cmdUninstall(argv) {
73
70
 
74
71
  if (opts.purge) {
75
72
  await fs.rm(path.join(home, '.vibeusage'), { recursive: true, force: true }).catch(() => {});
76
- await fs.rm(legacyRootDir, { recursive: true, force: true }).catch(() => {});
77
73
  }
78
74
 
79
75
  process.stdout.write(
@@ -6,7 +6,7 @@ const DEFAULT_BASE_URL = 'https://5tmappuk.us-east.insforge.app';
6
6
 
7
7
  async function beginBrowserAuth({ baseUrl, dashboardUrl, timeoutMs, open }) {
8
8
  const nonce = crypto.randomBytes(16).toString('hex');
9
- const callbackPath = `/vibescore/callback/${nonce}`;
9
+ const callbackPath = `/vibeusage/callback/${nonce}`;
10
10
  const authUrl = dashboardUrl ? new URL('/', dashboardUrl) : new URL('/auth/sign-up', baseUrl);
11
11
  const postAuthRedirect = resolvePostAuthRedirect({ dashboardUrl, authUrl });
12
12
  const { callbackUrl, waitForCallback } = await startLocalCallbackServer({
@@ -1,8 +1,6 @@
1
1
  function stripDebugFlag(argv, env = process.env) {
2
2
  const filtered = Array.isArray(argv) ? argv.filter((arg) => arg !== '--debug') : [];
3
- const debugEnv =
4
- String(env?.VIBEUSAGE_DEBUG || '') === '1' ||
5
- String(env?.VIBESCORE_DEBUG || '') === '1';
3
+ const debugEnv = String(env?.VIBEUSAGE_DEBUG || '') === '1';
6
4
  return { argv: filtered, debug: filtered.length !== (argv || []).length || debugEnv };
7
5
  }
8
6
 
@@ -20,7 +20,7 @@ async function collectTrackerDiagnostics({
20
20
  codexHome = process.env.CODEX_HOME || path.join(home, '.codex'),
21
21
  codeHome = process.env.CODE_HOME || path.join(home, '.code')
22
22
  } = {}) {
23
- const { trackerDir, binDir } = await resolveTrackerPaths({ home, migrate: true });
23
+ const { trackerDir, binDir } = await resolveTrackerPaths({ home });
24
24
  const configPath = path.join(trackerDir, 'config.json');
25
25
  const queuePath = path.join(trackerDir, 'queue.jsonl');
26
26
  const queueStatePath = path.join(trackerDir, 'queue.state.json');
@@ -0,0 +1,343 @@
1
+ const fs = require('node:fs/promises');
2
+ const { constants } = require('node:fs');
3
+
4
+ const { readJsonStrict } = require('./fs');
5
+
6
+ async function buildDoctorReport({
7
+ runtime = {},
8
+ diagnostics = null,
9
+ fetch = globalThis.fetch,
10
+ now = () => new Date(),
11
+ paths = {}
12
+ } = {}) {
13
+ const checks = [];
14
+
15
+ checks.push(...buildRuntimeChecks(runtime));
16
+
17
+ if (paths.trackerDir) {
18
+ checks.push(await checkTrackerDir(paths.trackerDir));
19
+ }
20
+ if (paths.configPath) {
21
+ checks.push(await checkConfigJson(paths.configPath));
22
+ }
23
+ if (paths.cliPath) {
24
+ checks.push(await checkCliEntrypoint(paths.cliPath));
25
+ }
26
+
27
+ checks.push(await checkNetwork({ baseUrl: runtime?.baseUrl || null, fetch }));
28
+
29
+ if (diagnostics) {
30
+ checks.push(...buildDiagnosticsChecks(diagnostics));
31
+ }
32
+
33
+ const summary = summarizeChecks(checks);
34
+
35
+ return {
36
+ version: 1,
37
+ generated_at: now().toISOString(),
38
+ ok: summary.critical === 0,
39
+ summary,
40
+ checks,
41
+ diagnostics
42
+ };
43
+ }
44
+
45
+ function buildRuntimeChecks(runtime = {}) {
46
+ const checks = [];
47
+ const baseUrl = typeof runtime.baseUrl === 'string' && runtime.baseUrl.trim() ? runtime.baseUrl.trim() : null;
48
+ const deviceToken = typeof runtime.deviceToken === 'string' && runtime.deviceToken.trim() ? 'set' : 'unset';
49
+ const dashboardUrl =
50
+ typeof runtime.dashboardUrl === 'string' && runtime.dashboardUrl.trim() ? runtime.dashboardUrl.trim() : null;
51
+ const httpTimeoutMs = Number.isFinite(Number(runtime.httpTimeoutMs)) ? Number(runtime.httpTimeoutMs) : null;
52
+ const debug = Boolean(runtime.debug);
53
+ const insforgeAnonKey = typeof runtime.insforgeAnonKey === 'string' && runtime.insforgeAnonKey.trim() ? 'set' : 'unset';
54
+ const autoRetryNoSpawn = Boolean(runtime.autoRetryNoSpawn);
55
+
56
+ checks.push({
57
+ id: 'runtime.base_url',
58
+ status: baseUrl ? 'ok' : 'fail',
59
+ detail: baseUrl ? 'base_url set' : 'base_url missing',
60
+ critical: false,
61
+ meta: {
62
+ base_url: baseUrl,
63
+ source: runtime?.sources?.baseUrl || null
64
+ }
65
+ });
66
+
67
+ checks.push({
68
+ id: 'runtime.device_token',
69
+ status: deviceToken === 'set' ? 'ok' : 'warn',
70
+ detail: deviceToken === 'set' ? 'device token set' : 'device token missing',
71
+ critical: false,
72
+ meta: {
73
+ device_token: deviceToken,
74
+ source: runtime?.sources?.deviceToken || null
75
+ }
76
+ });
77
+
78
+ checks.push({
79
+ id: 'runtime.dashboard_url',
80
+ status: 'ok',
81
+ detail: dashboardUrl ? 'dashboard_url set' : 'dashboard_url unset',
82
+ critical: false,
83
+ meta: {
84
+ dashboard_url: dashboardUrl,
85
+ source: runtime?.sources?.dashboardUrl || null
86
+ }
87
+ });
88
+
89
+ checks.push({
90
+ id: 'runtime.http_timeout_ms',
91
+ status: 'ok',
92
+ detail: 'http timeout resolved',
93
+ critical: false,
94
+ meta: {
95
+ http_timeout_ms: httpTimeoutMs,
96
+ source: runtime?.sources?.httpTimeoutMs || null
97
+ }
98
+ });
99
+
100
+ checks.push({
101
+ id: 'runtime.debug',
102
+ status: 'ok',
103
+ detail: debug ? 'debug enabled' : 'debug disabled',
104
+ critical: false,
105
+ meta: {
106
+ debug,
107
+ source: runtime?.sources?.debug || null
108
+ }
109
+ });
110
+
111
+ checks.push({
112
+ id: 'runtime.insforge_anon_key',
113
+ status: 'ok',
114
+ detail: insforgeAnonKey === 'set' ? 'anon key set' : 'anon key unset',
115
+ critical: false,
116
+ meta: {
117
+ anon_key: insforgeAnonKey,
118
+ source: runtime?.sources?.insforgeAnonKey || null
119
+ }
120
+ });
121
+
122
+ checks.push({
123
+ id: 'runtime.auto_retry_no_spawn',
124
+ status: 'ok',
125
+ detail: autoRetryNoSpawn ? 'auto retry spawn disabled' : 'auto retry spawn enabled',
126
+ critical: false,
127
+ meta: {
128
+ auto_retry_no_spawn: autoRetryNoSpawn,
129
+ source: runtime?.sources?.autoRetryNoSpawn || null
130
+ }
131
+ });
132
+
133
+ return checks;
134
+ }
135
+
136
+ async function checkTrackerDir(trackerDir) {
137
+ try {
138
+ const st = await fs.stat(trackerDir);
139
+ if (!st.isDirectory()) {
140
+ return {
141
+ id: 'fs.tracker_dir',
142
+ status: 'fail',
143
+ detail: 'tracker dir is not a directory',
144
+ critical: true,
145
+ meta: { path: trackerDir }
146
+ };
147
+ }
148
+ await fs.access(trackerDir, constants.R_OK);
149
+ return {
150
+ id: 'fs.tracker_dir',
151
+ status: 'ok',
152
+ detail: 'tracker dir readable',
153
+ critical: false,
154
+ meta: { path: trackerDir }
155
+ };
156
+ } catch (err) {
157
+ if (err && (err.code === 'ENOENT' || err.code === 'ENOTDIR')) {
158
+ return {
159
+ id: 'fs.tracker_dir',
160
+ status: 'warn',
161
+ detail: 'tracker dir missing',
162
+ critical: false,
163
+ meta: { path: trackerDir }
164
+ };
165
+ }
166
+ if (err && (err.code === 'EACCES' || err.code === 'EPERM')) {
167
+ return {
168
+ id: 'fs.tracker_dir',
169
+ status: 'fail',
170
+ detail: 'tracker dir permission denied',
171
+ critical: true,
172
+ meta: { path: trackerDir, code: err.code }
173
+ };
174
+ }
175
+ return {
176
+ id: 'fs.tracker_dir',
177
+ status: 'fail',
178
+ detail: 'tracker dir error',
179
+ critical: true,
180
+ meta: { path: trackerDir, code: err?.code || 'error' }
181
+ };
182
+ }
183
+ }
184
+
185
+ async function checkConfigJson(configPath) {
186
+ const res = await readJsonStrict(configPath);
187
+ if (res.status === 'ok') {
188
+ return {
189
+ id: 'fs.config_json',
190
+ status: 'ok',
191
+ detail: 'config.json readable',
192
+ critical: false,
193
+ meta: { path: configPath }
194
+ };
195
+ }
196
+ if (res.status === 'missing') {
197
+ return {
198
+ id: 'fs.config_json',
199
+ status: 'warn',
200
+ detail: 'config.json missing',
201
+ critical: false,
202
+ meta: { path: configPath }
203
+ };
204
+ }
205
+ if (res.status === 'invalid') {
206
+ return {
207
+ id: 'fs.config_json',
208
+ status: 'fail',
209
+ detail: 'config.json invalid',
210
+ critical: true,
211
+ meta: { path: configPath }
212
+ };
213
+ }
214
+ return {
215
+ id: 'fs.config_json',
216
+ status: 'fail',
217
+ detail: 'config.json read error',
218
+ critical: true,
219
+ meta: { path: configPath }
220
+ };
221
+ }
222
+
223
+ async function checkCliEntrypoint(cliPath) {
224
+ try {
225
+ const st = await fs.stat(cliPath);
226
+ if (!st.isFile()) {
227
+ return {
228
+ id: 'cli.entrypoint',
229
+ status: 'fail',
230
+ detail: 'cli entrypoint is not a file',
231
+ critical: false,
232
+ meta: { path: cliPath }
233
+ };
234
+ }
235
+ await fs.access(cliPath, constants.R_OK);
236
+ if (process.platform !== 'win32') {
237
+ await fs.access(cliPath, constants.X_OK);
238
+ }
239
+ return {
240
+ id: 'cli.entrypoint',
241
+ status: 'ok',
242
+ detail: 'cli entrypoint readable',
243
+ critical: false,
244
+ meta: { path: cliPath }
245
+ };
246
+ } catch (err) {
247
+ return {
248
+ id: 'cli.entrypoint',
249
+ status: 'fail',
250
+ detail: 'cli entrypoint not accessible',
251
+ critical: false,
252
+ meta: { path: cliPath, code: err?.code || 'error' }
253
+ };
254
+ }
255
+ }
256
+
257
+ async function checkNetwork({ baseUrl, fetch }) {
258
+ if (!baseUrl) {
259
+ return {
260
+ id: 'network.base_url',
261
+ status: 'warn',
262
+ detail: 'base_url missing (skipped)',
263
+ critical: false,
264
+ meta: { base_url: null }
265
+ };
266
+ }
267
+
268
+ const start = Date.now();
269
+ try {
270
+ if (typeof fetch !== 'function') throw new Error('Missing fetch');
271
+ const res = await fetch(baseUrl, { method: 'GET' });
272
+ const latency = Date.now() - start;
273
+ return {
274
+ id: 'network.base_url',
275
+ status: 'ok',
276
+ detail: `HTTP ${res.status} (reachable)`,
277
+ critical: false,
278
+ meta: {
279
+ status_code: res.status,
280
+ latency_ms: latency,
281
+ base_url: baseUrl
282
+ }
283
+ };
284
+ } catch (err) {
285
+ const latency = Date.now() - start;
286
+ return {
287
+ id: 'network.base_url',
288
+ status: 'fail',
289
+ detail: 'Network error',
290
+ critical: false,
291
+ meta: {
292
+ error: err?.message || String(err),
293
+ latency_ms: latency,
294
+ base_url: baseUrl
295
+ }
296
+ };
297
+ }
298
+ }
299
+
300
+ function buildDiagnosticsChecks(diagnostics) {
301
+ const checks = [];
302
+ const notify = diagnostics?.notify || {};
303
+ const notifyConfigured = Boolean(
304
+ notify.codex_notify_configured ||
305
+ notify.every_code_notify_configured ||
306
+ notify.claude_hook_configured ||
307
+ notify.gemini_hook_configured ||
308
+ notify.opencode_plugin_configured
309
+ );
310
+
311
+ checks.push({
312
+ id: 'notify.configured',
313
+ status: notifyConfigured ? 'ok' : 'warn',
314
+ detail: notifyConfigured ? 'notify configured' : 'notify not configured',
315
+ critical: false,
316
+ meta: { configured: notifyConfigured }
317
+ });
318
+
319
+ const uploadError = diagnostics?.upload?.last_error || null;
320
+ checks.push({
321
+ id: 'upload.last_error',
322
+ status: uploadError ? 'warn' : 'ok',
323
+ detail: uploadError ? 'last upload error present' : 'no upload errors',
324
+ critical: false,
325
+ meta: { last_error: uploadError ? uploadError.message || null : null }
326
+ });
327
+
328
+ return checks;
329
+ }
330
+
331
+ function summarizeChecks(checks = []) {
332
+ const summary = { ok: 0, warn: 0, fail: 0, critical: 0 };
333
+ for (const check of checks) {
334
+ if (!check || typeof check.status !== 'string') continue;
335
+ if (check.status === 'ok') summary.ok += 1;
336
+ else if (check.status === 'warn') summary.warn += 1;
337
+ else if (check.status === 'fail') summary.fail += 1;
338
+ if (check.status === 'fail' && check.critical) summary.critical += 1;
339
+ }
340
+ return summary;
341
+ }
342
+
343
+ module.exports = { buildDoctorReport };
package/src/lib/fs.js CHANGED
@@ -22,6 +22,21 @@ async function readJson(filePath) {
22
22
  }
23
23
  }
24
24
 
25
+ async function readJsonStrict(filePath) {
26
+ try {
27
+ const raw = await fs.readFile(filePath, 'utf8');
28
+ return { status: 'ok', value: JSON.parse(raw), error: null };
29
+ } catch (err) {
30
+ if (err && (err.code === 'ENOENT' || err.code === 'ENOTDIR')) {
31
+ return { status: 'missing', value: null, error: err };
32
+ }
33
+ if (err && err.name === 'SyntaxError') {
34
+ return { status: 'invalid', value: null, error: err };
35
+ }
36
+ return { status: 'error', value: null, error: err };
37
+ }
38
+ }
39
+
25
40
  async function writeJson(filePath, obj) {
26
41
  await writeFileAtomic(filePath, JSON.stringify(obj, null, 2) + '\n');
27
42
  }
@@ -55,8 +70,8 @@ module.exports = {
55
70
  ensureDir,
56
71
  writeFileAtomic,
57
72
  readJson,
73
+ readJsonStrict,
58
74
  writeJson,
59
75
  chmod600IfPossible,
60
76
  openLock
61
77
  };
62
-
@@ -11,16 +11,11 @@ function loadInsforgeSdk() {
11
11
  }
12
12
 
13
13
  function getAnonKey({ env = process.env } = {}) {
14
- return (
15
- env.VIBEUSAGE_INSFORGE_ANON_KEY ||
16
- env.VIBESCORE_INSFORGE_ANON_KEY ||
17
- env.INSFORGE_ANON_KEY ||
18
- ''
19
- );
14
+ return env.VIBEUSAGE_INSFORGE_ANON_KEY || '';
20
15
  }
21
16
 
22
17
  function getHttpTimeoutMs({ env = process.env } = {}) {
23
- const raw = readEnvValue(env, ['VIBEUSAGE_HTTP_TIMEOUT_MS', 'VIBESCORE_HTTP_TIMEOUT_MS']);
18
+ const raw = readEnvValue(env, ['VIBEUSAGE_HTTP_TIMEOUT_MS']);
24
19
  if (raw == null || raw === '') return 20_000;
25
20
  const n = Number(raw);
26
21
  if (!Number.isFinite(n)) return 20_000;
@@ -1,4 +1,4 @@
1
- const { exchangeLinkCode, issueDeviceToken, signInWithPassword } = require('./vibescore-api');
1
+ const { exchangeLinkCode, issueDeviceToken, signInWithPassword } = require('./vibeusage-api');
2
2
 
3
3
  async function issueDeviceTokenWithPassword({ baseUrl, email, password, deviceName }) {
4
4
  const accessToken = await signInWithPassword({ baseUrl, email, password });
@@ -0,0 +1,120 @@
1
+ const DEFAULT_BASE_URL = 'https://5tmappuk.us-east.insforge.app';
2
+ const DEFAULT_DASHBOARD_URL = 'https://www.vibeusage.cc';
3
+ const DEFAULT_HTTP_TIMEOUT_MS = 20_000;
4
+
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);
7
+ const dashboardUrl = pickString(
8
+ cli.dashboardUrl,
9
+ config.dashboardUrl,
10
+ env?.VIBEUSAGE_DASHBOARD_URL,
11
+ defaults.dashboardUrl,
12
+ DEFAULT_DASHBOARD_URL
13
+ );
14
+ const deviceToken = pickString(cli.deviceToken, config.deviceToken, env?.VIBEUSAGE_DEVICE_TOKEN, defaults.deviceToken, null);
15
+ const httpTimeoutMs = pickHttpTimeoutMs(
16
+ cli.httpTimeoutMs,
17
+ config.httpTimeoutMs,
18
+ env?.VIBEUSAGE_HTTP_TIMEOUT_MS,
19
+ defaults.httpTimeoutMs,
20
+ DEFAULT_HTTP_TIMEOUT_MS
21
+ );
22
+ const debug = pickBoolean(cli.debug, config.debug, env?.VIBEUSAGE_DEBUG, defaults.debug, false);
23
+ const insforgeAnonKey = pickString(
24
+ cli.insforgeAnonKey,
25
+ config.insforgeAnonKey,
26
+ env?.VIBEUSAGE_INSFORGE_ANON_KEY,
27
+ defaults.insforgeAnonKey,
28
+ ''
29
+ );
30
+ if (insforgeAnonKey.value == null) insforgeAnonKey.value = '';
31
+ const autoRetryNoSpawn = pickBoolean(
32
+ cli.autoRetryNoSpawn,
33
+ config.autoRetryNoSpawn,
34
+ env?.VIBEUSAGE_AUTO_RETRY_NO_SPAWN,
35
+ defaults.autoRetryNoSpawn,
36
+ false
37
+ );
38
+
39
+ return {
40
+ baseUrl: baseUrl.value,
41
+ dashboardUrl: dashboardUrl.value,
42
+ deviceToken: deviceToken.value,
43
+ httpTimeoutMs: httpTimeoutMs.value,
44
+ debug: debug.value,
45
+ insforgeAnonKey: insforgeAnonKey.value,
46
+ autoRetryNoSpawn: autoRetryNoSpawn.value,
47
+ sources: {
48
+ baseUrl: baseUrl.source,
49
+ dashboardUrl: dashboardUrl.source,
50
+ deviceToken: deviceToken.source,
51
+ httpTimeoutMs: httpTimeoutMs.source,
52
+ debug: debug.source,
53
+ insforgeAnonKey: insforgeAnonKey.source,
54
+ autoRetryNoSpawn: autoRetryNoSpawn.source
55
+ }
56
+ };
57
+ }
58
+
59
+ function pickString(...candidates) {
60
+ return pickValue(candidates, normalizeString);
61
+ }
62
+
63
+ function pickBoolean(...candidates) {
64
+ return pickValue(candidates, normalizeBoolean);
65
+ }
66
+
67
+ function pickHttpTimeoutMs(...candidates) {
68
+ return pickValue(candidates, normalizeHttpTimeoutMs);
69
+ }
70
+
71
+ function pickValue(candidates, normalize) {
72
+ const labels = ['cli', 'config', 'env', 'default', 'default'];
73
+ for (let i = 0; i < candidates.length; i += 1) {
74
+ const value = normalize(candidates[i]);
75
+ if (value !== undefined) {
76
+ return { value, source: labels[i] || 'default' };
77
+ }
78
+ }
79
+ return { value: null, source: 'default' };
80
+ }
81
+
82
+ function normalizeString(value) {
83
+ if (typeof value !== 'string') return undefined;
84
+ const trimmed = value.trim();
85
+ if (!trimmed) return undefined;
86
+ return trimmed;
87
+ }
88
+
89
+ function normalizeBoolean(value) {
90
+ if (typeof value === 'boolean') return value;
91
+ if (typeof value === 'number') return value !== 0;
92
+ if (typeof value === 'string') {
93
+ const trimmed = value.trim().toLowerCase();
94
+ if (!trimmed) return undefined;
95
+ if (trimmed === '1' || trimmed === 'true') return true;
96
+ if (trimmed === '0' || trimmed === 'false') return false;
97
+ }
98
+ return undefined;
99
+ }
100
+
101
+ function normalizeHttpTimeoutMs(value) {
102
+ if (value == null || value === '') return undefined;
103
+ const n = Number(value);
104
+ if (!Number.isFinite(n)) return undefined;
105
+ if (n <= 0) return 0;
106
+ return clampInt(n, 1000, 120_000);
107
+ }
108
+
109
+ function clampInt(value, min, max) {
110
+ const n = Number(value);
111
+ if (!Number.isFinite(n)) return min;
112
+ return Math.min(max, Math.max(min, Math.floor(n)));
113
+ }
114
+
115
+ module.exports = {
116
+ DEFAULT_BASE_URL,
117
+ DEFAULT_DASHBOARD_URL,
118
+ DEFAULT_HTTP_TIMEOUT_MS,
119
+ resolveRuntimeConfig
120
+ };
@@ -1,66 +1,15 @@
1
1
  const os = require('node:os');
2
2
  const path = require('node:path');
3
- const fs = require('node:fs/promises');
4
3
 
5
- async function resolveTrackerPaths({ home = os.homedir(), migrate = true } = {}) {
6
- const legacyRootDir = path.join(home, '.vibescore');
4
+ async function resolveTrackerPaths({ home = os.homedir() } = {}) {
7
5
  const rootDir = path.join(home, '.vibeusage');
8
- const legacyTrackerDir = path.join(legacyRootDir, 'tracker');
9
- const legacyBinDir = path.join(legacyRootDir, 'bin');
10
- const trackerDir = path.join(rootDir, 'tracker');
11
- const binDir = path.join(rootDir, 'bin');
12
-
13
- const legacyExists = await pathExists(legacyRootDir);
14
- const newExists = await pathExists(rootDir);
15
-
16
- let usingLegacy = false;
17
- let migrated = false;
18
-
19
- if (migrate && legacyExists && !newExists) {
20
- const result = await migrateLegacyRoot({ legacyRootDir, rootDir });
21
- usingLegacy = result.usingLegacy;
22
- migrated = result.migrated;
23
- } else if (!newExists && legacyExists) {
24
- usingLegacy = true;
25
- }
26
-
27
- const activeRootDir = usingLegacy ? legacyRootDir : rootDir;
28
6
  return {
29
- rootDir: activeRootDir,
30
- trackerDir: path.join(activeRootDir, 'tracker'),
31
- binDir: path.join(activeRootDir, 'bin'),
32
- legacyRootDir,
33
- legacyTrackerDir,
34
- legacyBinDir,
35
- migrated,
36
- usingLegacy
7
+ rootDir,
8
+ trackerDir: path.join(rootDir, 'tracker'),
9
+ binDir: path.join(rootDir, 'bin')
37
10
  };
38
11
  }
39
12
 
40
- async function migrateLegacyRoot({ legacyRootDir, rootDir }) {
41
- try {
42
- await fs.rename(legacyRootDir, rootDir);
43
- return { migrated: true, usingLegacy: false };
44
- } catch (err) {
45
- try {
46
- await fs.cp(legacyRootDir, rootDir, { recursive: true });
47
- return { migrated: true, usingLegacy: false };
48
- } catch (copyErr) {
49
- return { migrated: false, usingLegacy: true, error: copyErr };
50
- }
51
- }
52
- }
53
-
54
- async function pathExists(target) {
55
- try {
56
- await fs.stat(target);
57
- return true;
58
- } catch (err) {
59
- if (err && err.code === 'ENOENT') return false;
60
- throw err;
61
- }
62
- }
63
-
64
13
  module.exports = {
65
14
  resolveTrackerPaths
66
15
  };
@@ -3,7 +3,7 @@ const fssync = require('node:fs');
3
3
  const readline = require('node:readline');
4
4
 
5
5
  const { ensureDir, readJson, writeJson } = require('./fs');
6
- const { ingestHourly } = require('./vibescore-api');
6
+ const { ingestHourly } = require('./vibeusage-api');
7
7
 
8
8
  const DEFAULT_SOURCE = 'codex';
9
9
  const DEFAULT_MODEL = 'unknown';
@@ -19,7 +19,7 @@ async function issueDeviceToken({ baseUrl, accessToken, deviceName, platform = '
19
19
  const data = await invokeFunction({
20
20
  baseUrl,
21
21
  accessToken,
22
- slug: 'vibescore-device-token-issue',
22
+ slug: 'vibeusage-device-token-issue',
23
23
  method: 'POST',
24
24
  body: { device_name: deviceName, platform },
25
25
  errorPrefix: 'Device token issue failed'
@@ -39,7 +39,7 @@ async function exchangeLinkCode({ baseUrl, linkCode, requestId, deviceName, plat
39
39
  const data = await invokeFunction({
40
40
  baseUrl,
41
41
  accessToken: null,
42
- slug: 'vibescore-link-code-exchange',
42
+ slug: 'vibeusage-link-code-exchange',
43
43
  method: 'POST',
44
44
  body: {
45
45
  link_code: linkCode,
@@ -66,7 +66,7 @@ async function ingestHourly({ baseUrl, deviceToken, hourly }) {
66
66
  const data = await invokeFunctionWithRetry({
67
67
  baseUrl,
68
68
  accessToken: deviceToken,
69
- slug: 'vibescore-ingest',
69
+ slug: 'vibeusage-ingest',
70
70
  method: 'POST',
71
71
  body: { hourly },
72
72
  errorPrefix: 'Ingest failed',
@@ -83,7 +83,7 @@ async function syncHeartbeat({ baseUrl, deviceToken }) {
83
83
  const data = await invokeFunction({
84
84
  baseUrl,
85
85
  accessToken: deviceToken,
86
- slug: 'vibescore-sync-ping',
86
+ slug: 'vibeusage-sync-ping',
87
87
  method: 'POST',
88
88
  body: {},
89
89
  errorPrefix: 'Sync heartbeat failed'