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.
- package/README.md +306 -173
- package/README.old.md +324 -0
- package/README.zh-CN.md +304 -188
- package/package.json +32 -32
- package/src/cli.js +37 -37
- package/src/commands/activate-if-needed.js +41 -0
- package/src/commands/diagnostics.js +8 -9
- package/src/commands/doctor.js +31 -26
- package/src/commands/init.js +285 -218
- package/src/commands/status.js +86 -83
- package/src/commands/sync.js +178 -130
- package/src/commands/uninstall.js +66 -62
- package/src/lib/activation-check.js +341 -0
- package/src/lib/browser-auth.js +52 -54
- package/src/lib/claude-config.js +25 -25
- package/src/lib/cli-ui.js +35 -35
- package/src/lib/codex-config.js +40 -36
- package/src/lib/debug-flags.js +2 -2
- package/src/lib/diagnostics.js +70 -57
- package/src/lib/doctor.js +139 -132
- package/src/lib/fs.js +17 -17
- package/src/lib/gemini-config.js +44 -40
- package/src/lib/init-flow.js +16 -22
- package/src/lib/insforge-client.js +10 -10
- package/src/lib/insforge.js +9 -3
- package/src/lib/openclaw-hook.js +89 -66
- package/src/lib/openclaw-session-plugin.js +116 -92
- package/src/lib/opencode-config.js +31 -32
- package/src/lib/opencode-usage-audit.js +34 -31
- package/src/lib/progress.js +12 -13
- package/src/lib/project-usage-purge.js +23 -17
- package/src/lib/prompt.js +8 -4
- package/src/lib/rollout.js +342 -241
- package/src/lib/runtime-config.js +34 -22
- package/src/lib/subscriptions.js +94 -92
- package/src/lib/tracker-paths.js +6 -6
- package/src/lib/upload-throttle.js +35 -16
- package/src/lib/uploader.js +33 -29
- package/src/lib/vibeusage-api.js +72 -56
- package/src/lib/vibeusage-public-repo.js +41 -24
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
const os = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const fs = require(
|
|
4
|
-
const fssync = require(
|
|
5
|
-
const cp = require(
|
|
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 =
|
|
8
|
-
const OPENCLAW_SESSION_PLUGIN_DIRNAME =
|
|
7
|
+
const OPENCLAW_SESSION_PLUGIN_ID = "openclaw-session-sync";
|
|
8
|
+
const OPENCLAW_SESSION_PLUGIN_DIRNAME = "openclaw-plugin";
|
|
9
9
|
|
|
10
|
-
function resolveOpenclawSessionPluginPaths({
|
|
11
|
-
|
|
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,
|
|
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,
|
|
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 =
|
|
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([
|
|
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([
|
|
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 ||
|
|
61
|
-
stderr: `${installResult.stderr ||
|
|
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 ||
|
|
75
|
-
stderr: `${installResult.stderr ||
|
|
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({
|
|
81
|
-
|
|
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,
|
|
87
|
-
const pluginMetaPath = path.join(pluginEntryDir,
|
|
88
|
-
const indexPath = path.join(pluginEntryDir,
|
|
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(),
|
|
91
|
-
await fs.writeFile(pluginMetaPath, buildSessionPluginMeta(),
|
|
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(),
|
|
106
|
+
openclawHome: openclawHome || path.join(os.homedir(), ".openclaw"),
|
|
98
107
|
}),
|
|
99
|
-
|
|
108
|
+
"utf8",
|
|
100
109
|
);
|
|
101
110
|
}
|
|
102
111
|
|
|
103
|
-
async function probeOpenclawSessionPluginState({
|
|
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,
|
|
109
|
-
fssync.existsSync(path.join(pluginEntryDir,
|
|
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,
|
|
126
|
+
const raw = await fs.readFile(openclawConfigPath, "utf8");
|
|
114
127
|
cfg = JSON.parse(raw);
|
|
115
128
|
} catch (err) {
|
|
116
|
-
if (err?.code ===
|
|
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:
|
|
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:
|
|
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(
|
|
157
|
+
const linked = loadPaths.some(
|
|
158
|
+
(entry) => path.resolve(String(entry || "")) === normalizedPluginEntryDir,
|
|
159
|
+
);
|
|
145
160
|
|
|
146
|
-
const 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({
|
|
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,
|
|
186
|
+
cfg = JSON.parse(await fs.readFile(openclawConfigPath, "utf8"));
|
|
167
187
|
} catch (err) {
|
|
168
|
-
if (err?.code ===
|
|
169
|
-
return { removed: false, skippedReason:
|
|
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:
|
|
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(
|
|
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 ===
|
|
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`,
|
|
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(
|
|
265
|
+
res = cp.spawnSync("openclaw", args, {
|
|
244
266
|
env,
|
|
245
|
-
encoding:
|
|
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 ===
|
|
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 ===
|
|
280
|
+
if (res.error?.code === "ENOENT") {
|
|
259
281
|
return {
|
|
260
282
|
code: 1,
|
|
261
|
-
skippedReason:
|
|
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:
|
|
272
|
-
error: (res.stderr || res.stdout ||
|
|
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:
|
|
289
|
-
version:
|
|
310
|
+
name: "@vibeusage/openclaw-session-sync",
|
|
311
|
+
version: "0.0.0",
|
|
290
312
|
private: true,
|
|
291
|
-
type:
|
|
313
|
+
type: "module",
|
|
292
314
|
openclaw: {
|
|
293
|
-
extensions: [
|
|
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:
|
|
306
|
-
description:
|
|
327
|
+
name: "VibeUsage OpenClaw Session Sync",
|
|
328
|
+
description: "Trigger vibeusage sync on OpenClaw agent/session lifecycle events.",
|
|
307
329
|
configSchema: {
|
|
308
|
-
type:
|
|
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 =
|
|
319
|
-
const trackerBinPath = path.join(trackerDir,
|
|
320
|
-
const fallbackPkg = packageName ||
|
|
321
|
-
const safeOpenclawHome = openclawHome || path.join(os.homedir(),
|
|
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
|
|
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 !==
|
|
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(
|
|
2
|
-
const path = require(
|
|
3
|
-
const fs = require(
|
|
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(
|
|
5
|
+
const { ensureDir } = require("./fs");
|
|
6
6
|
|
|
7
|
-
const DEFAULT_PLUGIN_NAME =
|
|
8
|
-
const PLUGIN_MARKER =
|
|
9
|
-
const DEFAULT_EVENT =
|
|
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 =
|
|
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 ===
|
|
15
|
-
const base = xdg || path.join(home,
|
|
16
|
-
return path.join(base,
|
|
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,
|
|
21
|
+
return path.join(configDir, "plugin");
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
function buildOpencodePlugin({ notifyPath }) {
|
|
24
|
-
const safeNotifyPath = typeof notifyPath ===
|
|
25
|
-
return
|
|
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 ${
|
|
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,
|
|
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,
|
|
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:
|
|
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,
|
|
72
|
-
if (existing == null) return { removed: false, skippedReason:
|
|
73
|
-
if (!hasPluginMarker(existing)) return { removed: false, skippedReason:
|
|
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,
|
|
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 ===
|
|
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
|
};
|