vibeusage 0.2.21 → 0.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +306 -173
  2. package/README.old.md +324 -0
  3. package/README.zh-CN.md +304 -188
  4. package/package.json +32 -32
  5. package/src/cli.js +37 -37
  6. package/src/commands/activate-if-needed.js +41 -0
  7. package/src/commands/diagnostics.js +8 -9
  8. package/src/commands/doctor.js +31 -26
  9. package/src/commands/init.js +285 -218
  10. package/src/commands/status.js +86 -83
  11. package/src/commands/sync.js +178 -130
  12. package/src/commands/uninstall.js +66 -62
  13. package/src/lib/activation-check.js +341 -0
  14. package/src/lib/browser-auth.js +52 -54
  15. package/src/lib/claude-config.js +25 -25
  16. package/src/lib/cli-ui.js +35 -35
  17. package/src/lib/codex-config.js +40 -36
  18. package/src/lib/debug-flags.js +2 -2
  19. package/src/lib/diagnostics.js +70 -57
  20. package/src/lib/doctor.js +139 -132
  21. package/src/lib/fs.js +17 -17
  22. package/src/lib/gemini-config.js +44 -40
  23. package/src/lib/init-flow.js +16 -22
  24. package/src/lib/insforge-client.js +10 -10
  25. package/src/lib/insforge.js +9 -3
  26. package/src/lib/openclaw-hook.js +89 -66
  27. package/src/lib/openclaw-session-plugin.js +116 -92
  28. package/src/lib/opencode-config.js +31 -32
  29. package/src/lib/opencode-usage-audit.js +34 -31
  30. package/src/lib/progress.js +12 -13
  31. package/src/lib/project-usage-purge.js +23 -17
  32. package/src/lib/prompt.js +8 -4
  33. package/src/lib/rollout.js +342 -241
  34. package/src/lib/runtime-config.js +34 -22
  35. package/src/lib/subscriptions.js +94 -92
  36. package/src/lib/tracker-paths.js +6 -6
  37. package/src/lib/upload-throttle.js +35 -16
  38. package/src/lib/uploader.js +33 -29
  39. package/src/lib/vibeusage-api.js +72 -56
  40. package/src/lib/vibeusage-public-repo.js +41 -24
@@ -1,21 +1,21 @@
1
- const os = require('node:os');
2
- const path = require('node:path');
3
- const fs = require('node:fs/promises');
1
+ const os = require("node:os");
2
+ const path = require("node:path");
3
+ const fs = require("node:fs/promises");
4
4
 
5
- const { ensureDir, readJson, writeJson } = require('./fs');
5
+ const { ensureDir, readJson, writeJson } = require("./fs");
6
6
 
7
- const DEFAULT_EVENT = 'SessionEnd';
8
- const DEFAULT_HOOK_NAME = 'vibeusage-tracker';
9
- const DEFAULT_MATCHER = 'exit|clear|logout|prompt_input_exit|other';
7
+ const DEFAULT_EVENT = "SessionEnd";
8
+ const DEFAULT_HOOK_NAME = "vibeusage-tracker";
9
+ const DEFAULT_MATCHER = "exit|clear|logout|prompt_input_exit|other";
10
10
 
11
11
  function resolveGeminiConfigDir({ home = os.homedir(), env = process.env } = {}) {
12
- const explicit = typeof env.GEMINI_HOME === 'string' ? env.GEMINI_HOME.trim() : '';
12
+ const explicit = typeof env.GEMINI_HOME === "string" ? env.GEMINI_HOME.trim() : "";
13
13
  if (explicit) return path.resolve(explicit);
14
- return path.join(home, '.gemini');
14
+ return path.join(home, ".gemini");
15
15
  }
16
16
 
17
17
  function resolveGeminiSettingsPath({ configDir }) {
18
- return path.join(configDir, 'settings.json');
18
+ return path.join(configDir, "settings.json");
19
19
  }
20
20
 
21
21
  async function upsertGeminiHook({
@@ -23,7 +23,7 @@ async function upsertGeminiHook({
23
23
  hookCommand,
24
24
  hookName = DEFAULT_HOOK_NAME,
25
25
  matcher = DEFAULT_MATCHER,
26
- event = DEFAULT_EVENT
26
+ event = DEFAULT_EVENT,
27
27
  }) {
28
28
  const existing = await readJson(settingsPath);
29
29
  const settings = normalizeSettings(existing);
@@ -53,15 +53,15 @@ async function removeGeminiHook({
53
53
  settingsPath,
54
54
  hookCommand,
55
55
  hookName = DEFAULT_HOOK_NAME,
56
- event = DEFAULT_EVENT
56
+ event = DEFAULT_EVENT,
57
57
  }) {
58
58
  const existing = await readJson(settingsPath);
59
- if (!existing) return { removed: false, skippedReason: 'settings-missing' };
59
+ if (!existing) return { removed: false, skippedReason: "settings-missing" };
60
60
 
61
61
  const settings = normalizeSettings(existing);
62
62
  const hooks = normalizeHooks(settings.hooks);
63
63
  const entries = normalizeEntries(hooks[event]);
64
- if (entries.length === 0) return { removed: false, skippedReason: 'hook-missing' };
64
+ if (entries.length === 0) return { removed: false, skippedReason: "hook-missing" };
65
65
 
66
66
  let removed = false;
67
67
  const nextEntries = [];
@@ -71,7 +71,7 @@ async function removeGeminiHook({
71
71
  if (res.entry) nextEntries.push(res.entry);
72
72
  }
73
73
 
74
- if (!removed) return { removed: false, skippedReason: 'hook-missing' };
74
+ if (!removed) return { removed: false, skippedReason: "hook-missing" };
75
75
 
76
76
  const nextHooks = { ...hooks };
77
77
  if (nextEntries.length > 0) nextHooks[event] = nextEntries;
@@ -89,42 +89,42 @@ async function isGeminiHookConfigured({
89
89
  settingsPath,
90
90
  hookCommand,
91
91
  hookName = DEFAULT_HOOK_NAME,
92
- event = DEFAULT_EVENT
92
+ event = DEFAULT_EVENT,
93
93
  }) {
94
94
  const settings = await readJson(settingsPath);
95
- if (!settings || typeof settings !== 'object') return false;
95
+ if (!settings || typeof settings !== "object") return false;
96
96
  const hooks = settings.hooks;
97
- if (!hooks || typeof hooks !== 'object') return false;
97
+ if (!hooks || typeof hooks !== "object") return false;
98
98
  const entries = normalizeEntries(hooks[event]);
99
99
  return hasHook(entries, { hookCommand, hookName });
100
100
  }
101
101
 
102
102
  function buildGeminiHookCommand(notifyPath) {
103
- const cmd = typeof notifyPath === 'string' ? notifyPath : '';
103
+ const cmd = typeof notifyPath === "string" ? notifyPath : "";
104
104
  return `/usr/bin/env node ${quoteArg(cmd)} --source=gemini`;
105
105
  }
106
106
 
107
107
  function buildHookEntry({ hookCommand, hookName, matcher }) {
108
108
  const hook = {
109
109
  name: hookName,
110
- type: 'command',
111
- command: hookCommand
110
+ type: "command",
111
+ command: hookCommand,
112
112
  };
113
113
  const entry = { hooks: [hook] };
114
- if (typeof matcher === 'string' && matcher.length > 0) entry.matcher = matcher;
114
+ if (typeof matcher === "string" && matcher.length > 0) entry.matcher = matcher;
115
115
  return entry;
116
116
  }
117
117
 
118
118
  function normalizeSettings(raw) {
119
- return raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
119
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
120
120
  }
121
121
 
122
122
  function normalizeHooks(raw) {
123
- return raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
123
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
124
124
  }
125
125
 
126
126
  function normalizeTools(raw) {
127
- return raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
127
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
128
128
  }
129
129
 
130
130
  function normalizeEntries(raw) {
@@ -132,13 +132,13 @@ function normalizeEntries(raw) {
132
132
  }
133
133
 
134
134
  function normalizeCommand(cmd) {
135
- if (Array.isArray(cmd)) return cmd.map((v) => String(v)).join('\u0000');
136
- if (typeof cmd === 'string') return cmd.trim();
135
+ if (Array.isArray(cmd)) return cmd.map((v) => String(v)).join("\u0000");
136
+ if (typeof cmd === "string") return cmd.trim();
137
137
  return null;
138
138
  }
139
139
 
140
140
  function normalizeName(name) {
141
- if (typeof name !== 'string') return null;
141
+ if (typeof name !== "string") return null;
142
142
  const trimmed = name.trim();
143
143
  return trimmed.length > 0 ? trimmed : null;
144
144
  }
@@ -151,7 +151,7 @@ function ensureHooksEnabled(settings) {
151
151
  }
152
152
 
153
153
  function hookMatches(hook, { hookCommand, hookName, requireCommand = false }) {
154
- if (!hook || typeof hook !== 'object') return false;
154
+ if (!hook || typeof hook !== "object") return false;
155
155
  const name = normalizeName(hook.name);
156
156
  const targetName = normalizeName(hookName);
157
157
  const cmd = normalizeCommand(hook.command);
@@ -163,8 +163,9 @@ function hookMatches(hook, { hookCommand, hookName, requireCommand = false }) {
163
163
  }
164
164
 
165
165
  function entryMatches(entry, { hookCommand, hookName, requireCommand = false }) {
166
- if (!entry || typeof entry !== 'object') return false;
167
- if (entry.command || entry.name) return hookMatches(entry, { hookCommand, hookName, requireCommand });
166
+ if (!entry || typeof entry !== "object") return false;
167
+ if (entry.command || entry.name)
168
+ return hookMatches(entry, { hookCommand, hookName, requireCommand });
168
169
  if (!Array.isArray(entry.hooks)) return false;
169
170
  return entry.hooks.some((hook) => hookMatches(hook, { hookCommand, hookName, requireCommand }));
170
171
  }
@@ -176,7 +177,7 @@ function hasHook(entries, { hookCommand, hookName }) {
176
177
  function normalizeEntriesForHook(entries, { hookCommand, hookName }) {
177
178
  let changed = false;
178
179
  const nextEntries = entries.map((entry) => {
179
- if (!entry || typeof entry !== 'object') return entry;
180
+ if (!entry || typeof entry !== "object") return entry;
180
181
 
181
182
  if (entry.command || entry.name) {
182
183
  if (hookMatches(entry, { hookCommand, hookName })) {
@@ -212,8 +213,8 @@ function normalizeHookObject(hook, { hookCommand, hookName }) {
212
213
  const next = { ...hook };
213
214
  let changed = false;
214
215
 
215
- if (next.type !== 'command') {
216
- next.type = 'command';
216
+ if (next.type !== "command") {
217
+ next.type = "command";
217
218
  changed = true;
218
219
  }
219
220
 
@@ -231,17 +232,20 @@ function normalizeHookObject(hook, { hookCommand, hookName }) {
231
232
  }
232
233
 
233
234
  function stripHookFromEntry(entry, { hookCommand, hookName }) {
234
- if (!entry || typeof entry !== 'object') return { entry, removed: false };
235
+ if (!entry || typeof entry !== "object") return { entry, removed: false };
235
236
 
236
237
  if (entry.command || entry.name) {
237
- if (hookMatches(entry, { hookCommand, hookName, requireCommand: true })) return { entry: null, removed: true };
238
+ if (hookMatches(entry, { hookCommand, hookName, requireCommand: true }))
239
+ return { entry: null, removed: true };
238
240
  return { entry, removed: false };
239
241
  }
240
242
 
241
243
  const hooks = Array.isArray(entry.hooks) ? entry.hooks : null;
242
244
  if (!hooks) return { entry, removed: false };
243
245
 
244
- const nextHooks = hooks.filter((hook) => !hookMatches(hook, { hookCommand, hookName, requireCommand: true }));
246
+ const nextHooks = hooks.filter(
247
+ (hook) => !hookMatches(hook, { hookCommand, hookName, requireCommand: true }),
248
+ );
245
249
  if (nextHooks.length === hooks.length) return { entry, removed: false };
246
250
  if (nextHooks.length === 0) return { entry: null, removed: true };
247
251
 
@@ -249,7 +253,7 @@ function stripHookFromEntry(entry, { hookCommand, hookName }) {
249
253
  }
250
254
 
251
255
  function quoteArg(value) {
252
- const v = typeof value === 'string' ? value : '';
256
+ const v = typeof value === "string" ? value : "";
253
257
  if (!v) return '""';
254
258
  if (/^[A-Za-z0-9_\-./:@]+$/.test(v)) return v;
255
259
  return `"${v.replace(/"/g, '\\"')}"`;
@@ -261,7 +265,7 @@ async function writeGeminiSettings({ settingsPath, settings }) {
261
265
  try {
262
266
  const st = await fs.stat(settingsPath);
263
267
  if (st && st.isFile()) {
264
- backupPath = `${settingsPath}.bak.${new Date().toISOString().replace(/[:.]/g, '-')}`;
268
+ backupPath = `${settingsPath}.bak.${new Date().toISOString().replace(/[:.]/g, "-")}`;
265
269
  await fs.copyFile(settingsPath, backupPath);
266
270
  }
267
271
  } catch (_e) {
@@ -280,5 +284,5 @@ module.exports = {
280
284
  buildGeminiHookCommand,
281
285
  upsertGeminiHook,
282
286
  removeGeminiHook,
283
- isGeminiHookConfigured
287
+ isGeminiHookConfigured,
284
288
  };
@@ -1,42 +1,36 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
- const { formatSummaryLine, renderBox, underline } = require('./cli-ui');
3
+ const { formatSummaryLine, renderBox, underline } = require("./cli-ui");
4
4
 
5
- const DIVIDER = '----------------------------------------------';
5
+ const DIVIDER = "----------------------------------------------";
6
6
 
7
7
  function renderLocalReport({ summary, isDryRun }) {
8
8
  const header = isDryRun
9
- ? 'Dry run complete. Preview only; no changes were applied.'
10
- : 'Local configuration complete.';
11
- const lines = [header, '', 'Integration Status:'];
9
+ ? "Dry run complete. Preview only; no changes were applied."
10
+ : "Local configuration complete.";
11
+ const lines = [header, "", "Integration Status:"];
12
12
  for (const item of summary || []) lines.push(formatSummaryLine(item));
13
- process.stdout.write(`${lines.join('\n')}\n`);
13
+ process.stdout.write(`${lines.join("\n")}\n`);
14
14
  }
15
15
 
16
16
  function renderAuthTransition({ authUrl, canAutoOpen }) {
17
- const lines = ['', DIVIDER, '', 'Next: Registering device...'];
17
+ const lines = ["", DIVIDER, "", "Next: Registering device..."];
18
18
  if (canAutoOpen) {
19
- lines.push('Opening your browser to link account...');
19
+ lines.push("Opening your browser to link account...");
20
20
  if (authUrl) lines.push(`If it does not open, visit: ${underline(authUrl)}`);
21
21
  } else {
22
- lines.push('Open the link below to sign in.');
22
+ lines.push("Open the link below to sign in.");
23
23
  if (authUrl) lines.push(`Visit: ${underline(authUrl)}`);
24
24
  }
25
- lines.push('');
26
- process.stdout.write(`${lines.join('\n')}\n`);
25
+ lines.push("");
26
+ process.stdout.write(`${lines.join("\n")}\n`);
27
27
  }
28
28
 
29
29
  function renderSuccessBox({ configPath, dashboardUrl }) {
30
- const identityLine = 'Account linked.';
31
- const lines = [
32
- 'You are all set!',
33
- '',
34
- identityLine,
35
- `Token saved to: ${configPath}`,
36
- ''
37
- ];
30
+ const identityLine = "Account linked.";
31
+ const lines = ["You are all set!", "", identityLine, `Token saved to: ${configPath}`, ""];
38
32
  if (dashboardUrl) lines.push(`View your stats at: ${dashboardUrl}`);
39
- lines.push('You can close this terminal window.');
33
+ lines.push("You can close this terminal window.");
40
34
  process.stdout.write(`${renderBox(lines)}\n`);
41
35
  }
42
36
 
@@ -44,5 +38,5 @@ module.exports = {
44
38
  DIVIDER,
45
39
  renderLocalReport,
46
40
  renderAuthTransition,
47
- renderSuccessBox
41
+ renderSuccessBox,
48
42
  };
@@ -1,22 +1,22 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  function loadInsforgeSdk() {
4
4
  try {
5
- return require('@insforge/sdk');
5
+ return require("@insforge/sdk");
6
6
  } catch (err) {
7
- const wrapped = new Error('Missing dependency @insforge/sdk. Please reinstall vibeusage.');
7
+ const wrapped = new Error("Missing dependency @insforge/sdk. Please reinstall vibeusage.");
8
8
  wrapped.cause = err;
9
9
  throw wrapped;
10
10
  }
11
11
  }
12
12
 
13
13
  function getAnonKey({ env = process.env } = {}) {
14
- return env.VIBEUSAGE_INSFORGE_ANON_KEY || '';
14
+ return env.VIBEUSAGE_INSFORGE_ANON_KEY || "";
15
15
  }
16
16
 
17
17
  function getHttpTimeoutMs({ env = process.env } = {}) {
18
- const raw = readEnvValue(env, ['VIBEUSAGE_HTTP_TIMEOUT_MS']);
19
- if (raw == null || raw === '') return 20_000;
18
+ const raw = readEnvValue(env, ["VIBEUSAGE_HTTP_TIMEOUT_MS"]);
19
+ if (raw == null || raw === "") return 20_000;
20
20
  const n = Number(raw);
21
21
  if (!Number.isFinite(n)) return 20_000;
22
22
  if (n <= 0) return 0;
@@ -46,14 +46,14 @@ function createTimeoutFetch(baseFetch) {
46
46
  }
47
47
 
48
48
  function createInsforgeClient({ baseUrl, accessToken } = {}) {
49
- if (!baseUrl) throw new Error('Missing baseUrl');
49
+ if (!baseUrl) throw new Error("Missing baseUrl");
50
50
  const { createClient } = loadInsforgeSdk();
51
51
  const anonKey = getAnonKey();
52
52
  return createClient({
53
53
  baseUrl,
54
54
  anonKey: anonKey || undefined,
55
55
  edgeFunctionToken: accessToken || undefined,
56
- fetch: createTimeoutFetch(globalThis.fetch)
56
+ fetch: createTimeoutFetch(globalThis.fetch),
57
57
  });
58
58
  }
59
59
 
@@ -67,7 +67,7 @@ function readEnvValue(env, keys) {
67
67
  if (!env || !Array.isArray(keys)) return undefined;
68
68
  for (const key of keys) {
69
69
  const value = env?.[key];
70
- if (value != null && value !== '') return value;
70
+ if (value != null && value !== "") return value;
71
71
  }
72
72
  return undefined;
73
73
  }
@@ -75,5 +75,5 @@ function readEnvValue(env, keys) {
75
75
  module.exports = {
76
76
  createInsforgeClient,
77
77
  getAnonKey,
78
- getHttpTimeoutMs
78
+ getHttpTimeoutMs,
79
79
  };
@@ -1,4 +1,4 @@
1
- const { exchangeLinkCode, issueDeviceToken, signInWithPassword } = require('./vibeusage-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 });
@@ -11,7 +11,13 @@ async function issueDeviceTokenWithAccessToken({ baseUrl, accessToken, deviceNam
11
11
  return issued;
12
12
  }
13
13
 
14
- async function issueDeviceTokenWithLinkCode({ baseUrl, linkCode, requestId, deviceName, platform }) {
14
+ async function issueDeviceTokenWithLinkCode({
15
+ baseUrl,
16
+ linkCode,
17
+ requestId,
18
+ deviceName,
19
+ platform,
20
+ }) {
15
21
  const issued = await exchangeLinkCode({ baseUrl, linkCode, requestId, deviceName, platform });
16
22
  return { token: issued.token, deviceId: issued.deviceId };
17
23
  }
@@ -19,5 +25,5 @@ async function issueDeviceTokenWithLinkCode({ baseUrl, linkCode, requestId, devi
19
25
  module.exports = {
20
26
  issueDeviceTokenWithPassword,
21
27
  issueDeviceTokenWithAccessToken,
22
- issueDeviceTokenWithLinkCode
28
+ issueDeviceTokenWithLinkCode,
23
29
  };