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 +16 -4
- package/README.zh-CN.md +16 -4
- package/package.json +4 -4
- package/src/cli.js +5 -0
- package/src/commands/doctor.js +91 -0
- package/src/commands/init.js +23 -24
- package/src/commands/status.js +1 -1
- package/src/commands/sync.js +14 -12
- package/src/commands/uninstall.js +1 -5
- package/src/lib/browser-auth.js +1 -1
- package/src/lib/debug-flags.js +1 -3
- package/src/lib/diagnostics.js +1 -1
- package/src/lib/doctor.js +343 -0
- package/src/lib/fs.js +16 -1
- package/src/lib/insforge-client.js +2 -7
- package/src/lib/insforge.js +1 -1
- package/src/lib/runtime-config.js +120 -0
- package/src/lib/tracker-paths.js +4 -55
- package/src/lib/uploader.js +1 -1
- package/src/lib/{vibescore-api.js → vibeusage-api.js} +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
# 🟢
|
|
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
|
-
- `
|
|
93
|
-
- `
|
|
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
|
-
# 🟢
|
|
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
|
-
- `
|
|
93
|
-
- `
|
|
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.
|
|
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 };
|
package/src/commands/init.js
CHANGED
|
@@ -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
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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,
|
|
212
|
-
const
|
|
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 ||
|
|
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'
|
|
766
|
+
return process.env.VIBEUSAGE_DEBUG === '1';
|
|
768
767
|
}
|
package/src/commands/status.js
CHANGED
|
@@ -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
|
|
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');
|
package/src/commands/sync.js
CHANGED
|
@@ -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/
|
|
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
|
|
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
|
|
173
|
-
const
|
|
174
|
-
|
|
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 (
|
|
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
|
|
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(
|
package/src/lib/browser-auth.js
CHANGED
|
@@ -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 = `/
|
|
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({
|
package/src/lib/debug-flags.js
CHANGED
|
@@ -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
|
|
package/src/lib/diagnostics.js
CHANGED
|
@@ -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
|
|
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'
|
|
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;
|
package/src/lib/insforge.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { exchangeLinkCode, issueDeviceToken, signInWithPassword } = require('./
|
|
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
|
+
};
|
package/src/lib/tracker-paths.js
CHANGED
|
@@ -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()
|
|
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
|
|
30
|
-
trackerDir: path.join(
|
|
31
|
-
binDir: path.join(
|
|
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
|
};
|
package/src/lib/uploader.js
CHANGED
|
@@ -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('./
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
86
|
+
slug: 'vibeusage-sync-ping',
|
|
87
87
|
method: 'POST',
|
|
88
88
|
body: {},
|
|
89
89
|
errorPrefix: 'Sync heartbeat failed'
|