vibeusage 0.3.2 → 0.3.4
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 +230 -334
- package/README.zh-CN.md +230 -317
- package/package.json +1 -1
- package/src/commands/status.js +1 -1
- package/src/commands/uninstall.js +14 -5
- package/src/lib/claude-plugin.js +381 -0
- package/src/lib/diagnostics.js +2 -2
- package/src/lib/doctor.js +1 -1
- package/src/lib/integrations/claude.js +79 -31
- package/src/lib/openclaw-session-plugin.js +68 -4
package/package.json
CHANGED
package/src/commands/status.js
CHANGED
|
@@ -108,7 +108,7 @@ async function cmdStatus(argv = []) {
|
|
|
108
108
|
autoRetryLine,
|
|
109
109
|
`- Codex notify: ${renderIntegrationStatus(descriptors.get("codex"), codexProbe)}`,
|
|
110
110
|
`- Every Code notify: ${renderIntegrationStatus(descriptors.get("every-code"), everyCodeProbe)}`,
|
|
111
|
-
`- Claude
|
|
111
|
+
`- Claude plugin: ${renderIntegrationStatus(descriptors.get("claude"), claudeProbe)}`,
|
|
112
112
|
`- Gemini hooks: ${renderIntegrationStatus(descriptors.get("gemini"), geminiProbe)}`,
|
|
113
113
|
`- Opencode plugin: ${renderIntegrationStatus(descriptors.get("opencode"), opencodeProbe)}`,
|
|
114
114
|
`- OpenCode SQLite DB: ${opencodeDbPresent ? "present" : "missing"}`,
|
|
@@ -18,7 +18,7 @@ async function cmdUninstall(argv) {
|
|
|
18
18
|
});
|
|
19
19
|
const codexConfigExists = await isFile(integrationContext.codex.configPath);
|
|
20
20
|
const codeConfigExists = await isFile(integrationContext.everyCode.configPath);
|
|
21
|
-
const claudeConfigExists = await
|
|
21
|
+
const claudeConfigExists = await isDir(integrationContext.claude.configDir);
|
|
22
22
|
const geminiConfigExists = await isDir(integrationContext.gemini.configDir);
|
|
23
23
|
const opencodeConfigExists = await isDir(integrationContext.opencode.configDir);
|
|
24
24
|
const integrationResults = await uninstallIntegrations(integrationContext);
|
|
@@ -58,11 +58,11 @@ async function cmdUninstall(argv) {
|
|
|
58
58
|
renderHookLine({
|
|
59
59
|
exists: claudeConfigExists,
|
|
60
60
|
result: resultByName.get("claude"),
|
|
61
|
-
missingText:
|
|
61
|
+
missingText: `- Claude plugin: skipped (${integrationContext.claude.configDir} not found)`,
|
|
62
62
|
removedText: (result) =>
|
|
63
|
-
`- Claude
|
|
64
|
-
noChangeText: "- Claude
|
|
65
|
-
skippedText: "- Claude
|
|
63
|
+
`- Claude plugin removed: ${result.detail || integrationContext.claude.settingsPath}`,
|
|
64
|
+
noChangeText: "- Claude plugin: no change",
|
|
65
|
+
skippedText: "- Claude plugin: skipped",
|
|
66
66
|
}),
|
|
67
67
|
renderHookLine({
|
|
68
68
|
exists: geminiConfigExists,
|
|
@@ -120,6 +120,15 @@ async function isFile(p) {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
async function isDir(p) {
|
|
124
|
+
try {
|
|
125
|
+
const st = await fs.stat(p);
|
|
126
|
+
return st.isDirectory();
|
|
127
|
+
} catch (_e) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
123
132
|
function renderRestoreLine({ exists, result, missingText, restoredText, noChangeText, skippedText }) {
|
|
124
133
|
if (!exists) return missingText;
|
|
125
134
|
if (!result) return noChangeText;
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
const os = require("node:os");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const fs = require("node:fs/promises");
|
|
4
|
+
const cp = require("node:child_process");
|
|
5
|
+
|
|
6
|
+
const { ensureDir, readJsonStrict, writeFileAtomic } = require("./fs");
|
|
7
|
+
const { buildClaudeHookCommand } = require("./claude-config");
|
|
8
|
+
|
|
9
|
+
const CLAUDE_PLUGIN_MARKETPLACE_NAME = "vibeusage-local";
|
|
10
|
+
const CLAUDE_PLUGIN_ID = "vibeusage-claude-sync";
|
|
11
|
+
const CLAUDE_PLUGIN_VERSION = "0.0.0";
|
|
12
|
+
|
|
13
|
+
function resolveClaudePluginPaths({ home = os.homedir(), trackerDir } = {}) {
|
|
14
|
+
if (!trackerDir) throw new Error("trackerDir is required");
|
|
15
|
+
|
|
16
|
+
const claudeDir = path.join(home, ".claude");
|
|
17
|
+
const pluginsDir = path.join(claudeDir, "plugins");
|
|
18
|
+
const marketplaceDir = path.join(trackerDir, "claude-marketplace");
|
|
19
|
+
const pluginRootDir = path.join(marketplaceDir, "plugins", CLAUDE_PLUGIN_ID);
|
|
20
|
+
const pluginRef = `${CLAUDE_PLUGIN_ID}@${CLAUDE_PLUGIN_MARKETPLACE_NAME}`;
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
claudeDir,
|
|
24
|
+
settingsPath: path.join(claudeDir, "settings.json"),
|
|
25
|
+
pluginsDir,
|
|
26
|
+
knownMarketplacesPath: path.join(pluginsDir, "known_marketplaces.json"),
|
|
27
|
+
installedPluginsPath: path.join(pluginsDir, "installed_plugins.json"),
|
|
28
|
+
marketplaceDir,
|
|
29
|
+
marketplaceManifestPath: path.join(marketplaceDir, ".claude-plugin", "marketplace.json"),
|
|
30
|
+
pluginRootDir,
|
|
31
|
+
pluginManifestPath: path.join(pluginRootDir, ".claude-plugin", "plugin.json"),
|
|
32
|
+
pluginHooksPath: path.join(pluginRootDir, "hooks", "hooks.json"),
|
|
33
|
+
pluginRef,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function ensureClaudePluginFiles({ trackerDir, notifyPath } = {}) {
|
|
38
|
+
if (!trackerDir || !notifyPath) {
|
|
39
|
+
throw new Error("trackerDir and notifyPath are required");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const paths = resolveClaudePluginPaths({ trackerDir });
|
|
43
|
+
|
|
44
|
+
await ensureDir(path.dirname(paths.marketplaceManifestPath));
|
|
45
|
+
await ensureDir(path.dirname(paths.pluginManifestPath));
|
|
46
|
+
await ensureDir(path.dirname(paths.pluginHooksPath));
|
|
47
|
+
|
|
48
|
+
await writeFileAtomic(paths.marketplaceManifestPath, buildMarketplaceManifest());
|
|
49
|
+
await writeFileAtomic(paths.pluginManifestPath, buildPluginManifest());
|
|
50
|
+
await writeFileAtomic(paths.pluginHooksPath, buildPluginHooks({ notifyPath }));
|
|
51
|
+
|
|
52
|
+
return paths;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function probeClaudePluginState({ home = os.homedir(), trackerDir } = {}) {
|
|
56
|
+
const paths = resolveClaudePluginPaths({ home, trackerDir });
|
|
57
|
+
const settings = await readJsonStrict(paths.settingsPath);
|
|
58
|
+
const known = await readJsonStrict(paths.knownMarketplacesPath);
|
|
59
|
+
const installed = await readJsonStrict(paths.installedPluginsPath);
|
|
60
|
+
const pluginFilesReady =
|
|
61
|
+
(await isFile(paths.marketplaceManifestPath)) &&
|
|
62
|
+
(await isFile(paths.pluginManifestPath)) &&
|
|
63
|
+
(await isFile(paths.pluginHooksPath));
|
|
64
|
+
|
|
65
|
+
if (settings.status === "invalid" || settings.status === "error") {
|
|
66
|
+
return unreadableState(paths, "Claude settings unreadable");
|
|
67
|
+
}
|
|
68
|
+
if (known.status === "invalid" || known.status === "error") {
|
|
69
|
+
return unreadableState(paths, "Claude marketplace registry unreadable");
|
|
70
|
+
}
|
|
71
|
+
if (installed.status === "invalid" || installed.status === "error") {
|
|
72
|
+
return unreadableState(paths, "Claude plugin registry unreadable");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const enabled = settings.value?.enabledPlugins?.[paths.pluginRef] === true;
|
|
76
|
+
const declaredMarketplace = marketplaceMatchesPath({
|
|
77
|
+
marketplaceEntry: known.value?.[CLAUDE_PLUGIN_MARKETPLACE_NAME],
|
|
78
|
+
marketplaceDir: paths.marketplaceDir,
|
|
79
|
+
});
|
|
80
|
+
const installedEntries = Array.isArray(installed.value?.plugins?.[paths.pluginRef])
|
|
81
|
+
? installed.value.plugins[paths.pluginRef]
|
|
82
|
+
: [];
|
|
83
|
+
const installedUserEntry =
|
|
84
|
+
installedEntries.find((entry) => String(entry?.scope || "") === "user") || null;
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
configured: Boolean(enabled && installedUserEntry && pluginFilesReady && declaredMarketplace),
|
|
88
|
+
enabled,
|
|
89
|
+
installed: Boolean(installedUserEntry),
|
|
90
|
+
marketplaceDeclared: declaredMarketplace,
|
|
91
|
+
pluginFilesReady,
|
|
92
|
+
pluginRef: paths.pluginRef,
|
|
93
|
+
unreadable: false,
|
|
94
|
+
detail: null,
|
|
95
|
+
...paths,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function installClaudePlugin({
|
|
100
|
+
home = os.homedir(),
|
|
101
|
+
trackerDir,
|
|
102
|
+
notifyPath,
|
|
103
|
+
env = process.env,
|
|
104
|
+
} = {}) {
|
|
105
|
+
const paths = resolveClaudePluginPaths({ home, trackerDir });
|
|
106
|
+
await ensureClaudePluginFiles({ trackerDir, notifyPath });
|
|
107
|
+
|
|
108
|
+
const initialState = await probeClaudePluginState({ home, trackerDir });
|
|
109
|
+
if (initialState.unreadable) {
|
|
110
|
+
return { configured: false, skippedReason: "claude-config-unreadable", ...initialState };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const marketplaceCmd = initialState.marketplaceDeclared
|
|
114
|
+
? ["plugin", "marketplace", "update", CLAUDE_PLUGIN_MARKETPLACE_NAME]
|
|
115
|
+
: ["plugin", "marketplace", "add", paths.marketplaceDir, "--scope", "user"];
|
|
116
|
+
const marketplaceResult = runClaudeCli(marketplaceCmd, env);
|
|
117
|
+
if (marketplaceResult.skippedReason) {
|
|
118
|
+
return { configured: false, ...paths, ...marketplaceResult };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let actionResult;
|
|
122
|
+
if (!initialState.installed) {
|
|
123
|
+
actionResult = runClaudeCli(["plugin", "install", paths.pluginRef, "--scope", "user"], env);
|
|
124
|
+
} else if (!initialState.enabled) {
|
|
125
|
+
actionResult = runClaudeCli(["plugin", "enable", paths.pluginRef, "--scope", "user"], env);
|
|
126
|
+
} else {
|
|
127
|
+
actionResult = runClaudeCli(["plugin", "update", paths.pluginRef, "--scope", "user"], env);
|
|
128
|
+
}
|
|
129
|
+
if (actionResult.skippedReason) {
|
|
130
|
+
return { configured: false, ...paths, ...actionResult };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const nextState = await probeClaudePluginState({ home, trackerDir });
|
|
134
|
+
return {
|
|
135
|
+
configured: nextState.configured,
|
|
136
|
+
changed: !initialState.configured || !initialState.enabled || !initialState.marketplaceDeclared,
|
|
137
|
+
stdout: `${marketplaceResult.stdout || ""}\n${actionResult.stdout || ""}`.trim(),
|
|
138
|
+
stderr: `${marketplaceResult.stderr || ""}\n${actionResult.stderr || ""}`.trim(),
|
|
139
|
+
...nextState,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function removeClaudePluginConfig({
|
|
144
|
+
home = os.homedir(),
|
|
145
|
+
trackerDir,
|
|
146
|
+
env = process.env,
|
|
147
|
+
} = {}) {
|
|
148
|
+
const paths = resolveClaudePluginPaths({ home, trackerDir });
|
|
149
|
+
const initialState = await probeClaudePluginState({ home, trackerDir });
|
|
150
|
+
const hadMarketplaceDir = await isDir(paths.marketplaceDir);
|
|
151
|
+
|
|
152
|
+
let changed = false;
|
|
153
|
+
let skippedReason = null;
|
|
154
|
+
if (initialState.installed || initialState.enabled) {
|
|
155
|
+
const uninstallResult = runClaudeCli(["plugin", "uninstall", paths.pluginRef, "--scope", "user"], env);
|
|
156
|
+
if (uninstallResult.skippedReason) {
|
|
157
|
+
return { removed: false, ...paths, ...uninstallResult };
|
|
158
|
+
}
|
|
159
|
+
changed = true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const siblingRefs = await listMarketplaceSiblingPluginRefs({
|
|
163
|
+
installedPluginsPath: paths.installedPluginsPath,
|
|
164
|
+
marketplaceName: CLAUDE_PLUGIN_MARKETPLACE_NAME,
|
|
165
|
+
excludePluginRef: paths.pluginRef,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const shouldRemoveMarketplace =
|
|
169
|
+
initialState.marketplaceDeclared && siblingRefs.unreadable !== true && siblingRefs.refs.length === 0;
|
|
170
|
+
|
|
171
|
+
if (shouldRemoveMarketplace) {
|
|
172
|
+
const removeMarketplaceResult = runClaudeCli(
|
|
173
|
+
["plugin", "marketplace", "remove", CLAUDE_PLUGIN_MARKETPLACE_NAME],
|
|
174
|
+
env,
|
|
175
|
+
);
|
|
176
|
+
if (removeMarketplaceResult.skippedReason && removeMarketplaceResult.skippedReason !== "claude-cli-error") {
|
|
177
|
+
return { removed: changed, ...paths, ...removeMarketplaceResult };
|
|
178
|
+
}
|
|
179
|
+
changed = true;
|
|
180
|
+
} else if (!changed) {
|
|
181
|
+
skippedReason = "plugin-missing";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (shouldRemoveMarketplace) {
|
|
185
|
+
await fs.rm(paths.marketplaceDir, { recursive: true, force: true }).catch(() => {});
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
removed: changed || (hadMarketplaceDir && shouldRemoveMarketplace),
|
|
189
|
+
skippedReason,
|
|
190
|
+
siblingPluginRefs: siblingRefs.refs,
|
|
191
|
+
...paths,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function runClaudeCli(args, env = process.env) {
|
|
196
|
+
let res;
|
|
197
|
+
try {
|
|
198
|
+
res = cp.spawnSync("claude", args, {
|
|
199
|
+
env,
|
|
200
|
+
encoding: "utf8",
|
|
201
|
+
timeout: 30_000,
|
|
202
|
+
});
|
|
203
|
+
} catch (err) {
|
|
204
|
+
return {
|
|
205
|
+
code: 1,
|
|
206
|
+
skippedReason: err?.code === "ENOENT" ? "claude-cli-missing" : "claude-cli-error",
|
|
207
|
+
error: err?.message || String(err),
|
|
208
|
+
stdout: "",
|
|
209
|
+
stderr: "",
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (res.error?.code === "ENOENT") {
|
|
214
|
+
return {
|
|
215
|
+
code: 1,
|
|
216
|
+
skippedReason: "claude-cli-missing",
|
|
217
|
+
error: res.error.message,
|
|
218
|
+
stdout: res.stdout || "",
|
|
219
|
+
stderr: res.stderr || "",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if ((res.status || 0) !== 0) {
|
|
224
|
+
return {
|
|
225
|
+
code: Number(res.status || 1),
|
|
226
|
+
skippedReason: "claude-cli-error",
|
|
227
|
+
error: (res.stderr || res.stdout || "").trim() || "claude plugin command failed",
|
|
228
|
+
stdout: res.stdout || "",
|
|
229
|
+
stderr: res.stderr || "",
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
code: 0,
|
|
235
|
+
stdout: res.stdout || "",
|
|
236
|
+
stderr: res.stderr || "",
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function buildMarketplaceManifest() {
|
|
241
|
+
return `${JSON.stringify(
|
|
242
|
+
{
|
|
243
|
+
$schema: "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
244
|
+
name: CLAUDE_PLUGIN_MARKETPLACE_NAME,
|
|
245
|
+
description: "Local VibeUsage Claude plugin marketplace.",
|
|
246
|
+
owner: {
|
|
247
|
+
name: "VibeUsage",
|
|
248
|
+
email: "support@vibeusage.cc",
|
|
249
|
+
},
|
|
250
|
+
version: CLAUDE_PLUGIN_VERSION,
|
|
251
|
+
plugins: [
|
|
252
|
+
{
|
|
253
|
+
name: CLAUDE_PLUGIN_ID,
|
|
254
|
+
source: `./plugins/${CLAUDE_PLUGIN_ID}`,
|
|
255
|
+
description: "Trigger VibeUsage Claude notify bridge on Claude session lifecycle events.",
|
|
256
|
+
version: CLAUDE_PLUGIN_VERSION,
|
|
257
|
+
strict: false,
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
null,
|
|
262
|
+
2,
|
|
263
|
+
)}\n`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function buildPluginManifest() {
|
|
267
|
+
return `${JSON.stringify(
|
|
268
|
+
{
|
|
269
|
+
name: CLAUDE_PLUGIN_ID,
|
|
270
|
+
description: "Trigger VibeUsage Claude notify bridge on Claude session lifecycle events.",
|
|
271
|
+
version: CLAUDE_PLUGIN_VERSION,
|
|
272
|
+
},
|
|
273
|
+
null,
|
|
274
|
+
2,
|
|
275
|
+
)}\n`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function buildPluginHooks({ notifyPath }) {
|
|
279
|
+
const hookCommand = buildClaudeHookCommand(notifyPath);
|
|
280
|
+
return `${JSON.stringify(
|
|
281
|
+
{
|
|
282
|
+
description: "Run VibeUsage Claude notify bridge on Stop and SessionEnd events.",
|
|
283
|
+
hooks: {
|
|
284
|
+
Stop: [
|
|
285
|
+
{
|
|
286
|
+
hooks: [{ type: "command", command: hookCommand }],
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
SessionEnd: [
|
|
290
|
+
{
|
|
291
|
+
hooks: [{ type: "command", command: hookCommand }],
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
null,
|
|
297
|
+
2,
|
|
298
|
+
)}\n`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function unreadableState(paths, detail) {
|
|
302
|
+
return {
|
|
303
|
+
configured: false,
|
|
304
|
+
enabled: false,
|
|
305
|
+
installed: false,
|
|
306
|
+
marketplaceDeclared: false,
|
|
307
|
+
pluginFilesReady: false,
|
|
308
|
+
pluginRef: paths.pluginRef,
|
|
309
|
+
unreadable: true,
|
|
310
|
+
detail,
|
|
311
|
+
...paths,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function marketplaceMatchesPath({ marketplaceEntry, marketplaceDir } = {}) {
|
|
316
|
+
if (!marketplaceEntry || typeof marketplaceEntry !== "object") return false;
|
|
317
|
+
|
|
318
|
+
const source = marketplaceEntry.source;
|
|
319
|
+
if (!source || typeof source !== "object") return false;
|
|
320
|
+
if (source.source !== "path") return false;
|
|
321
|
+
if (typeof source.path !== "string" || source.path.length === 0) return false;
|
|
322
|
+
|
|
323
|
+
return path.resolve(source.path) === path.resolve(marketplaceDir);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function listMarketplaceSiblingPluginRefs({
|
|
327
|
+
installedPluginsPath,
|
|
328
|
+
marketplaceName,
|
|
329
|
+
excludePluginRef,
|
|
330
|
+
} = {}) {
|
|
331
|
+
const installed = await readJsonStrict(installedPluginsPath);
|
|
332
|
+
if (installed.status === "invalid") {
|
|
333
|
+
return { unreadable: true, refs: [] };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const plugins =
|
|
337
|
+
installed.value?.plugins && typeof installed.value.plugins === "object"
|
|
338
|
+
? installed.value.plugins
|
|
339
|
+
: {};
|
|
340
|
+
const refs = Object.entries(plugins)
|
|
341
|
+
.filter(([ref, entries]) => {
|
|
342
|
+
if (ref === excludePluginRef) return false;
|
|
343
|
+
if (!ref.endsWith(`@${marketplaceName}`)) return false;
|
|
344
|
+
return Array.isArray(entries) && entries.length > 0;
|
|
345
|
+
})
|
|
346
|
+
.map(([ref]) => ref);
|
|
347
|
+
|
|
348
|
+
return { unreadable: false, refs };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function isNonEmptyObject(value) {
|
|
352
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function isFile(targetPath) {
|
|
356
|
+
try {
|
|
357
|
+
const stat = await fs.stat(targetPath);
|
|
358
|
+
return stat.isFile();
|
|
359
|
+
} catch (_err) {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function isDir(targetPath) {
|
|
365
|
+
try {
|
|
366
|
+
const stat = await fs.stat(targetPath);
|
|
367
|
+
return stat.isDirectory();
|
|
368
|
+
} catch (_err) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
module.exports = {
|
|
374
|
+
CLAUDE_PLUGIN_MARKETPLACE_NAME,
|
|
375
|
+
CLAUDE_PLUGIN_ID,
|
|
376
|
+
resolveClaudePluginPaths,
|
|
377
|
+
ensureClaudePluginFiles,
|
|
378
|
+
probeClaudePluginState,
|
|
379
|
+
installClaudePlugin,
|
|
380
|
+
removeClaudePluginConfig,
|
|
381
|
+
};
|
package/src/lib/diagnostics.js
CHANGED
|
@@ -141,8 +141,8 @@ async function collectTrackerDiagnostics({
|
|
|
141
141
|
every_code_notify_status: everyCodeProbe?.status || "unknown",
|
|
142
142
|
every_code_notify_configured: Boolean(everyCodeProbe?.configured),
|
|
143
143
|
every_code_notify: everyCodeNotify,
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
claude_plugin_status: claudeProbe?.status || "unknown",
|
|
145
|
+
claude_plugin_configured: Boolean(claudeProbe?.configured),
|
|
146
146
|
gemini_hook_status: geminiProbe?.status || "unknown",
|
|
147
147
|
gemini_hook_configured: Boolean(geminiProbe?.configured),
|
|
148
148
|
opencode_plugin_status: opencodeProbe?.status || "unknown",
|
package/src/lib/doctor.js
CHANGED
|
@@ -310,7 +310,7 @@ function buildDiagnosticsChecks(diagnostics) {
|
|
|
310
310
|
const notifyConfigured = Boolean(
|
|
311
311
|
notify.codex_notify_configured ||
|
|
312
312
|
notify.every_code_notify_configured ||
|
|
313
|
-
notify.
|
|
313
|
+
notify.claude_plugin_configured ||
|
|
314
314
|
notify.gemini_hook_configured ||
|
|
315
315
|
notify.opencode_plugin_configured ||
|
|
316
316
|
notify.openclaw_session_plugin_configured,
|
|
@@ -1,81 +1,129 @@
|
|
|
1
|
-
const { probeClaudeHook,
|
|
1
|
+
const { probeClaudeHook, removeClaudeHook } = require("../claude-config");
|
|
2
|
+
const {
|
|
3
|
+
installClaudePlugin,
|
|
4
|
+
probeClaudePluginState,
|
|
5
|
+
removeClaudePluginConfig,
|
|
6
|
+
} = require("../claude-plugin");
|
|
2
7
|
const { isDir, isFile } = require("./utils");
|
|
3
8
|
|
|
4
9
|
module.exports = {
|
|
5
10
|
name: "claude",
|
|
6
11
|
summaryLabel: "Claude",
|
|
7
|
-
statusLabel: "Claude
|
|
12
|
+
statusLabel: "Claude plugin",
|
|
8
13
|
async probe(ctx) {
|
|
9
14
|
const hasConfigDir = await isDir(ctx.claude.configDir);
|
|
10
15
|
if (!hasConfigDir) {
|
|
11
16
|
return baseProbe(this, { status: "not_installed", detail: "Config not found" });
|
|
12
17
|
}
|
|
13
18
|
|
|
19
|
+
const pluginState = await probeClaudePluginState({
|
|
20
|
+
home: ctx.home,
|
|
21
|
+
trackerDir: ctx.trackerPaths.trackerDir,
|
|
22
|
+
});
|
|
23
|
+
if (pluginState.unreadable) {
|
|
24
|
+
return baseProbe(this, { status: "unreadable", detail: pluginState.detail });
|
|
25
|
+
}
|
|
26
|
+
if (pluginState.configured) {
|
|
27
|
+
return baseProbe(this, {
|
|
28
|
+
status: "ready",
|
|
29
|
+
detail: "Plugin installed",
|
|
30
|
+
configured: true,
|
|
31
|
+
pluginState,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
14
35
|
const hasSettings = await isFile(ctx.claude.settingsPath);
|
|
15
36
|
if (!hasSettings) {
|
|
16
|
-
return baseProbe(this, {
|
|
37
|
+
return baseProbe(this, {
|
|
38
|
+
status: pluginState.pluginFilesReady || pluginState.marketplaceDeclared ? "drifted" : "not_installed",
|
|
39
|
+
detail:
|
|
40
|
+
pluginState.pluginFilesReady || pluginState.marketplaceDeclared
|
|
41
|
+
? "Run vibeusage init to reconcile plugin"
|
|
42
|
+
: "Run vibeusage init to install plugin",
|
|
43
|
+
pluginState,
|
|
44
|
+
});
|
|
17
45
|
}
|
|
18
46
|
|
|
19
47
|
const hookState = await probeClaudeHook({
|
|
20
48
|
settingsPath: ctx.claude.settingsPath,
|
|
21
49
|
hookCommand: ctx.claude.hookCommand,
|
|
22
50
|
});
|
|
23
|
-
|
|
24
|
-
if (hookState.configured) {
|
|
25
|
-
return baseProbe(this, {
|
|
26
|
-
status: "ready",
|
|
27
|
-
detail: "Hooks installed",
|
|
28
|
-
configured: true,
|
|
29
|
-
hookState,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const sessionEndPresent = hookState.eventStates?.SessionEnd === true;
|
|
34
|
-
const stopPresent = hookState.eventStates?.Stop === true;
|
|
35
|
-
const status = hookState.anyPresent && sessionEndPresent && !stopPresent
|
|
51
|
+
const status = hookState.anyPresent
|
|
36
52
|
? "unsupported_legacy"
|
|
37
|
-
:
|
|
53
|
+
: pluginState.installed || pluginState.marketplaceDeclared || pluginState.pluginFilesReady
|
|
54
|
+
? "drifted"
|
|
55
|
+
: "not_installed";
|
|
38
56
|
return baseProbe(this, {
|
|
39
57
|
status,
|
|
40
58
|
detail:
|
|
41
59
|
status === "unsupported_legacy"
|
|
42
60
|
? "Legacy hook config detected; run vibeusage init"
|
|
43
|
-
:
|
|
61
|
+
: status === "drifted"
|
|
62
|
+
? "Run vibeusage init to reconcile plugin"
|
|
63
|
+
: "Run vibeusage init to install plugin",
|
|
44
64
|
hookState,
|
|
65
|
+
pluginState,
|
|
45
66
|
});
|
|
46
67
|
},
|
|
47
68
|
async install(ctx) {
|
|
48
69
|
if (!(await isDir(ctx.claude.configDir))) {
|
|
49
70
|
return action(this, "skipped", false, "Config not found");
|
|
50
71
|
}
|
|
51
|
-
const result = await
|
|
52
|
-
|
|
53
|
-
|
|
72
|
+
const result = await installClaudePlugin({
|
|
73
|
+
home: ctx.home,
|
|
74
|
+
trackerDir: ctx.trackerPaths.trackerDir,
|
|
75
|
+
notifyPath: ctx.notifyPath,
|
|
76
|
+
env: ctx.env,
|
|
54
77
|
});
|
|
78
|
+
if (result.skippedReason === "claude-cli-missing") {
|
|
79
|
+
return action(this, "skipped", false, "Claude CLI not found", {
|
|
80
|
+
skippedReason: result.skippedReason,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (!result.configured) {
|
|
84
|
+
return action(this, "skipped", false, result.error || "Claude plugin install incomplete", {
|
|
85
|
+
skippedReason: result.skippedReason || "claude-plugin-install-incomplete",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (result.configured && (await isFile(ctx.claude.settingsPath))) {
|
|
89
|
+
await removeClaudeHook({
|
|
90
|
+
settingsPath: ctx.claude.settingsPath,
|
|
91
|
+
hookCommand: ctx.claude.hookCommand,
|
|
92
|
+
}).catch(() => {});
|
|
93
|
+
}
|
|
55
94
|
return action(
|
|
56
95
|
this,
|
|
57
96
|
result.changed ? "installed" : "set",
|
|
58
97
|
Boolean(result.changed),
|
|
59
|
-
result.changed ? "
|
|
98
|
+
result.changed ? "Plugin installed" : "Plugin already installed",
|
|
60
99
|
);
|
|
61
100
|
},
|
|
62
101
|
async uninstall(ctx) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
settingsPath: ctx.claude.settingsPath,
|
|
68
|
-
hookCommand: ctx.claude.hookCommand,
|
|
102
|
+
const result = await removeClaudePluginConfig({
|
|
103
|
+
home: ctx.home,
|
|
104
|
+
trackerDir: ctx.trackerPaths.trackerDir,
|
|
105
|
+
env: ctx.env,
|
|
69
106
|
});
|
|
107
|
+
if (await isFile(ctx.claude.settingsPath)) {
|
|
108
|
+
await removeClaudeHook({
|
|
109
|
+
settingsPath: ctx.claude.settingsPath,
|
|
110
|
+
hookCommand: ctx.claude.hookCommand,
|
|
111
|
+
}).catch(() => {});
|
|
112
|
+
}
|
|
113
|
+
if (result.skippedReason === "claude-cli-missing") {
|
|
114
|
+
return action(this, "skipped", false, "Claude CLI not found", {
|
|
115
|
+
skippedReason: result.skippedReason,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
70
118
|
if (result.removed) {
|
|
71
|
-
return action(this, "removed", true,
|
|
119
|
+
return action(this, "removed", true, result.pluginRef || "Claude plugin removed");
|
|
72
120
|
}
|
|
73
|
-
if (result.skippedReason === "
|
|
121
|
+
if (result.skippedReason === "plugin-missing") {
|
|
74
122
|
return action(this, "unchanged", false, "no change", {
|
|
75
123
|
skippedReason: result.skippedReason,
|
|
76
124
|
});
|
|
77
125
|
}
|
|
78
|
-
return action(this, "skipped", false, "
|
|
126
|
+
return action(this, "skipped", false, "Claude plugin not found");
|
|
79
127
|
},
|
|
80
128
|
renderStatusValue(probe) {
|
|
81
129
|
if (probe.status === "ready") return "set";
|