vibeusage 0.2.21 → 0.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +306 -173
  2. package/README.old.md +324 -0
  3. package/README.zh-CN.md +304 -188
  4. package/package.json +32 -32
  5. package/src/cli.js +41 -37
  6. package/src/commands/activate-if-needed.js +41 -0
  7. package/src/commands/diagnostics.js +8 -9
  8. package/src/commands/doctor.js +31 -26
  9. package/src/commands/init.js +285 -218
  10. package/src/commands/status.js +86 -83
  11. package/src/commands/sync.js +182 -130
  12. package/src/commands/uninstall.js +66 -62
  13. package/src/lib/activation-check.js +290 -0
  14. package/src/lib/browser-auth.js +52 -54
  15. package/src/lib/claude-config.js +25 -25
  16. package/src/lib/cli-ui.js +35 -35
  17. package/src/lib/codex-config.js +40 -36
  18. package/src/lib/debug-flags.js +2 -2
  19. package/src/lib/diagnostics.js +70 -57
  20. package/src/lib/doctor.js +139 -132
  21. package/src/lib/fs.js +17 -17
  22. package/src/lib/gemini-config.js +44 -40
  23. package/src/lib/init-flow.js +16 -22
  24. package/src/lib/insforge-client.js +10 -10
  25. package/src/lib/insforge.js +9 -3
  26. package/src/lib/openclaw-hook.js +89 -66
  27. package/src/lib/openclaw-session-plugin.js +116 -92
  28. package/src/lib/opencode-config.js +31 -32
  29. package/src/lib/opencode-usage-audit.js +34 -31
  30. package/src/lib/progress.js +12 -13
  31. package/src/lib/project-usage-purge.js +23 -17
  32. package/src/lib/prompt.js +8 -4
  33. package/src/lib/rollout.js +342 -241
  34. package/src/lib/runtime-config.js +34 -22
  35. package/src/lib/subscriptions.js +94 -92
  36. package/src/lib/tracker-paths.js +6 -6
  37. package/src/lib/upload-throttle.js +35 -16
  38. package/src/lib/uploader.js +33 -29
  39. package/src/lib/vibeusage-api.js +72 -56
  40. package/src/lib/vibeusage-public-repo.js +41 -24
package/src/lib/doctor.js CHANGED
@@ -1,14 +1,14 @@
1
- const fs = require('node:fs/promises');
2
- const { constants } = require('node:fs');
1
+ const fs = require("node:fs/promises");
2
+ const { constants } = require("node:fs");
3
3
 
4
- const { readJsonStrict } = require('./fs');
4
+ const { readJsonStrict } = require("./fs");
5
5
 
6
6
  async function buildDoctorReport({
7
7
  runtime = {},
8
8
  diagnostics = null,
9
9
  fetch = globalThis.fetch,
10
10
  now = () => new Date(),
11
- paths = {}
11
+ paths = {},
12
12
  } = {}) {
13
13
  const checks = [];
14
14
 
@@ -38,96 +38,103 @@ async function buildDoctorReport({
38
38
  ok: summary.critical === 0,
39
39
  summary,
40
40
  checks,
41
- diagnostics
41
+ diagnostics,
42
42
  };
43
43
  }
44
44
 
45
45
  function buildRuntimeChecks(runtime = {}) {
46
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';
47
+ const baseUrl =
48
+ typeof runtime.baseUrl === "string" && runtime.baseUrl.trim() ? runtime.baseUrl.trim() : null;
49
+ const deviceToken =
50
+ typeof runtime.deviceToken === "string" && runtime.deviceToken.trim() ? "set" : "unset";
49
51
  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
+ typeof runtime.dashboardUrl === "string" && runtime.dashboardUrl.trim()
53
+ ? runtime.dashboardUrl.trim()
54
+ : null;
55
+ const httpTimeoutMs = Number.isFinite(Number(runtime.httpTimeoutMs))
56
+ ? Number(runtime.httpTimeoutMs)
57
+ : null;
52
58
  const debug = Boolean(runtime.debug);
53
- const insforgeAnonKey = typeof runtime.insforgeAnonKey === 'string' && runtime.insforgeAnonKey.trim() ? 'set' : 'unset';
59
+ const insforgeAnonKey =
60
+ typeof runtime.insforgeAnonKey === "string" && runtime.insforgeAnonKey.trim() ? "set" : "unset";
54
61
  const autoRetryNoSpawn = Boolean(runtime.autoRetryNoSpawn);
55
62
 
56
63
  checks.push({
57
- id: 'runtime.base_url',
58
- status: baseUrl ? 'ok' : 'fail',
59
- detail: baseUrl ? 'base_url set' : 'base_url missing',
64
+ id: "runtime.base_url",
65
+ status: baseUrl ? "ok" : "fail",
66
+ detail: baseUrl ? "base_url set" : "base_url missing",
60
67
  critical: false,
61
68
  meta: {
62
69
  base_url: baseUrl,
63
- source: runtime?.sources?.baseUrl || null
64
- }
70
+ source: runtime?.sources?.baseUrl || null,
71
+ },
65
72
  });
66
73
 
67
74
  checks.push({
68
- id: 'runtime.device_token',
69
- status: deviceToken === 'set' ? 'ok' : 'warn',
70
- detail: deviceToken === 'set' ? 'device token set' : 'device token missing',
75
+ id: "runtime.device_token",
76
+ status: deviceToken === "set" ? "ok" : "warn",
77
+ detail: deviceToken === "set" ? "device token set" : "device token missing",
71
78
  critical: false,
72
79
  meta: {
73
80
  device_token: deviceToken,
74
- source: runtime?.sources?.deviceToken || null
75
- }
81
+ source: runtime?.sources?.deviceToken || null,
82
+ },
76
83
  });
77
84
 
78
85
  checks.push({
79
- id: 'runtime.dashboard_url',
80
- status: 'ok',
81
- detail: dashboardUrl ? 'dashboard_url set' : 'dashboard_url unset',
86
+ id: "runtime.dashboard_url",
87
+ status: "ok",
88
+ detail: dashboardUrl ? "dashboard_url set" : "dashboard_url unset",
82
89
  critical: false,
83
90
  meta: {
84
91
  dashboard_url: dashboardUrl,
85
- source: runtime?.sources?.dashboardUrl || null
86
- }
92
+ source: runtime?.sources?.dashboardUrl || null,
93
+ },
87
94
  });
88
95
 
89
96
  checks.push({
90
- id: 'runtime.http_timeout_ms',
91
- status: 'ok',
92
- detail: 'http timeout resolved',
97
+ id: "runtime.http_timeout_ms",
98
+ status: "ok",
99
+ detail: "http timeout resolved",
93
100
  critical: false,
94
101
  meta: {
95
102
  http_timeout_ms: httpTimeoutMs,
96
- source: runtime?.sources?.httpTimeoutMs || null
97
- }
103
+ source: runtime?.sources?.httpTimeoutMs || null,
104
+ },
98
105
  });
99
106
 
100
107
  checks.push({
101
- id: 'runtime.debug',
102
- status: 'ok',
103
- detail: debug ? 'debug enabled' : 'debug disabled',
108
+ id: "runtime.debug",
109
+ status: "ok",
110
+ detail: debug ? "debug enabled" : "debug disabled",
104
111
  critical: false,
105
112
  meta: {
106
113
  debug,
107
- source: runtime?.sources?.debug || null
108
- }
114
+ source: runtime?.sources?.debug || null,
115
+ },
109
116
  });
110
117
 
111
118
  checks.push({
112
- id: 'runtime.insforge_anon_key',
113
- status: 'ok',
114
- detail: insforgeAnonKey === 'set' ? 'anon key set' : 'anon key unset',
119
+ id: "runtime.insforge_anon_key",
120
+ status: "ok",
121
+ detail: insforgeAnonKey === "set" ? "anon key set" : "anon key unset",
115
122
  critical: false,
116
123
  meta: {
117
124
  anon_key: insforgeAnonKey,
118
- source: runtime?.sources?.insforgeAnonKey || null
119
- }
125
+ source: runtime?.sources?.insforgeAnonKey || null,
126
+ },
120
127
  });
121
128
 
122
129
  checks.push({
123
- id: 'runtime.auto_retry_no_spawn',
124
- status: 'ok',
125
- detail: autoRetryNoSpawn ? 'auto retry spawn disabled' : 'auto retry spawn enabled',
130
+ id: "runtime.auto_retry_no_spawn",
131
+ status: "ok",
132
+ detail: autoRetryNoSpawn ? "auto retry spawn disabled" : "auto retry spawn enabled",
126
133
  critical: false,
127
134
  meta: {
128
135
  auto_retry_no_spawn: autoRetryNoSpawn,
129
- source: runtime?.sources?.autoRetryNoSpawn || null
130
- }
136
+ source: runtime?.sources?.autoRetryNoSpawn || null,
137
+ },
131
138
  });
132
139
 
133
140
  return checks;
@@ -138,85 +145,85 @@ async function checkTrackerDir(trackerDir) {
138
145
  const st = await fs.stat(trackerDir);
139
146
  if (!st.isDirectory()) {
140
147
  return {
141
- id: 'fs.tracker_dir',
142
- status: 'fail',
143
- detail: 'tracker dir is not a directory',
148
+ id: "fs.tracker_dir",
149
+ status: "fail",
150
+ detail: "tracker dir is not a directory",
144
151
  critical: true,
145
- meta: { path: trackerDir }
152
+ meta: { path: trackerDir },
146
153
  };
147
154
  }
148
155
  await fs.access(trackerDir, constants.R_OK);
149
156
  return {
150
- id: 'fs.tracker_dir',
151
- status: 'ok',
152
- detail: 'tracker dir readable',
157
+ id: "fs.tracker_dir",
158
+ status: "ok",
159
+ detail: "tracker dir readable",
153
160
  critical: false,
154
- meta: { path: trackerDir }
161
+ meta: { path: trackerDir },
155
162
  };
156
163
  } catch (err) {
157
- if (err && (err.code === 'ENOENT' || err.code === 'ENOTDIR')) {
164
+ if (err && (err.code === "ENOENT" || err.code === "ENOTDIR")) {
158
165
  return {
159
- id: 'fs.tracker_dir',
160
- status: 'warn',
161
- detail: 'tracker dir missing',
166
+ id: "fs.tracker_dir",
167
+ status: "warn",
168
+ detail: "tracker dir missing",
162
169
  critical: false,
163
- meta: { path: trackerDir }
170
+ meta: { path: trackerDir },
164
171
  };
165
172
  }
166
- if (err && (err.code === 'EACCES' || err.code === 'EPERM')) {
173
+ if (err && (err.code === "EACCES" || err.code === "EPERM")) {
167
174
  return {
168
- id: 'fs.tracker_dir',
169
- status: 'fail',
170
- detail: 'tracker dir permission denied',
175
+ id: "fs.tracker_dir",
176
+ status: "fail",
177
+ detail: "tracker dir permission denied",
171
178
  critical: true,
172
- meta: { path: trackerDir, code: err.code }
179
+ meta: { path: trackerDir, code: err.code },
173
180
  };
174
181
  }
175
182
  return {
176
- id: 'fs.tracker_dir',
177
- status: 'fail',
178
- detail: 'tracker dir error',
183
+ id: "fs.tracker_dir",
184
+ status: "fail",
185
+ detail: "tracker dir error",
179
186
  critical: true,
180
- meta: { path: trackerDir, code: err?.code || 'error' }
187
+ meta: { path: trackerDir, code: err?.code || "error" },
181
188
  };
182
189
  }
183
190
  }
184
191
 
185
192
  async function checkConfigJson(configPath) {
186
193
  const res = await readJsonStrict(configPath);
187
- if (res.status === 'ok') {
194
+ if (res.status === "ok") {
188
195
  return {
189
- id: 'fs.config_json',
190
- status: 'ok',
191
- detail: 'config.json readable',
196
+ id: "fs.config_json",
197
+ status: "ok",
198
+ detail: "config.json readable",
192
199
  critical: false,
193
- meta: { path: configPath }
200
+ meta: { path: configPath },
194
201
  };
195
202
  }
196
- if (res.status === 'missing') {
203
+ if (res.status === "missing") {
197
204
  return {
198
- id: 'fs.config_json',
199
- status: 'warn',
200
- detail: 'config.json missing',
205
+ id: "fs.config_json",
206
+ status: "warn",
207
+ detail: "config.json missing",
201
208
  critical: false,
202
- meta: { path: configPath }
209
+ meta: { path: configPath },
203
210
  };
204
211
  }
205
- if (res.status === 'invalid') {
212
+ if (res.status === "invalid") {
206
213
  return {
207
- id: 'fs.config_json',
208
- status: 'fail',
209
- detail: 'config.json invalid',
214
+ id: "fs.config_json",
215
+ status: "fail",
216
+ detail: "config.json invalid",
210
217
  critical: true,
211
- meta: { path: configPath }
218
+ meta: { path: configPath },
212
219
  };
213
220
  }
214
221
  return {
215
- id: 'fs.config_json',
216
- status: 'fail',
217
- detail: 'config.json read error',
222
+ id: "fs.config_json",
223
+ status: "fail",
224
+ detail: "config.json read error",
218
225
  critical: true,
219
- meta: { path: configPath }
226
+ meta: { path: configPath },
220
227
  };
221
228
  }
222
229
 
@@ -225,31 +232,31 @@ async function checkCliEntrypoint(cliPath) {
225
232
  const st = await fs.stat(cliPath);
226
233
  if (!st.isFile()) {
227
234
  return {
228
- id: 'cli.entrypoint',
229
- status: 'fail',
230
- detail: 'cli entrypoint is not a file',
235
+ id: "cli.entrypoint",
236
+ status: "fail",
237
+ detail: "cli entrypoint is not a file",
231
238
  critical: false,
232
- meta: { path: cliPath }
239
+ meta: { path: cliPath },
233
240
  };
234
241
  }
235
242
  await fs.access(cliPath, constants.R_OK);
236
- if (process.platform !== 'win32') {
243
+ if (process.platform !== "win32") {
237
244
  await fs.access(cliPath, constants.X_OK);
238
245
  }
239
246
  return {
240
- id: 'cli.entrypoint',
241
- status: 'ok',
242
- detail: 'cli entrypoint readable',
247
+ id: "cli.entrypoint",
248
+ status: "ok",
249
+ detail: "cli entrypoint readable",
243
250
  critical: false,
244
- meta: { path: cliPath }
251
+ meta: { path: cliPath },
245
252
  };
246
253
  } catch (err) {
247
254
  return {
248
- id: 'cli.entrypoint',
249
- status: 'fail',
250
- detail: 'cli entrypoint not accessible',
255
+ id: "cli.entrypoint",
256
+ status: "fail",
257
+ detail: "cli entrypoint not accessible",
251
258
  critical: false,
252
- meta: { path: cliPath, code: err?.code || 'error' }
259
+ meta: { path: cliPath, code: err?.code || "error" },
253
260
  };
254
261
  }
255
262
  }
@@ -257,42 +264,42 @@ async function checkCliEntrypoint(cliPath) {
257
264
  async function checkNetwork({ baseUrl, fetch }) {
258
265
  if (!baseUrl) {
259
266
  return {
260
- id: 'network.base_url',
261
- status: 'warn',
262
- detail: 'base_url missing (skipped)',
267
+ id: "network.base_url",
268
+ status: "warn",
269
+ detail: "base_url missing (skipped)",
263
270
  critical: false,
264
- meta: { base_url: null }
271
+ meta: { base_url: null },
265
272
  };
266
273
  }
267
274
 
268
275
  const start = Date.now();
269
276
  try {
270
- if (typeof fetch !== 'function') throw new Error('Missing fetch');
271
- const res = await fetch(baseUrl, { method: 'GET' });
277
+ if (typeof fetch !== "function") throw new Error("Missing fetch");
278
+ const res = await fetch(baseUrl, { method: "GET" });
272
279
  const latency = Date.now() - start;
273
280
  return {
274
- id: 'network.base_url',
275
- status: 'ok',
281
+ id: "network.base_url",
282
+ status: "ok",
276
283
  detail: `HTTP ${res.status} (reachable)`,
277
284
  critical: false,
278
285
  meta: {
279
286
  status_code: res.status,
280
287
  latency_ms: latency,
281
- base_url: baseUrl
282
- }
288
+ base_url: baseUrl,
289
+ },
283
290
  };
284
291
  } catch (err) {
285
292
  const latency = Date.now() - start;
286
293
  return {
287
- id: 'network.base_url',
288
- status: 'fail',
289
- detail: 'Network error',
294
+ id: "network.base_url",
295
+ status: "fail",
296
+ detail: "Network error",
290
297
  critical: false,
291
298
  meta: {
292
299
  error: err?.message || String(err),
293
300
  latency_ms: latency,
294
- base_url: baseUrl
295
- }
301
+ base_url: baseUrl,
302
+ },
296
303
  };
297
304
  }
298
305
  }
@@ -302,28 +309,28 @@ function buildDiagnosticsChecks(diagnostics) {
302
309
  const notify = diagnostics?.notify || {};
303
310
  const notifyConfigured = Boolean(
304
311
  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
- notify.openclaw_hook_configured
312
+ notify.every_code_notify_configured ||
313
+ notify.claude_hook_configured ||
314
+ notify.gemini_hook_configured ||
315
+ notify.opencode_plugin_configured ||
316
+ notify.openclaw_hook_configured,
310
317
  );
311
318
 
312
319
  checks.push({
313
- id: 'notify.configured',
314
- status: notifyConfigured ? 'ok' : 'warn',
315
- detail: notifyConfigured ? 'notify configured' : 'notify not configured',
320
+ id: "notify.configured",
321
+ status: notifyConfigured ? "ok" : "warn",
322
+ detail: notifyConfigured ? "notify configured" : "notify not configured",
316
323
  critical: false,
317
- meta: { configured: notifyConfigured }
324
+ meta: { configured: notifyConfigured },
318
325
  });
319
326
 
320
327
  const uploadError = diagnostics?.upload?.last_error || null;
321
328
  checks.push({
322
- id: 'upload.last_error',
323
- status: uploadError ? 'warn' : 'ok',
324
- detail: uploadError ? 'last upload error present' : 'no upload errors',
329
+ id: "upload.last_error",
330
+ status: uploadError ? "warn" : "ok",
331
+ detail: uploadError ? "last upload error present" : "no upload errors",
325
332
  critical: false,
326
- meta: { last_error: uploadError ? uploadError.message || null : null }
333
+ meta: { last_error: uploadError ? uploadError.message || null : null },
327
334
  });
328
335
 
329
336
  return checks;
@@ -332,11 +339,11 @@ function buildDiagnosticsChecks(diagnostics) {
332
339
  function summarizeChecks(checks = []) {
333
340
  const summary = { ok: 0, warn: 0, fail: 0, critical: 0 };
334
341
  for (const check of checks) {
335
- if (!check || typeof check.status !== 'string') continue;
336
- if (check.status === 'ok') summary.ok += 1;
337
- else if (check.status === 'warn') summary.warn += 1;
338
- else if (check.status === 'fail') summary.fail += 1;
339
- if (check.status === 'fail' && check.critical) summary.critical += 1;
342
+ if (!check || typeof check.status !== "string") continue;
343
+ if (check.status === "ok") summary.ok += 1;
344
+ else if (check.status === "warn") summary.warn += 1;
345
+ else if (check.status === "fail") summary.fail += 1;
346
+ if (check.status === "fail" && check.critical) summary.critical += 1;
340
347
  }
341
348
  return summary;
342
349
  }
package/src/lib/fs.js CHANGED
@@ -1,5 +1,5 @@
1
- const fs = require('node:fs/promises');
2
- const path = require('node:path');
1
+ const fs = require("node:fs/promises");
2
+ const path = require("node:path");
3
3
 
4
4
  async function ensureDir(p) {
5
5
  await fs.mkdir(p, { recursive: true });
@@ -9,13 +9,13 @@ async function writeFileAtomic(filePath, content) {
9
9
  const dir = path.dirname(filePath);
10
10
  await ensureDir(dir);
11
11
  const tmp = `${filePath}.tmp.${Date.now()}`;
12
- await fs.writeFile(tmp, content, { encoding: 'utf8' });
12
+ await fs.writeFile(tmp, content, { encoding: "utf8" });
13
13
  await fs.rename(tmp, filePath);
14
14
  }
15
15
 
16
16
  async function readJson(filePath) {
17
17
  try {
18
- const raw = await fs.readFile(filePath, 'utf8');
18
+ const raw = await fs.readFile(filePath, "utf8");
19
19
  return JSON.parse(raw);
20
20
  } catch (_e) {
21
21
  return null;
@@ -24,21 +24,21 @@ async function readJson(filePath) {
24
24
 
25
25
  async function readJsonStrict(filePath) {
26
26
  try {
27
- const raw = await fs.readFile(filePath, 'utf8');
28
- return { status: 'ok', value: JSON.parse(raw), error: null };
27
+ const raw = await fs.readFile(filePath, "utf8");
28
+ return { status: "ok", value: JSON.parse(raw), error: null };
29
29
  } catch (err) {
30
- if (err && (err.code === 'ENOENT' || err.code === 'ENOTDIR')) {
31
- return { status: 'missing', value: null, error: err };
30
+ if (err && (err.code === "ENOENT" || err.code === "ENOTDIR")) {
31
+ return { status: "missing", value: null, error: err };
32
32
  }
33
- if (err && err.name === 'SyntaxError') {
34
- return { status: 'invalid', value: null, error: err };
33
+ if (err && err.name === "SyntaxError") {
34
+ return { status: "invalid", value: null, error: err };
35
35
  }
36
- return { status: 'error', value: null, error: err };
36
+ return { status: "error", value: null, error: err };
37
37
  }
38
38
  }
39
39
 
40
40
  async function writeJson(filePath, obj) {
41
- await writeFileAtomic(filePath, JSON.stringify(obj, null, 2) + '\n');
41
+ await writeFileAtomic(filePath, JSON.stringify(obj, null, 2) + "\n");
42
42
  }
43
43
 
44
44
  async function chmod600IfPossible(filePath) {
@@ -49,16 +49,16 @@ async function chmod600IfPossible(filePath) {
49
49
 
50
50
  async function openLock(lockPath, { quietIfLocked }) {
51
51
  try {
52
- const handle = await fs.open(lockPath, 'wx');
52
+ const handle = await fs.open(lockPath, "wx");
53
53
  return {
54
54
  async release() {
55
55
  await handle.close().catch(() => {});
56
- }
56
+ },
57
57
  };
58
58
  } catch (e) {
59
- if (e && e.code === 'EEXIST') {
59
+ if (e && e.code === "EEXIST") {
60
60
  if (!quietIfLocked) {
61
- process.stdout.write('Another sync is already running.\n');
61
+ process.stdout.write("Another sync is already running.\n");
62
62
  }
63
63
  return null;
64
64
  }
@@ -73,5 +73,5 @@ module.exports = {
73
73
  readJsonStrict,
74
74
  writeJson,
75
75
  chmod600IfPossible,
76
- openLock
76
+ openLock,
77
77
  };