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
@@ -1,22 +1,26 @@
1
- const os = require('node:os');
2
- const path = require('node:path');
3
- const fs = require('node:fs/promises');
4
- const fssync = require('node:fs');
5
- const cp = require('node:child_process');
1
+ const os = require("node:os");
2
+ const path = require("node:path");
3
+ const fs = require("node:fs/promises");
4
+ const fssync = require("node:fs");
5
+ const cp = require("node:child_process");
6
6
 
7
- const OPENCLAW_SESSION_PLUGIN_ID = 'openclaw-session-sync';
8
- const OPENCLAW_SESSION_PLUGIN_DIRNAME = 'openclaw-plugin';
7
+ const OPENCLAW_SESSION_PLUGIN_ID = "openclaw-session-sync";
8
+ const OPENCLAW_SESSION_PLUGIN_DIRNAME = "openclaw-plugin";
9
9
 
10
- function resolveOpenclawSessionPluginPaths({ home = os.homedir(), trackerDir, env = process.env } = {}) {
11
- if (!trackerDir) throw new Error('trackerDir is required');
10
+ function resolveOpenclawSessionPluginPaths({
11
+ home = os.homedir(),
12
+ trackerDir,
13
+ env = process.env,
14
+ } = {}) {
15
+ if (!trackerDir) throw new Error("trackerDir is required");
12
16
 
13
17
  const openclawConfigPath =
14
- normalizeString(env.OPENCLAW_CONFIG_PATH) || path.join(home, '.openclaw', 'openclaw.json');
18
+ normalizeString(env.OPENCLAW_CONFIG_PATH) || path.join(home, ".openclaw", "openclaw.json");
15
19
 
16
20
  const openclawHome =
17
21
  normalizeString(env.VIBEUSAGE_OPENCLAW_HOME) ||
18
22
  normalizeString(env.OPENCLAW_STATE_DIR) ||
19
- path.join(home, '.openclaw');
23
+ path.join(home, ".openclaw");
20
24
 
21
25
  const pluginDir = path.join(trackerDir, OPENCLAW_SESSION_PLUGIN_DIRNAME);
22
26
  const pluginEntryDir = path.join(pluginDir, OPENCLAW_SESSION_PLUGIN_ID);
@@ -26,15 +30,15 @@ function resolveOpenclawSessionPluginPaths({ home = os.homedir(), trackerDir, en
26
30
  pluginDir,
27
31
  pluginEntryDir,
28
32
  openclawConfigPath,
29
- openclawHome
33
+ openclawHome,
30
34
  };
31
35
  }
32
36
 
33
37
  async function installOpenclawSessionPlugin({
34
38
  home = os.homedir(),
35
39
  trackerDir,
36
- packageName = 'vibeusage',
37
- env = process.env
40
+ packageName = "vibeusage",
41
+ env = process.env,
38
42
  } = {}) {
39
43
  const paths = resolveOpenclawSessionPluginPaths({ home, trackerDir, env });
40
44
 
@@ -42,24 +46,24 @@ async function installOpenclawSessionPlugin({
42
46
  pluginDir: paths.pluginDir,
43
47
  trackerDir,
44
48
  packageName,
45
- openclawHome: paths.openclawHome
49
+ openclawHome: paths.openclawHome,
46
50
  });
47
51
 
48
- const installResult = runOpenclawCli(['plugins', 'install', '--link', paths.pluginEntryDir], env);
52
+ const installResult = runOpenclawCli(["plugins", "install", "--link", paths.pluginEntryDir], env);
49
53
  if (installResult.skippedReason) {
50
54
  return { configured: false, ...paths, ...installResult };
51
55
  }
52
56
 
53
- const enableResult = runOpenclawCli(['plugins', 'enable', paths.pluginId], env);
57
+ const enableResult = runOpenclawCli(["plugins", "enable", paths.pluginId], env);
54
58
  if (enableResult.skippedReason) {
55
59
  return {
56
60
  configured: false,
57
61
  ...paths,
58
62
  skippedReason: enableResult.skippedReason,
59
63
  error: enableResult.error,
60
- stdout: `${installResult.stdout || ''}\n${enableResult.stdout || ''}`.trim(),
61
- stderr: `${installResult.stderr || ''}\n${enableResult.stderr || ''}`.trim(),
62
- code: enableResult.code
64
+ stdout: `${installResult.stdout || ""}\n${enableResult.stdout || ""}`.trim(),
65
+ stderr: `${installResult.stderr || ""}\n${enableResult.stderr || ""}`.trim(),
66
+ code: enableResult.code,
63
67
  };
64
68
  }
65
69
 
@@ -67,61 +71,70 @@ async function installOpenclawSessionPlugin({
67
71
  return {
68
72
  configured: state.configured,
69
73
  changed:
70
- /Linked plugin path:/i.test(installResult.stdout || '') ||
71
- /Enabled plugin/i.test(enableResult.stdout || '') ||
72
- /already enabled/i.test(enableResult.stdout || ''),
74
+ /Linked plugin path:/i.test(installResult.stdout || "") ||
75
+ /Enabled plugin/i.test(enableResult.stdout || "") ||
76
+ /already enabled/i.test(enableResult.stdout || ""),
73
77
  ...paths,
74
- stdout: `${installResult.stdout || ''}\n${enableResult.stdout || ''}`.trim(),
75
- stderr: `${installResult.stderr || ''}\n${enableResult.stderr || ''}`.trim(),
76
- code: enableResult.code
78
+ stdout: `${installResult.stdout || ""}\n${enableResult.stdout || ""}`.trim(),
79
+ stderr: `${installResult.stderr || ""}\n${enableResult.stderr || ""}`.trim(),
80
+ code: enableResult.code,
77
81
  };
78
82
  }
79
83
 
80
- async function ensureOpenclawSessionPluginFiles({ pluginDir, trackerDir, packageName = 'vibeusage', openclawHome } = {}) {
81
- if (!pluginDir || !trackerDir) throw new Error('pluginDir and trackerDir are required');
84
+ async function ensureOpenclawSessionPluginFiles({
85
+ pluginDir,
86
+ trackerDir,
87
+ packageName = "vibeusage",
88
+ openclawHome,
89
+ } = {}) {
90
+ if (!pluginDir || !trackerDir) throw new Error("pluginDir and trackerDir are required");
82
91
 
83
92
  const pluginEntryDir = path.join(pluginDir, OPENCLAW_SESSION_PLUGIN_ID);
84
93
  await fs.mkdir(pluginEntryDir, { recursive: true });
85
94
 
86
- const packageJsonPath = path.join(pluginEntryDir, 'package.json');
87
- const pluginMetaPath = path.join(pluginEntryDir, 'openclaw.plugin.json');
88
- const indexPath = path.join(pluginEntryDir, 'index.js');
95
+ const packageJsonPath = path.join(pluginEntryDir, "package.json");
96
+ const pluginMetaPath = path.join(pluginEntryDir, "openclaw.plugin.json");
97
+ const indexPath = path.join(pluginEntryDir, "index.js");
89
98
 
90
- await fs.writeFile(packageJsonPath, buildSessionPluginPackageJson(), 'utf8');
91
- await fs.writeFile(pluginMetaPath, buildSessionPluginMeta(), 'utf8');
99
+ await fs.writeFile(packageJsonPath, buildSessionPluginPackageJson(), "utf8");
100
+ await fs.writeFile(pluginMetaPath, buildSessionPluginMeta(), "utf8");
92
101
  await fs.writeFile(
93
102
  indexPath,
94
103
  buildSessionPluginIndex({
95
104
  trackerDir,
96
105
  packageName,
97
- openclawHome: openclawHome || path.join(os.homedir(), '.openclaw')
106
+ openclawHome: openclawHome || path.join(os.homedir(), ".openclaw"),
98
107
  }),
99
- 'utf8'
108
+ "utf8",
100
109
  );
101
110
  }
102
111
 
103
- async function probeOpenclawSessionPluginState({ home = os.homedir(), trackerDir, env = process.env } = {}) {
112
+ async function probeOpenclawSessionPluginState({
113
+ home = os.homedir(),
114
+ trackerDir,
115
+ env = process.env,
116
+ } = {}) {
104
117
  const paths = resolveOpenclawSessionPluginPaths({ home, trackerDir, env });
105
118
  const { openclawConfigPath, pluginEntryDir, pluginId } = paths;
106
119
 
107
120
  const pluginFilesReady =
108
- fssync.existsSync(path.join(pluginEntryDir, 'package.json')) &&
109
- fssync.existsSync(path.join(pluginEntryDir, 'index.js'));
121
+ fssync.existsSync(path.join(pluginEntryDir, "package.json")) &&
122
+ fssync.existsSync(path.join(pluginEntryDir, "index.js"));
110
123
 
111
124
  let cfg = null;
112
125
  try {
113
- const raw = await fs.readFile(openclawConfigPath, 'utf8');
126
+ const raw = await fs.readFile(openclawConfigPath, "utf8");
114
127
  cfg = JSON.parse(raw);
115
128
  } catch (err) {
116
- if (err?.code === 'ENOENT' || err?.code === 'ENOTDIR') {
129
+ if (err?.code === "ENOENT" || err?.code === "ENOTDIR") {
117
130
  return {
118
131
  configured: false,
119
132
  enabled: false,
120
133
  linked: false,
121
134
  installed: false,
122
135
  pluginFilesReady,
123
- skippedReason: 'openclaw-config-missing',
124
- ...paths
136
+ skippedReason: "openclaw-config-missing",
137
+ ...paths,
125
138
  };
126
139
  }
127
140
  return {
@@ -130,9 +143,9 @@ async function probeOpenclawSessionPluginState({ home = os.homedir(), trackerDir
130
143
  linked: false,
131
144
  installed: false,
132
145
  pluginFilesReady,
133
- skippedReason: 'openclaw-config-unreadable',
146
+ skippedReason: "openclaw-config-unreadable",
134
147
  error: err?.message || String(err),
135
- ...paths
148
+ ...paths,
136
149
  };
137
150
  }
138
151
 
@@ -141,9 +154,12 @@ async function probeOpenclawSessionPluginState({ home = os.homedir(), trackerDir
141
154
 
142
155
  const loadPaths = Array.isArray(cfg?.plugins?.load?.paths) ? cfg.plugins.load.paths : [];
143
156
  const normalizedPluginEntryDir = path.resolve(pluginEntryDir);
144
- const linked = loadPaths.some((entry) => path.resolve(String(entry || '')) === normalizedPluginEntryDir);
157
+ const linked = loadPaths.some(
158
+ (entry) => path.resolve(String(entry || "")) === normalizedPluginEntryDir,
159
+ );
145
160
 
146
- const installs = cfg?.plugins?.installs && typeof cfg.plugins.installs === 'object' ? cfg.plugins.installs : {};
161
+ const installs =
162
+ cfg?.plugins?.installs && typeof cfg.plugins.installs === "object" ? cfg.plugins.installs : {};
147
163
  const installEntry = installs[pluginId];
148
164
  const installed = Boolean(installEntry);
149
165
 
@@ -153,26 +169,30 @@ async function probeOpenclawSessionPluginState({ home = os.homedir(), trackerDir
153
169
  linked,
154
170
  installed,
155
171
  pluginFilesReady,
156
- ...paths
172
+ ...paths,
157
173
  };
158
174
  }
159
175
 
160
- async function removeOpenclawSessionPluginConfig({ home = os.homedir(), trackerDir, env = process.env } = {}) {
176
+ async function removeOpenclawSessionPluginConfig({
177
+ home = os.homedir(),
178
+ trackerDir,
179
+ env = process.env,
180
+ } = {}) {
161
181
  const paths = resolveOpenclawSessionPluginPaths({ home, trackerDir, env });
162
182
  const { openclawConfigPath, pluginEntryDir, pluginId } = paths;
163
183
 
164
184
  let cfg;
165
185
  try {
166
- cfg = JSON.parse(await fs.readFile(openclawConfigPath, 'utf8'));
186
+ cfg = JSON.parse(await fs.readFile(openclawConfigPath, "utf8"));
167
187
  } catch (err) {
168
- if (err?.code === 'ENOENT' || err?.code === 'ENOTDIR') {
169
- return { removed: false, skippedReason: 'openclaw-config-missing', ...paths };
188
+ if (err?.code === "ENOENT" || err?.code === "ENOTDIR") {
189
+ return { removed: false, skippedReason: "openclaw-config-missing", ...paths };
170
190
  }
171
191
  return {
172
192
  removed: false,
173
- skippedReason: 'openclaw-config-unreadable',
193
+ skippedReason: "openclaw-config-unreadable",
174
194
  error: err?.message || String(err),
175
- ...paths
195
+ ...paths,
176
196
  };
177
197
  }
178
198
 
@@ -187,7 +207,9 @@ async function removeOpenclawSessionPluginConfig({ home = os.homedir(), trackerD
187
207
 
188
208
  if (plugins?.load && Array.isArray(plugins.load.paths)) {
189
209
  const target = path.resolve(pluginEntryDir);
190
- const after = plugins.load.paths.filter((entry) => path.resolve(String(entry || '')) !== target);
210
+ const after = plugins.load.paths.filter(
211
+ (entry) => path.resolve(String(entry || "")) !== target,
212
+ );
191
213
  if (after.length !== plugins.load.paths.length) {
192
214
  plugins.load.paths = after;
193
215
  changed = true;
@@ -196,7 +218,7 @@ async function removeOpenclawSessionPluginConfig({ home = os.homedir(), trackerD
196
218
  }
197
219
  }
198
220
 
199
- if (plugins?.installs && typeof plugins.installs === 'object') {
221
+ if (plugins?.installs && typeof plugins.installs === "object") {
200
222
  const installs = plugins.installs;
201
223
  if (Object.prototype.hasOwnProperty.call(installs, pluginId)) {
202
224
  delete installs[pluginId];
@@ -225,7 +247,7 @@ async function removeOpenclawSessionPluginConfig({ home = os.homedir(), trackerD
225
247
  }
226
248
 
227
249
  if (changed) {
228
- await fs.writeFile(openclawConfigPath, `${JSON.stringify(cfg, null, 2)}\n`, 'utf8');
250
+ await fs.writeFile(openclawConfigPath, `${JSON.stringify(cfg, null, 2)}\n`, "utf8");
229
251
  }
230
252
 
231
253
  const hadFiles = await fs
@@ -240,61 +262,61 @@ async function removeOpenclawSessionPluginConfig({ home = os.homedir(), trackerD
240
262
  function runOpenclawCli(args, env = process.env) {
241
263
  let res;
242
264
  try {
243
- res = cp.spawnSync('openclaw', args, {
265
+ res = cp.spawnSync("openclaw", args, {
244
266
  env,
245
- encoding: 'utf8',
246
- timeout: 30_000
267
+ encoding: "utf8",
268
+ timeout: 30_000,
247
269
  });
248
270
  } catch (err) {
249
271
  return {
250
272
  code: 1,
251
- skippedReason: err?.code === 'ENOENT' ? 'openclaw-cli-missing' : 'openclaw-cli-error',
273
+ skippedReason: err?.code === "ENOENT" ? "openclaw-cli-missing" : "openclaw-cli-error",
252
274
  error: err?.message || String(err),
253
- stdout: '',
254
- stderr: ''
275
+ stdout: "",
276
+ stderr: "",
255
277
  };
256
278
  }
257
279
 
258
- if (res.error?.code === 'ENOENT') {
280
+ if (res.error?.code === "ENOENT") {
259
281
  return {
260
282
  code: 1,
261
- skippedReason: 'openclaw-cli-missing',
283
+ skippedReason: "openclaw-cli-missing",
262
284
  error: res.error.message,
263
- stdout: res.stdout || '',
264
- stderr: res.stderr || ''
285
+ stdout: res.stdout || "",
286
+ stderr: res.stderr || "",
265
287
  };
266
288
  }
267
289
 
268
290
  if ((res.status || 0) !== 0) {
269
291
  return {
270
292
  code: Number(res.status || 1),
271
- skippedReason: 'openclaw-plugins-install-failed',
272
- error: (res.stderr || res.stdout || '').trim() || 'openclaw plugins install failed',
273
- stdout: res.stdout || '',
274
- stderr: res.stderr || ''
293
+ skippedReason: "openclaw-plugins-install-failed",
294
+ error: (res.stderr || res.stdout || "").trim() || "openclaw plugins install failed",
295
+ stdout: res.stdout || "",
296
+ stderr: res.stderr || "",
275
297
  };
276
298
  }
277
299
 
278
300
  return {
279
301
  code: 0,
280
- stdout: res.stdout || '',
281
- stderr: res.stderr || ''
302
+ stdout: res.stdout || "",
303
+ stderr: res.stderr || "",
282
304
  };
283
305
  }
284
306
 
285
307
  function buildSessionPluginPackageJson() {
286
308
  return `${JSON.stringify(
287
309
  {
288
- name: '@vibeusage/openclaw-session-sync',
289
- version: '0.0.0',
310
+ name: "@vibeusage/openclaw-session-sync",
311
+ version: "0.0.0",
290
312
  private: true,
291
- type: 'module',
313
+ type: "module",
292
314
  openclaw: {
293
- extensions: ['./index.js']
294
- }
315
+ extensions: ["./index.js"],
316
+ },
295
317
  },
296
318
  null,
297
- 2
319
+ 2,
298
320
  )}\n`;
299
321
  }
300
322
 
@@ -302,25 +324,26 @@ function buildSessionPluginMeta() {
302
324
  return `${JSON.stringify(
303
325
  {
304
326
  id: OPENCLAW_SESSION_PLUGIN_ID,
305
- name: 'VibeUsage OpenClaw Session Sync',
306
- description: 'Trigger vibeusage sync on OpenClaw agent/session lifecycle events.',
327
+ name: "VibeUsage OpenClaw Session Sync",
328
+ description: "Trigger vibeusage sync on OpenClaw agent/session lifecycle events.",
307
329
  configSchema: {
308
- type: 'object',
330
+ type: "object",
309
331
  additionalProperties: false,
310
- properties: {}
311
- }
332
+ properties: {},
333
+ },
312
334
  },
313
335
  null,
314
- 2
336
+ 2,
315
337
  )}\n`;
316
338
  }
317
339
 
318
- function buildSessionPluginIndex({ trackerDir, packageName = 'vibeusage', openclawHome }) {
319
- const trackerBinPath = path.join(trackerDir, 'app', 'bin', 'tracker.js');
320
- const fallbackPkg = packageName || 'vibeusage';
321
- const safeOpenclawHome = openclawHome || path.join(os.homedir(), '.openclaw');
340
+ function buildSessionPluginIndex({ trackerDir, packageName = "vibeusage", openclawHome }) {
341
+ const trackerBinPath = path.join(trackerDir, "app", "bin", "tracker.js");
342
+ const fallbackPkg = packageName || "vibeusage";
343
+ const safeOpenclawHome = openclawHome || path.join(os.homedir(), ".openclaw");
322
344
 
323
- return `import fs from 'node:fs';\n` +
345
+ return (
346
+ `import fs from 'node:fs';\n` +
324
347
  `import path from 'node:path';\n` +
325
348
  `import cp from 'node:child_process';\n` +
326
349
  `\n` +
@@ -476,11 +499,12 @@ function buildSessionPluginIndex({ trackerDir, packageName = 'vibeusage', opencl
476
499
  ` const ms = n < 1e12 ? Math.floor(n * 1000) : Math.floor(n);\n` +
477
500
  ` const d = new Date(ms);\n` +
478
501
  ` return Number.isNaN(d.getTime()) ? null : d.toISOString();\n` +
479
- `}\n`;
502
+ `}\n`
503
+ );
480
504
  }
481
505
 
482
506
  function normalizeString(value) {
483
- if (typeof value !== 'string') return null;
507
+ if (typeof value !== "string") return null;
484
508
  const trimmed = value.trim();
485
509
  return trimmed.length > 0 ? trimmed : null;
486
510
  }
@@ -492,5 +516,5 @@ module.exports = {
492
516
  ensureOpenclawSessionPluginFiles,
493
517
  installOpenclawSessionPlugin,
494
518
  probeOpenclawSessionPluginState,
495
- removeOpenclawSessionPluginConfig
519
+ removeOpenclawSessionPluginConfig,
496
520
  };
@@ -1,28 +1,30 @@
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 } = require('./fs');
5
+ const { ensureDir } = require("./fs");
6
6
 
7
- const DEFAULT_PLUGIN_NAME = 'vibeusage-tracker.js';
8
- const PLUGIN_MARKER = 'VIBEUSAGE_TRACKER_PLUGIN';
9
- const DEFAULT_EVENT = 'session.updated';
7
+ const DEFAULT_PLUGIN_NAME = "vibeusage-tracker.js";
8
+ const PLUGIN_MARKER = "VIBEUSAGE_TRACKER_PLUGIN";
9
+ const DEFAULT_EVENT = "session.updated";
10
10
 
11
11
  function resolveOpencodeConfigDir({ home = os.homedir(), env = process.env } = {}) {
12
- const explicit = typeof env.OPENCODE_CONFIG_DIR === 'string' ? env.OPENCODE_CONFIG_DIR.trim() : '';
12
+ const explicit =
13
+ typeof env.OPENCODE_CONFIG_DIR === "string" ? env.OPENCODE_CONFIG_DIR.trim() : "";
13
14
  if (explicit) return path.resolve(explicit);
14
- const xdg = typeof env.XDG_CONFIG_HOME === 'string' ? env.XDG_CONFIG_HOME.trim() : '';
15
- const base = xdg || path.join(home, '.config');
16
- return path.join(base, 'opencode');
15
+ const xdg = typeof env.XDG_CONFIG_HOME === "string" ? env.XDG_CONFIG_HOME.trim() : "";
16
+ const base = xdg || path.join(home, ".config");
17
+ return path.join(base, "opencode");
17
18
  }
18
19
 
19
20
  function resolveOpencodePluginDir({ configDir }) {
20
- return path.join(configDir, 'plugin');
21
+ return path.join(configDir, "plugin");
21
22
  }
22
23
 
23
24
  function buildOpencodePlugin({ notifyPath }) {
24
- const safeNotifyPath = typeof notifyPath === 'string' ? notifyPath : '';
25
- return `// ${PLUGIN_MARKER}\n` +
25
+ const safeNotifyPath = typeof notifyPath === "string" ? notifyPath : "";
26
+ return (
27
+ `// ${PLUGIN_MARKER}\n` +
26
28
  `const notifyPath = ${JSON.stringify(safeNotifyPath)};\n` +
27
29
  `export const VibeUsagePlugin = async ({ $ }) => {\n` +
28
30
  ` return {\n` +
@@ -30,24 +32,21 @@ function buildOpencodePlugin({ notifyPath }) {
30
32
  ` if (!event || event.type !== ${JSON.stringify(DEFAULT_EVENT)}) return;\n` +
31
33
  ` try {\n` +
32
34
  ` if (!notifyPath) return;\n` +
33
- ` const proc = $\`/usr/bin/env node ${'${notifyPath}'} --source=opencode\`;\n` +
35
+ ` const proc = $\`/usr/bin/env node ${"${notifyPath}"} --source=opencode\`;\n` +
34
36
  ` if (proc && typeof proc.catch === 'function') proc.catch(() => {});\n` +
35
37
  ` } catch (_) {}\n` +
36
38
  ` }\n` +
37
39
  ` };\n` +
38
- `};\n`;
40
+ `};\n`
41
+ );
39
42
  }
40
43
 
41
- async function upsertOpencodePlugin({
42
- configDir,
43
- notifyPath,
44
- pluginName = DEFAULT_PLUGIN_NAME
45
- }) {
46
- if (!configDir) return { changed: false, pluginPath: null, skippedReason: 'config-missing' };
44
+ async function upsertOpencodePlugin({ configDir, notifyPath, pluginName = DEFAULT_PLUGIN_NAME }) {
45
+ if (!configDir) return { changed: false, pluginPath: null, skippedReason: "config-missing" };
47
46
  const pluginDir = resolveOpencodePluginDir({ configDir });
48
47
  const pluginPath = path.join(pluginDir, pluginName);
49
48
  const next = buildOpencodePlugin({ notifyPath });
50
- const existing = await fs.readFile(pluginPath, 'utf8').catch(() => null);
49
+ const existing = await fs.readFile(pluginPath, "utf8").catch(() => null);
51
50
 
52
51
  if (existing === next) {
53
52
  return { changed: false, pluginPath, skippedReason: null };
@@ -57,20 +56,20 @@ async function upsertOpencodePlugin({
57
56
 
58
57
  let backupPath = null;
59
58
  if (existing != null) {
60
- backupPath = `${pluginPath}.bak.${new Date().toISOString().replace(/[:.]/g, '-')}`;
59
+ backupPath = `${pluginPath}.bak.${new Date().toISOString().replace(/[:.]/g, "-")}`;
61
60
  await fs.copyFile(pluginPath, backupPath).catch(() => {});
62
61
  }
63
62
 
64
- await fs.writeFile(pluginPath, next, 'utf8');
63
+ await fs.writeFile(pluginPath, next, "utf8");
65
64
  return { changed: true, pluginPath, backupPath, skippedReason: null };
66
65
  }
67
66
 
68
67
  async function removeOpencodePlugin({ configDir, pluginName = DEFAULT_PLUGIN_NAME }) {
69
- if (!configDir) return { removed: false, skippedReason: 'config-missing' };
68
+ if (!configDir) return { removed: false, skippedReason: "config-missing" };
70
69
  const pluginPath = path.join(resolveOpencodePluginDir({ configDir }), pluginName);
71
- const existing = await fs.readFile(pluginPath, 'utf8').catch(() => null);
72
- if (existing == null) return { removed: false, skippedReason: 'plugin-missing' };
73
- if (!hasPluginMarker(existing)) return { removed: false, skippedReason: 'unexpected-content' };
70
+ const existing = await fs.readFile(pluginPath, "utf8").catch(() => null);
71
+ if (existing == null) return { removed: false, skippedReason: "plugin-missing" };
72
+ if (!hasPluginMarker(existing)) return { removed: false, skippedReason: "unexpected-content" };
74
73
  await fs.unlink(pluginPath).catch(() => {});
75
74
  return { removed: true, skippedReason: null };
76
75
  }
@@ -78,13 +77,13 @@ async function removeOpencodePlugin({ configDir, pluginName = DEFAULT_PLUGIN_NAM
78
77
  async function isOpencodePluginInstalled({ configDir, pluginName = DEFAULT_PLUGIN_NAME }) {
79
78
  if (!configDir) return false;
80
79
  const pluginPath = path.join(resolveOpencodePluginDir({ configDir }), pluginName);
81
- const existing = await fs.readFile(pluginPath, 'utf8').catch(() => null);
80
+ const existing = await fs.readFile(pluginPath, "utf8").catch(() => null);
82
81
  if (!existing) return false;
83
82
  return hasPluginMarker(existing);
84
83
  }
85
84
 
86
85
  function hasPluginMarker(text) {
87
- return typeof text === 'string' && text.includes(PLUGIN_MARKER);
86
+ return typeof text === "string" && text.includes(PLUGIN_MARKER);
88
87
  }
89
88
 
90
89
  module.exports = {
@@ -96,5 +95,5 @@ module.exports = {
96
95
  buildOpencodePlugin,
97
96
  upsertOpencodePlugin,
98
97
  removeOpencodePlugin,
99
- isOpencodePluginInstalled
98
+ isOpencodePluginInstalled,
100
99
  };