vibeusage 0.2.23 → 0.3.0
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 +13 -14
- package/README.zh-CN.md +7 -2
- package/package.json +4 -5
- package/src/cli.js +2 -2
- package/src/commands/init.js +20 -362
- package/src/commands/status.js +36 -51
- package/src/commands/sync.js +4 -3
- package/src/commands/uninstall.js +121 -104
- package/src/lib/claude-config.js +130 -35
- package/src/lib/diagnostics.js +54 -57
- package/src/lib/doctor.js +27 -0
- package/src/lib/integrations/claude.js +106 -0
- package/src/lib/integrations/codex.js +88 -0
- package/src/lib/integrations/context.js +76 -0
- package/src/lib/integrations/every-code.js +88 -0
- package/src/lib/integrations/gemini.js +86 -0
- package/src/lib/integrations/index.js +85 -0
- package/src/lib/integrations/openclaw-legacy.js +123 -0
- package/src/lib/integrations/openclaw-session.js +132 -0
- package/src/lib/integrations/opencode.js +86 -0
- package/src/lib/integrations/utils.js +39 -0
- package/src/lib/runtime-config.js +7 -5
- package/src/lib/vibeusage-api.js +9 -5
- package/src/shared/copy-registry.cjs +142 -0
- package/src/shared/copy-registry.cjs.d.ts +33 -0
- package/src/shared/runtime-defaults.cjs +11 -0
- package/src/shared/runtime-defaults.cjs.d.ts +3 -0
- package/src/shared/vibeusage-function-contract.cjs +34 -0
- package/src/shared/vibeusage-function-contract.cjs.d.ts +4 -0
- package/src/commands/activate-if-needed.js +0 -41
- package/src/lib/activation-check.js +0 -341
package/src/lib/diagnostics.js
CHANGED
|
@@ -3,26 +3,23 @@ const path = require("node:path");
|
|
|
3
3
|
const fs = require("node:fs/promises");
|
|
4
4
|
|
|
5
5
|
const { readJson } = require("./fs");
|
|
6
|
-
const { readCodexNotify, readEveryCodeNotify } = require("./codex-config");
|
|
7
|
-
const { isClaudeHookConfigured, buildClaudeHookCommand } = require("./claude-config");
|
|
8
|
-
const {
|
|
9
|
-
resolveGeminiConfigDir,
|
|
10
|
-
resolveGeminiSettingsPath,
|
|
11
|
-
buildGeminiHookCommand,
|
|
12
|
-
isGeminiHookConfigured,
|
|
13
|
-
} = require("./gemini-config");
|
|
14
|
-
const { resolveOpencodeConfigDir, isOpencodePluginInstalled } = require("./opencode-config");
|
|
15
6
|
const { normalizeState: normalizeUploadState } = require("./upload-throttle");
|
|
16
|
-
const {
|
|
17
|
-
const { probeOpenclawSessionPluginState } = require("./openclaw-session-plugin");
|
|
18
|
-
const { resolveTrackerPaths } = require("./tracker-paths");
|
|
7
|
+
const { createIntegrationContext, probeIntegrations } = require("./integrations");
|
|
19
8
|
|
|
20
9
|
async function collectTrackerDiagnostics({
|
|
21
10
|
home = os.homedir(),
|
|
22
11
|
codexHome = process.env.CODEX_HOME || path.join(home, ".codex"),
|
|
23
12
|
codeHome = process.env.CODE_HOME || path.join(home, ".code"),
|
|
24
13
|
} = {}) {
|
|
25
|
-
const
|
|
14
|
+
const integrationContext = await createIntegrationContext({
|
|
15
|
+
home,
|
|
16
|
+
env: {
|
|
17
|
+
...process.env,
|
|
18
|
+
CODEX_HOME: codexHome,
|
|
19
|
+
CODE_HOME: codeHome,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
const trackerDir = integrationContext.trackerPaths.trackerDir;
|
|
26
23
|
const configPath = path.join(trackerDir, "config.json");
|
|
27
24
|
const queuePath = path.join(trackerDir, "queue.jsonl");
|
|
28
25
|
const queueStatePath = path.join(trackerDir, "queue.state.json");
|
|
@@ -34,16 +31,14 @@ async function collectTrackerDiagnostics({
|
|
|
34
31
|
const autoRetryPath = path.join(trackerDir, "auto.retry.json");
|
|
35
32
|
const codexConfigPath = path.join(codexHome, "config.toml");
|
|
36
33
|
const codeConfigPath = path.join(codeHome, "config.toml");
|
|
37
|
-
const claudeConfigPath = path.join(home, ".claude", "settings.json");
|
|
38
|
-
const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
|
|
39
|
-
const geminiSettingsPath = resolveGeminiSettingsPath({ configDir: geminiConfigDir });
|
|
40
|
-
const opencodeConfigDir = resolveOpencodeConfigDir({ home, env: process.env });
|
|
41
34
|
|
|
42
35
|
const config = await readJson(configPath);
|
|
43
36
|
const cursors = await readJson(cursorsPath);
|
|
44
37
|
const queueState = (await readJson(queueStatePath)) || { offset: 0 };
|
|
45
38
|
const uploadThrottle = normalizeUploadState(await readJson(uploadThrottlePath));
|
|
46
39
|
const autoRetry = await readJson(autoRetryPath);
|
|
40
|
+
const probes = await probeIntegrations(integrationContext);
|
|
41
|
+
const probeByName = new Map(probes.map((probe) => [probe.name, probe]));
|
|
47
42
|
|
|
48
43
|
const queueSize = await safeStatSize(queuePath);
|
|
49
44
|
const offsetBytes = Number(queueState.offset || 0);
|
|
@@ -53,33 +48,20 @@ async function collectTrackerDiagnostics({
|
|
|
53
48
|
const lastOpenclawSync = (await safeReadText(openclawSignalPath))?.trim() || null;
|
|
54
49
|
const lastNotifySpawn = parseEpochMsToIso((await safeReadText(throttlePath))?.trim() || null);
|
|
55
50
|
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
51
|
+
const codexProbe = probeByName.get("codex");
|
|
52
|
+
const everyCodeProbe = probeByName.get("every-code");
|
|
53
|
+
const claudeProbe = probeByName.get("claude");
|
|
54
|
+
const geminiProbe = probeByName.get("gemini");
|
|
55
|
+
const opencodeProbe = probeByName.get("opencode");
|
|
56
|
+
const openclawSessionProbe = probeByName.get("openclaw-session");
|
|
57
|
+
const openclawLegacyProbe = probeByName.get("openclaw-legacy");
|
|
58
|
+
|
|
59
|
+
const codexNotify = Array.isArray(codexProbe?.currentNotify)
|
|
60
|
+
? codexProbe.currentNotify.map((value) => redactValue(value, home))
|
|
61
|
+
: null;
|
|
62
|
+
const everyCodeNotify = Array.isArray(everyCodeProbe?.currentNotify)
|
|
63
|
+
? everyCodeProbe.currentNotify.map((value) => redactValue(value, home))
|
|
63
64
|
: null;
|
|
64
|
-
const claudeHookCommand = buildClaudeHookCommand(path.join(binDir, "notify.cjs"));
|
|
65
|
-
const claudeHookConfigured = await isClaudeHookConfigured({
|
|
66
|
-
settingsPath: claudeConfigPath,
|
|
67
|
-
hookCommand: claudeHookCommand,
|
|
68
|
-
});
|
|
69
|
-
const geminiHookCommand = buildGeminiHookCommand(path.join(binDir, "notify.cjs"));
|
|
70
|
-
const geminiHookConfigured = await isGeminiHookConfigured({
|
|
71
|
-
settingsPath: geminiSettingsPath,
|
|
72
|
-
hookCommand: geminiHookCommand,
|
|
73
|
-
});
|
|
74
|
-
const opencodePluginConfigured = await isOpencodePluginInstalled({
|
|
75
|
-
configDir: opencodeConfigDir,
|
|
76
|
-
});
|
|
77
|
-
const openclawSessionPluginState = await probeOpenclawSessionPluginState({
|
|
78
|
-
home,
|
|
79
|
-
trackerDir,
|
|
80
|
-
env: process.env,
|
|
81
|
-
});
|
|
82
|
-
const openclawHookState = await probeOpenclawHookState({ home, trackerDir, env: process.env });
|
|
83
65
|
|
|
84
66
|
const lastSuccessAt = uploadThrottle.lastSuccessMs
|
|
85
67
|
? new Date(uploadThrottle.lastSuccessMs).toISOString()
|
|
@@ -101,9 +83,9 @@ async function collectTrackerDiagnostics({
|
|
|
101
83
|
codex_config: redactValue(codexConfigPath, home),
|
|
102
84
|
code_home: redactValue(codeHome, home),
|
|
103
85
|
code_config: redactValue(codeConfigPath, home),
|
|
104
|
-
claude_config: redactValue(
|
|
105
|
-
gemini_config: redactValue(
|
|
106
|
-
opencode_config: redactValue(
|
|
86
|
+
claude_config: redactValue(integrationContext.claude.settingsPath, home),
|
|
87
|
+
gemini_config: redactValue(integrationContext.gemini.settingsPath, home),
|
|
88
|
+
opencode_config: redactValue(integrationContext.opencode.configDir, home),
|
|
107
89
|
},
|
|
108
90
|
config: {
|
|
109
91
|
base_url: typeof config?.baseUrl === "string" ? config.baseUrl : null,
|
|
@@ -128,19 +110,34 @@ async function collectTrackerDiagnostics({
|
|
|
128
110
|
last_notify: lastNotify,
|
|
129
111
|
last_openclaw_triggered_sync: lastOpenclawSync,
|
|
130
112
|
last_notify_triggered_sync: lastNotifySpawn,
|
|
131
|
-
|
|
113
|
+
codex_notify_status: codexProbe?.status || "unknown",
|
|
114
|
+
codex_notify_configured: Boolean(codexProbe?.configured),
|
|
132
115
|
codex_notify: codexNotify,
|
|
133
|
-
|
|
116
|
+
every_code_notify_status: everyCodeProbe?.status || "unknown",
|
|
117
|
+
every_code_notify_configured: Boolean(everyCodeProbe?.configured),
|
|
134
118
|
every_code_notify: everyCodeNotify,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
119
|
+
claude_hook_status: claudeProbe?.status || "unknown",
|
|
120
|
+
claude_hook_configured: Boolean(claudeProbe?.configured),
|
|
121
|
+
gemini_hook_status: geminiProbe?.status || "unknown",
|
|
122
|
+
gemini_hook_configured: Boolean(geminiProbe?.configured),
|
|
123
|
+
opencode_plugin_status: opencodeProbe?.status || "unknown",
|
|
124
|
+
opencode_plugin_configured: Boolean(opencodeProbe?.configured),
|
|
125
|
+
openclaw_session_plugin_status: openclawSessionProbe?.status || "unknown",
|
|
126
|
+
openclaw_session_plugin_configured: Boolean(openclawSessionProbe?.configured),
|
|
127
|
+
openclaw_session_plugin_linked: Boolean(openclawSessionProbe?.linked),
|
|
128
|
+
openclaw_session_plugin_enabled: Boolean(openclawSessionProbe?.enabled),
|
|
129
|
+
openclaw_session_plugin_detail:
|
|
130
|
+
typeof openclawSessionProbe?.detail === "string"
|
|
131
|
+
? redactError(openclawSessionProbe.detail, home)
|
|
132
|
+
: null,
|
|
133
|
+
openclaw_hook_status: openclawLegacyProbe?.status || "unknown",
|
|
134
|
+
openclaw_hook_configured: Boolean(openclawLegacyProbe?.configured),
|
|
135
|
+
openclaw_hook_linked: Boolean(openclawLegacyProbe?.linked),
|
|
136
|
+
openclaw_hook_enabled: Boolean(openclawLegacyProbe?.enabled),
|
|
137
|
+
openclaw_hook_detail:
|
|
138
|
+
typeof openclawLegacyProbe?.detail === "string"
|
|
139
|
+
? redactError(openclawLegacyProbe.detail, home)
|
|
140
|
+
: null,
|
|
144
141
|
},
|
|
145
142
|
upload: {
|
|
146
143
|
last_success_at: lastSuccessAt,
|
package/src/lib/doctor.js
CHANGED
|
@@ -313,6 +313,7 @@ function buildDiagnosticsChecks(diagnostics) {
|
|
|
313
313
|
notify.claude_hook_configured ||
|
|
314
314
|
notify.gemini_hook_configured ||
|
|
315
315
|
notify.opencode_plugin_configured ||
|
|
316
|
+
notify.openclaw_session_plugin_configured ||
|
|
316
317
|
notify.openclaw_hook_configured,
|
|
317
318
|
);
|
|
318
319
|
|
|
@@ -324,6 +325,32 @@ function buildDiagnosticsChecks(diagnostics) {
|
|
|
324
325
|
meta: { configured: notifyConfigured },
|
|
325
326
|
});
|
|
326
327
|
|
|
328
|
+
if (notify.openclaw_session_plugin_status === "unreadable") {
|
|
329
|
+
checks.push({
|
|
330
|
+
id: "notify.openclaw_session_plugin",
|
|
331
|
+
status: "warn",
|
|
332
|
+
detail: "OpenClaw session plugin config unreadable",
|
|
333
|
+
critical: false,
|
|
334
|
+
meta: {
|
|
335
|
+
status: notify.openclaw_session_plugin_status,
|
|
336
|
+
detail: notify.openclaw_session_plugin_detail || null,
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (notify.openclaw_hook_status === "unreadable") {
|
|
342
|
+
checks.push({
|
|
343
|
+
id: "notify.openclaw_hook",
|
|
344
|
+
status: "warn",
|
|
345
|
+
detail: "OpenClaw hook config unreadable",
|
|
346
|
+
critical: false,
|
|
347
|
+
meta: {
|
|
348
|
+
status: notify.openclaw_hook_status,
|
|
349
|
+
detail: notify.openclaw_hook_detail || null,
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
327
354
|
const uploadError = diagnostics?.upload?.last_error || null;
|
|
328
355
|
checks.push({
|
|
329
356
|
id: "upload.last_error",
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const { probeClaudeHook, upsertClaudeHook, removeClaudeHook } = require("../claude-config");
|
|
2
|
+
const { isDir, isFile } = require("./utils");
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
name: "claude",
|
|
6
|
+
summaryLabel: "Claude",
|
|
7
|
+
statusLabel: "Claude hooks",
|
|
8
|
+
async probe(ctx) {
|
|
9
|
+
const hasConfigDir = await isDir(ctx.claude.configDir);
|
|
10
|
+
if (!hasConfigDir) {
|
|
11
|
+
return baseProbe(this, { status: "not_installed", detail: "Config not found" });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const hasSettings = await isFile(ctx.claude.settingsPath);
|
|
15
|
+
if (!hasSettings) {
|
|
16
|
+
return baseProbe(this, { status: "drifted", detail: "Run vibeusage init to install hooks" });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const hookState = await probeClaudeHook({
|
|
20
|
+
settingsPath: ctx.claude.settingsPath,
|
|
21
|
+
hookCommand: ctx.claude.hookCommand,
|
|
22
|
+
});
|
|
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
|
|
36
|
+
? "unsupported_legacy"
|
|
37
|
+
: "drifted";
|
|
38
|
+
return baseProbe(this, {
|
|
39
|
+
status,
|
|
40
|
+
detail:
|
|
41
|
+
status === "unsupported_legacy"
|
|
42
|
+
? "Legacy hook config detected; run vibeusage init"
|
|
43
|
+
: "Run vibeusage init to reconcile hooks",
|
|
44
|
+
hookState,
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
async install(ctx) {
|
|
48
|
+
if (!(await isDir(ctx.claude.configDir))) {
|
|
49
|
+
return action(this, "skipped", false, "Config not found");
|
|
50
|
+
}
|
|
51
|
+
const result = await upsertClaudeHook({
|
|
52
|
+
settingsPath: ctx.claude.settingsPath,
|
|
53
|
+
hookCommand: ctx.claude.hookCommand,
|
|
54
|
+
});
|
|
55
|
+
return action(
|
|
56
|
+
this,
|
|
57
|
+
result.changed ? "installed" : "set",
|
|
58
|
+
Boolean(result.changed),
|
|
59
|
+
result.changed ? "Hooks installed" : "Hooks already installed",
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
async uninstall(ctx) {
|
|
63
|
+
if (!(await isFile(ctx.claude.settingsPath))) {
|
|
64
|
+
return action(this, "skipped", false, "settings.json not found");
|
|
65
|
+
}
|
|
66
|
+
const result = await removeClaudeHook({
|
|
67
|
+
settingsPath: ctx.claude.settingsPath,
|
|
68
|
+
hookCommand: ctx.claude.hookCommand,
|
|
69
|
+
});
|
|
70
|
+
if (result.removed) {
|
|
71
|
+
return action(this, "removed", true, ctx.claude.settingsPath);
|
|
72
|
+
}
|
|
73
|
+
if (result.skippedReason === "hook-missing") {
|
|
74
|
+
return action(this, "unchanged", false, "no change", {
|
|
75
|
+
skippedReason: result.skippedReason,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return action(this, "skipped", false, "settings.json not found");
|
|
79
|
+
},
|
|
80
|
+
renderStatusValue(probe) {
|
|
81
|
+
if (probe.status === "ready") return "set";
|
|
82
|
+
if (probe.status === "not_installed") return "unset";
|
|
83
|
+
return probe.status;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
function baseProbe(descriptor, values) {
|
|
88
|
+
return {
|
|
89
|
+
name: descriptor.name,
|
|
90
|
+
summaryLabel: descriptor.summaryLabel,
|
|
91
|
+
statusLabel: descriptor.statusLabel,
|
|
92
|
+
configured: false,
|
|
93
|
+
...values,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function action(descriptor, status, changed, detail, extras = {}) {
|
|
98
|
+
return {
|
|
99
|
+
name: descriptor.name,
|
|
100
|
+
label: descriptor.summaryLabel,
|
|
101
|
+
status,
|
|
102
|
+
changed,
|
|
103
|
+
detail,
|
|
104
|
+
...extras,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const {
|
|
2
|
+
readCodexNotify,
|
|
3
|
+
upsertCodexNotify,
|
|
4
|
+
restoreCodexNotify,
|
|
5
|
+
} = require("../codex-config");
|
|
6
|
+
const { arraysEqual, isFile } = require("./utils");
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
name: "codex",
|
|
10
|
+
summaryLabel: "Codex CLI",
|
|
11
|
+
statusLabel: "Codex notify",
|
|
12
|
+
async probe(ctx) {
|
|
13
|
+
const installed = await isFile(ctx.codex.configPath);
|
|
14
|
+
if (!installed) {
|
|
15
|
+
return baseProbe(this, { status: "not_installed", detail: "Config not found" });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const currentNotify = await readCodexNotify(ctx.codex.configPath);
|
|
19
|
+
const ready = arraysEqual(currentNotify, ctx.codex.notifyCmd);
|
|
20
|
+
return baseProbe(this, {
|
|
21
|
+
status: ready ? "ready" : "drifted",
|
|
22
|
+
detail: ready ? "Config already set" : "Run vibeusage init to reconcile notify",
|
|
23
|
+
configured: ready,
|
|
24
|
+
currentNotify,
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
async install(ctx) {
|
|
28
|
+
if (!(await isFile(ctx.codex.configPath))) {
|
|
29
|
+
return action(this, "skipped", false, "Config not found");
|
|
30
|
+
}
|
|
31
|
+
const result = await upsertCodexNotify({
|
|
32
|
+
codexConfigPath: ctx.codex.configPath,
|
|
33
|
+
notifyCmd: ctx.codex.notifyCmd,
|
|
34
|
+
notifyOriginalPath: ctx.codex.notifyOriginalPath,
|
|
35
|
+
});
|
|
36
|
+
return action(
|
|
37
|
+
this,
|
|
38
|
+
result.changed ? "updated" : "set",
|
|
39
|
+
Boolean(result.changed),
|
|
40
|
+
result.changed ? "Updated config" : "Config already set",
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
async uninstall(ctx) {
|
|
44
|
+
if (!(await isFile(ctx.codex.configPath))) {
|
|
45
|
+
return action(this, "skipped", false, "config.toml not found");
|
|
46
|
+
}
|
|
47
|
+
const result = await restoreCodexNotify({
|
|
48
|
+
codexConfigPath: ctx.codex.configPath,
|
|
49
|
+
notifyOriginalPath: ctx.codex.notifyOriginalPath,
|
|
50
|
+
notifyCmd: ctx.codex.notifyCmd,
|
|
51
|
+
});
|
|
52
|
+
if (result.restored) {
|
|
53
|
+
return action(this, "restored", true, ctx.codex.configPath);
|
|
54
|
+
}
|
|
55
|
+
if (result.skippedReason === "no-backup-not-installed") {
|
|
56
|
+
return action(this, "skipped", false, "no backup; not installed", {
|
|
57
|
+
skippedReason: result.skippedReason,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return action(this, "unchanged", false, "no change");
|
|
61
|
+
},
|
|
62
|
+
renderStatusValue(probe) {
|
|
63
|
+
if (probe.status === "ready") return JSON.stringify(probe.currentNotify || []);
|
|
64
|
+
if (probe.status === "not_installed") return "unset";
|
|
65
|
+
return probe.status;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function baseProbe(descriptor, values) {
|
|
70
|
+
return {
|
|
71
|
+
name: descriptor.name,
|
|
72
|
+
summaryLabel: descriptor.summaryLabel,
|
|
73
|
+
statusLabel: descriptor.statusLabel,
|
|
74
|
+
configured: false,
|
|
75
|
+
...values,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function action(descriptor, status, changed, detail, extras = {}) {
|
|
80
|
+
return {
|
|
81
|
+
name: descriptor.name,
|
|
82
|
+
label: descriptor.summaryLabel,
|
|
83
|
+
status,
|
|
84
|
+
changed,
|
|
85
|
+
detail,
|
|
86
|
+
...extras,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const os = require("node:os");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
|
|
4
|
+
const { buildClaudeHookCommand } = require("../claude-config");
|
|
5
|
+
const {
|
|
6
|
+
resolveGeminiConfigDir,
|
|
7
|
+
resolveGeminiSettingsPath,
|
|
8
|
+
buildGeminiHookCommand,
|
|
9
|
+
} = require("../gemini-config");
|
|
10
|
+
const { resolveOpencodeConfigDir } = require("../opencode-config");
|
|
11
|
+
const { resolveOpenclawHookPaths } = require("../openclaw-hook");
|
|
12
|
+
const { resolveOpenclawSessionPluginPaths } = require("../openclaw-session-plugin");
|
|
13
|
+
const { resolveTrackerPaths } = require("../tracker-paths");
|
|
14
|
+
|
|
15
|
+
async function createIntegrationContext({
|
|
16
|
+
home = os.homedir(),
|
|
17
|
+
env = process.env,
|
|
18
|
+
trackerPaths = null,
|
|
19
|
+
notifyPath = null,
|
|
20
|
+
} = {}) {
|
|
21
|
+
const resolvedTrackerPaths = trackerPaths || (await resolveTrackerPaths({ home }));
|
|
22
|
+
const resolvedNotifyPath = notifyPath || path.join(resolvedTrackerPaths.binDir, "notify.cjs");
|
|
23
|
+
|
|
24
|
+
const codexHome = env.CODEX_HOME || path.join(home, ".codex");
|
|
25
|
+
const codeHome = env.CODE_HOME || path.join(home, ".code");
|
|
26
|
+
const claudeDir = path.join(home, ".claude");
|
|
27
|
+
const geminiConfigDir = resolveGeminiConfigDir({ home, env });
|
|
28
|
+
const opencodeConfigDir = resolveOpencodeConfigDir({ home, env });
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
home,
|
|
32
|
+
env,
|
|
33
|
+
trackerPaths: resolvedTrackerPaths,
|
|
34
|
+
notifyPath: resolvedNotifyPath,
|
|
35
|
+
codex: {
|
|
36
|
+
configPath: path.join(codexHome, "config.toml"),
|
|
37
|
+
notifyCmd: ["/usr/bin/env", "node", resolvedNotifyPath],
|
|
38
|
+
notifyOriginalPath: path.join(resolvedTrackerPaths.trackerDir, "codex_notify_original.json"),
|
|
39
|
+
},
|
|
40
|
+
everyCode: {
|
|
41
|
+
configPath: path.join(codeHome, "config.toml"),
|
|
42
|
+
notifyCmd: ["/usr/bin/env", "node", resolvedNotifyPath, "--source=every-code"],
|
|
43
|
+
notifyOriginalPath: path.join(
|
|
44
|
+
resolvedTrackerPaths.trackerDir,
|
|
45
|
+
"code_notify_original.json",
|
|
46
|
+
),
|
|
47
|
+
},
|
|
48
|
+
claude: {
|
|
49
|
+
configDir: claudeDir,
|
|
50
|
+
settingsPath: path.join(claudeDir, "settings.json"),
|
|
51
|
+
hookCommand: buildClaudeHookCommand(resolvedNotifyPath),
|
|
52
|
+
},
|
|
53
|
+
gemini: {
|
|
54
|
+
configDir: geminiConfigDir,
|
|
55
|
+
settingsPath: resolveGeminiSettingsPath({ configDir: geminiConfigDir }),
|
|
56
|
+
hookCommand: buildGeminiHookCommand(resolvedNotifyPath),
|
|
57
|
+
},
|
|
58
|
+
opencode: {
|
|
59
|
+
configDir: opencodeConfigDir,
|
|
60
|
+
},
|
|
61
|
+
openclawSession: resolveOpenclawSessionPluginPaths({
|
|
62
|
+
home,
|
|
63
|
+
trackerDir: resolvedTrackerPaths.trackerDir,
|
|
64
|
+
env,
|
|
65
|
+
}),
|
|
66
|
+
openclawLegacy: resolveOpenclawHookPaths({
|
|
67
|
+
home,
|
|
68
|
+
trackerDir: resolvedTrackerPaths.trackerDir,
|
|
69
|
+
env,
|
|
70
|
+
}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
createIntegrationContext,
|
|
76
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const {
|
|
2
|
+
readEveryCodeNotify,
|
|
3
|
+
upsertEveryCodeNotify,
|
|
4
|
+
restoreEveryCodeNotify,
|
|
5
|
+
} = require("../codex-config");
|
|
6
|
+
const { arraysEqual, isFile } = require("./utils");
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
name: "every-code",
|
|
10
|
+
summaryLabel: "Every Code",
|
|
11
|
+
statusLabel: "Every Code notify",
|
|
12
|
+
async probe(ctx) {
|
|
13
|
+
const installed = await isFile(ctx.everyCode.configPath);
|
|
14
|
+
if (!installed) {
|
|
15
|
+
return baseProbe(this, { status: "not_installed", detail: "Config not found" });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const currentNotify = await readEveryCodeNotify(ctx.everyCode.configPath);
|
|
19
|
+
const ready = arraysEqual(currentNotify, ctx.everyCode.notifyCmd);
|
|
20
|
+
return baseProbe(this, {
|
|
21
|
+
status: ready ? "ready" : "drifted",
|
|
22
|
+
detail: ready ? "Config already set" : "Run vibeusage init to reconcile notify",
|
|
23
|
+
configured: ready,
|
|
24
|
+
currentNotify,
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
async install(ctx) {
|
|
28
|
+
if (!(await isFile(ctx.everyCode.configPath))) {
|
|
29
|
+
return action(this, "skipped", false, "Config not found");
|
|
30
|
+
}
|
|
31
|
+
const result = await upsertEveryCodeNotify({
|
|
32
|
+
codeConfigPath: ctx.everyCode.configPath,
|
|
33
|
+
notifyCmd: ctx.everyCode.notifyCmd,
|
|
34
|
+
notifyOriginalPath: ctx.everyCode.notifyOriginalPath,
|
|
35
|
+
});
|
|
36
|
+
return action(
|
|
37
|
+
this,
|
|
38
|
+
result.changed ? "updated" : "set",
|
|
39
|
+
Boolean(result.changed),
|
|
40
|
+
result.changed ? "Updated config" : "Config already set",
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
async uninstall(ctx) {
|
|
44
|
+
if (!(await isFile(ctx.everyCode.configPath))) {
|
|
45
|
+
return action(this, "skipped", false, "config.toml not found");
|
|
46
|
+
}
|
|
47
|
+
const result = await restoreEveryCodeNotify({
|
|
48
|
+
codeConfigPath: ctx.everyCode.configPath,
|
|
49
|
+
notifyOriginalPath: ctx.everyCode.notifyOriginalPath,
|
|
50
|
+
notifyCmd: ctx.everyCode.notifyCmd,
|
|
51
|
+
});
|
|
52
|
+
if (result.restored) {
|
|
53
|
+
return action(this, "restored", true, ctx.everyCode.configPath);
|
|
54
|
+
}
|
|
55
|
+
if (result.skippedReason === "no-backup-not-installed") {
|
|
56
|
+
return action(this, "skipped", false, "no backup; not installed", {
|
|
57
|
+
skippedReason: result.skippedReason,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return action(this, "unchanged", false, "no change");
|
|
61
|
+
},
|
|
62
|
+
renderStatusValue(probe) {
|
|
63
|
+
if (probe.status === "ready") return JSON.stringify(probe.currentNotify || []);
|
|
64
|
+
if (probe.status === "not_installed") return "unset";
|
|
65
|
+
return probe.status;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function baseProbe(descriptor, values) {
|
|
70
|
+
return {
|
|
71
|
+
name: descriptor.name,
|
|
72
|
+
summaryLabel: descriptor.summaryLabel,
|
|
73
|
+
statusLabel: descriptor.statusLabel,
|
|
74
|
+
configured: false,
|
|
75
|
+
...values,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function action(descriptor, status, changed, detail, extras = {}) {
|
|
80
|
+
return {
|
|
81
|
+
name: descriptor.name,
|
|
82
|
+
label: descriptor.summaryLabel,
|
|
83
|
+
status,
|
|
84
|
+
changed,
|
|
85
|
+
detail,
|
|
86
|
+
...extras,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const {
|
|
2
|
+
isGeminiHookConfigured,
|
|
3
|
+
upsertGeminiHook,
|
|
4
|
+
removeGeminiHook,
|
|
5
|
+
} = require("../gemini-config");
|
|
6
|
+
const { isDir, isFile } = require("./utils");
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
name: "gemini",
|
|
10
|
+
summaryLabel: "Gemini",
|
|
11
|
+
statusLabel: "Gemini hooks",
|
|
12
|
+
async probe(ctx) {
|
|
13
|
+
const hasConfigDir = await isDir(ctx.gemini.configDir);
|
|
14
|
+
if (!hasConfigDir) {
|
|
15
|
+
return baseProbe(this, { status: "not_installed", detail: "Config not found" });
|
|
16
|
+
}
|
|
17
|
+
const configured = await isGeminiHookConfigured({
|
|
18
|
+
settingsPath: ctx.gemini.settingsPath,
|
|
19
|
+
hookCommand: ctx.gemini.hookCommand,
|
|
20
|
+
});
|
|
21
|
+
return baseProbe(this, {
|
|
22
|
+
status: configured ? "ready" : "drifted",
|
|
23
|
+
detail: configured ? "Hooks installed" : "Run vibeusage init to reconcile hooks",
|
|
24
|
+
configured,
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
async install(ctx) {
|
|
28
|
+
if (!(await isDir(ctx.gemini.configDir))) {
|
|
29
|
+
return action(this, "skipped", false, "Config not found");
|
|
30
|
+
}
|
|
31
|
+
const result = await upsertGeminiHook({
|
|
32
|
+
settingsPath: ctx.gemini.settingsPath,
|
|
33
|
+
hookCommand: ctx.gemini.hookCommand,
|
|
34
|
+
});
|
|
35
|
+
return action(
|
|
36
|
+
this,
|
|
37
|
+
result.changed ? "installed" : "set",
|
|
38
|
+
Boolean(result.changed),
|
|
39
|
+
result.changed ? "Hooks installed" : "Hooks already installed",
|
|
40
|
+
);
|
|
41
|
+
},
|
|
42
|
+
async uninstall(ctx) {
|
|
43
|
+
if (!(await isDir(ctx.gemini.configDir))) {
|
|
44
|
+
return action(this, "skipped", false, "config dir not found");
|
|
45
|
+
}
|
|
46
|
+
const result = await removeGeminiHook({
|
|
47
|
+
settingsPath: ctx.gemini.settingsPath,
|
|
48
|
+
hookCommand: ctx.gemini.hookCommand,
|
|
49
|
+
});
|
|
50
|
+
if (result.removed) {
|
|
51
|
+
return action(this, "removed", true, ctx.gemini.settingsPath);
|
|
52
|
+
}
|
|
53
|
+
if (result.skippedReason === "hook-missing") {
|
|
54
|
+
return action(this, "unchanged", false, "no change", {
|
|
55
|
+
skippedReason: result.skippedReason,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return action(this, "skipped", false, "settings.json not found");
|
|
59
|
+
},
|
|
60
|
+
renderStatusValue(probe) {
|
|
61
|
+
if (probe.status === "ready") return "set";
|
|
62
|
+
if (probe.status === "not_installed") return "unset";
|
|
63
|
+
return probe.status;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function baseProbe(descriptor, values) {
|
|
68
|
+
return {
|
|
69
|
+
name: descriptor.name,
|
|
70
|
+
summaryLabel: descriptor.summaryLabel,
|
|
71
|
+
statusLabel: descriptor.statusLabel,
|
|
72
|
+
configured: false,
|
|
73
|
+
...values,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function action(descriptor, status, changed, detail, extras = {}) {
|
|
78
|
+
return {
|
|
79
|
+
name: descriptor.name,
|
|
80
|
+
label: descriptor.summaryLabel,
|
|
81
|
+
status,
|
|
82
|
+
changed,
|
|
83
|
+
detail,
|
|
84
|
+
...extras,
|
|
85
|
+
};
|
|
86
|
+
}
|