vibeusage 0.2.22 → 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 -6
- package/src/commands/init.js +20 -362
- package/src/commands/status.js +36 -51
- package/src/commands/sync.js +4 -7
- 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 -290
package/README.md
CHANGED
|
@@ -111,6 +111,9 @@ Initialize your environment once - VibeUsage handles all synchronization automat
|
|
|
111
111
|
npx vibeusage init
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
> [!IMPORTANT]
|
|
115
|
+
> Starting with `vibeusage@0.3.0`, `init` is the only supported command that writes local integration config. If you upgrade from an older install layout, re-run `npx vibeusage init`; `status`, `diagnostics`, `doctor`, and `sync` will not auto-repair legacy hooks.
|
|
116
|
+
|
|
114
117
|
### Authentication Methods
|
|
115
118
|
|
|
116
119
|
1. **Browser Auth** (default) - Opens browser for secure authentication
|
|
@@ -140,12 +143,14 @@ Once `init` completes, all supported CLI tools are automatically configured for
|
|
|
140
143
|
| **Codex CLI** | `~/.codex/config.toml` | `notify` hook |
|
|
141
144
|
| **Every Code** | `~/.code/config.toml` (or `CODE_HOME`) | `notify` hook |
|
|
142
145
|
| **Gemini CLI** | `~/.gemini/settings.json` (or `GEMINI_HOME`) | `SessionEnd` hook |
|
|
143
|
-
| **Opencode** |
|
|
144
|
-
| **Claude Code** | `~/.claude/
|
|
145
|
-
| **OpenClaw** | Auto-links when installed |
|
|
146
|
+
| **Opencode** | OpenCode config/plugins | Message parser plugin |
|
|
147
|
+
| **Claude Code** | `~/.claude/settings.json` | `Stop` + `SessionEnd` hooks |
|
|
148
|
+
| **OpenClaw** | Auto-links when installed | Session plugin (requires restart) |
|
|
146
149
|
|
|
147
150
|
No further intervention required! 🎉
|
|
148
151
|
|
|
152
|
+
If any integration drifts later, re-run `npx vibeusage init`. The read-only commands intentionally do not rewrite local hook/plugin state.
|
|
153
|
+
|
|
149
154
|
## 💡 Usage
|
|
150
155
|
|
|
151
156
|
### Manual Sync
|
|
@@ -204,7 +209,7 @@ graph LR
|
|
|
204
209
|
C[Gemini CLI] -->|Session Logs| G
|
|
205
210
|
D[Opencode] -->|Message Logs| G
|
|
206
211
|
E[Claude Code] -->|Hook Output| G
|
|
207
|
-
F[OpenClaw] -->|
|
|
212
|
+
F[OpenClaw] -->|Session Plugin| G
|
|
208
213
|
G -->|AI Tokens| H{Core Relay}
|
|
209
214
|
H --> I[VibeUsage Dashboard]
|
|
210
215
|
H --> J[AI Analytics Engine]
|
|
@@ -245,7 +250,7 @@ graph LR
|
|
|
245
250
|
| **Gemini CLI** | `~/.gemini/tmp/**/chats/session-*.json` | `GEMINI_HOME` |
|
|
246
251
|
| **Opencode** | `~/.opencode/messages/*.json` | - |
|
|
247
252
|
| **Claude Code** | Parsed from hook output | - |
|
|
248
|
-
| **OpenClaw** |
|
|
253
|
+
| **OpenClaw** | Session plugin integration | - |
|
|
249
254
|
|
|
250
255
|
## ⚙️ Configuration
|
|
251
256
|
|
|
@@ -374,17 +379,11 @@ This project uses **OpenSpec** for spec-driven development. Before making signif
|
|
|
374
379
|
|
|
375
380
|
See [CLAUDE.md](CLAUDE.md) for detailed guidelines.
|
|
376
381
|
|
|
377
|
-
###
|
|
382
|
+
### Repository Navigation
|
|
378
383
|
|
|
379
384
|
```bash
|
|
380
|
-
#
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
# Generate architecture canvas
|
|
384
|
-
npm run architecture:canvas
|
|
385
|
-
|
|
386
|
-
# Generate focused canvas for a module
|
|
387
|
-
npm run architecture:canvas:focus -- src
|
|
385
|
+
# Read the repository sitemap first
|
|
386
|
+
cat docs/repo-sitemap.md
|
|
388
387
|
```
|
|
389
388
|
|
|
390
389
|
## 🗺️ Roadmap
|
package/README.zh-CN.md
CHANGED
|
@@ -111,6 +111,9 @@ npx vibeusage init
|
|
|
111
111
|
npx vibeusage init
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
> [!IMPORTANT]
|
|
115
|
+
> 从 `vibeusage@0.3.0` 开始,只有 `init` 会修改本地集成配置。如果你是从旧版本升级,请重新执行一次 `npx vibeusage init`;`status`、`diagnostics`、`doctor`、`sync` 都不会自动修复旧 hook 布局。
|
|
116
|
+
|
|
114
117
|
### 认证方式
|
|
115
118
|
|
|
116
119
|
1. **浏览器认证**(默认)- 打开浏览器进行安全认证
|
|
@@ -141,11 +144,13 @@ npx vibeusage init [选项]
|
|
|
141
144
|
| **Every Code** | `~/.code/config.toml`(或 `CODE_HOME`) | `notify` 钩子 |
|
|
142
145
|
| **Gemini CLI** | `~/.gemini/settings.json`(或 `GEMINI_HOME`) | `SessionEnd` 钩子 |
|
|
143
146
|
| **Opencode** | 全局插件 | 消息解析器插件 |
|
|
144
|
-
| **Claude Code** | `~/.claude/
|
|
145
|
-
| **OpenClaw** | 安装时自动链接 |
|
|
147
|
+
| **Claude Code** | `~/.claude/settings.json` | `Stop` + `SessionEnd` 钩子 |
|
|
148
|
+
| **OpenClaw** | 安装时自动链接 | Session plugin(需要重启) |
|
|
146
149
|
|
|
147
150
|
无需进一步操作!🎉
|
|
148
151
|
|
|
152
|
+
如果后续某个集成出现漂移,请重新执行 `npx vibeusage init`。只读命令不会重写本地 hook/plugin 状态。
|
|
153
|
+
|
|
149
154
|
## 💡 使用方法
|
|
150
155
|
|
|
151
156
|
### 手动同步
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeusage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Codex CLI token usage tracker (macOS-first, notify-driven).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -19,9 +19,6 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
|
-
"architecture:canvas": "node scripts/ops/architecture-canvas.cjs",
|
|
23
|
-
"architecture:canvas:focus": "node scripts/ops/architecture-canvas.cjs --focus",
|
|
24
|
-
"architecture:canvas:list-modules": "node scripts/ops/architecture-canvas.cjs --list-modules",
|
|
25
22
|
"build:insforge": "node scripts/build-insforge-functions.cjs",
|
|
26
23
|
"build:insforge:check": "node scripts/build-insforge-functions.cjs --check",
|
|
27
24
|
"ci:local": "npm test && npm run validate:copy && npm run validate:ui-hardcode && npm run validate:guardrails && node --test test/architecture-guardrails.test.js && npm run build:insforge:check && npm --prefix dashboard run build",
|
|
@@ -32,12 +29,14 @@
|
|
|
32
29
|
"dashboard:preview": "npm --prefix dashboard run preview",
|
|
33
30
|
"dev:shim": "node scripts/dev-bin-shim.cjs",
|
|
34
31
|
"graph:auto-index": "node scripts/graph/auto-index.cjs",
|
|
32
|
+
"review:preflight": "npm run validate:pr-risk-layer --",
|
|
35
33
|
"graph:scip": "node scripts/graph/generate-scip.cjs",
|
|
36
34
|
"smoke": "node scripts/smoke/insforge-smoke.cjs",
|
|
37
|
-
"test": "node --test test/*.test.js",
|
|
35
|
+
"test": "node --test --test-concurrency=1 test/*.test.js",
|
|
38
36
|
"validate:copy": "node scripts/validate-copy-registry.cjs",
|
|
39
37
|
"validate:guardrails": "node scripts/validate-architecture-guardrails.cjs",
|
|
40
38
|
"validate:insforge2-db": "node scripts/ops/insforge2-db-validate.cjs",
|
|
39
|
+
"validate:pr-risk-layer": "node scripts/ops/pr-risk-layer-gate.cjs --config scripts/ops/pr-risk-layer-gate.config.json",
|
|
41
40
|
"validate:retros": "node scripts/validate-retros.cjs",
|
|
42
41
|
"validate:ui-hardcode": "node scripts/ops/validate-ui-hardcode.cjs"
|
|
43
42
|
},
|
package/src/cli.js
CHANGED
|
@@ -4,7 +4,6 @@ const { cmdStatus } = require("./commands/status");
|
|
|
4
4
|
const { cmdDiagnostics } = require("./commands/diagnostics");
|
|
5
5
|
const { cmdDoctor } = require("./commands/doctor");
|
|
6
6
|
const { cmdUninstall } = require("./commands/uninstall");
|
|
7
|
-
const { cmdActivateIfNeeded } = require("./commands/activate-if-needed");
|
|
8
7
|
|
|
9
8
|
async function run(argv) {
|
|
10
9
|
const [command, ...rest] = argv;
|
|
@@ -33,9 +32,6 @@ async function run(argv) {
|
|
|
33
32
|
case "uninstall":
|
|
34
33
|
await cmdUninstall(rest);
|
|
35
34
|
return;
|
|
36
|
-
case "activate-if-needed":
|
|
37
|
-
await cmdActivateIfNeeded(rest);
|
|
38
|
-
return;
|
|
39
35
|
default:
|
|
40
36
|
throw new Error(`Unknown command: ${command}`);
|
|
41
37
|
}
|
|
@@ -61,11 +57,11 @@ function printHelp() {
|
|
|
61
57
|
" - --dry-run previews changes without writing files.",
|
|
62
58
|
" - optional: --link-code <code> skips browser login when provided by Dashboard.",
|
|
63
59
|
" - Every Code notify installs when ~/.code/config.toml exists.",
|
|
64
|
-
" - OpenClaw
|
|
60
|
+
" - OpenClaw session plugin auto-links when OpenClaw is installed (requires gateway restart).",
|
|
65
61
|
" - auto sync waits for a device token.",
|
|
66
62
|
" - optional: VIBEUSAGE_DASHBOARD_URL or --dashboard-url for hosted landing.",
|
|
67
63
|
" - sync parses ~/.codex/sessions/**/rollout-*.jsonl and ~/.code/sessions/**/rollout-*.jsonl, then uploads token deltas.",
|
|
68
|
-
" - --from-openclaw marks sync runs triggered by OpenClaw
|
|
64
|
+
" - --from-openclaw marks sync runs triggered by OpenClaw session plugin events.",
|
|
69
65
|
" - --debug shows original backend errors.",
|
|
70
66
|
"",
|
|
71
67
|
].join("\n"),
|
package/src/commands/init.js
CHANGED
|
@@ -13,34 +13,6 @@ const {
|
|
|
13
13
|
chmod600IfPossible,
|
|
14
14
|
} = require("../lib/fs");
|
|
15
15
|
const { prompt, promptHidden } = require("../lib/prompt");
|
|
16
|
-
const {
|
|
17
|
-
upsertCodexNotify,
|
|
18
|
-
upsertEveryCodeNotify,
|
|
19
|
-
readCodexNotify,
|
|
20
|
-
readEveryCodeNotify,
|
|
21
|
-
} = require("../lib/codex-config");
|
|
22
|
-
const {
|
|
23
|
-
upsertClaudeHook,
|
|
24
|
-
buildClaudeHookCommand,
|
|
25
|
-
isClaudeHookConfigured,
|
|
26
|
-
} = require("../lib/claude-config");
|
|
27
|
-
const {
|
|
28
|
-
resolveGeminiConfigDir,
|
|
29
|
-
resolveGeminiSettingsPath,
|
|
30
|
-
buildGeminiHookCommand,
|
|
31
|
-
upsertGeminiHook,
|
|
32
|
-
isGeminiHookConfigured,
|
|
33
|
-
} = require("../lib/gemini-config");
|
|
34
|
-
const {
|
|
35
|
-
resolveOpencodeConfigDir,
|
|
36
|
-
upsertOpencodePlugin,
|
|
37
|
-
isOpencodePluginInstalled,
|
|
38
|
-
} = require("../lib/opencode-config");
|
|
39
|
-
const { removeOpenclawHookConfig, probeOpenclawHookState } = require("../lib/openclaw-hook");
|
|
40
|
-
const {
|
|
41
|
-
installOpenclawSessionPlugin,
|
|
42
|
-
probeOpenclawSessionPluginState,
|
|
43
|
-
} = require("../lib/openclaw-session-plugin");
|
|
44
16
|
const { beginBrowserAuth, openInBrowser } = require("../lib/browser-auth");
|
|
45
17
|
const {
|
|
46
18
|
issueDeviceTokenWithPassword,
|
|
@@ -60,6 +32,13 @@ const {
|
|
|
60
32
|
createSpinner,
|
|
61
33
|
} = require("../lib/cli-ui");
|
|
62
34
|
const { renderLocalReport, renderAuthTransition, renderSuccessBox } = require("../lib/init-flow");
|
|
35
|
+
const {
|
|
36
|
+
createIntegrationContext,
|
|
37
|
+
installIntegrations,
|
|
38
|
+
probeIntegrations,
|
|
39
|
+
summarizeProbeForInitPreview,
|
|
40
|
+
} = require("../lib/integrations");
|
|
41
|
+
const { DEFAULT_DASHBOARD_URL } = require("../shared/runtime-defaults.cjs");
|
|
63
42
|
|
|
64
43
|
const ASCII_LOGO = [
|
|
65
44
|
"██╗ ██╗██╗██████╗ ███████╗██╗ ██╗███████╗ █████╗ ██████╗ ███████╗",
|
|
@@ -71,16 +50,13 @@ const ASCII_LOGO = [
|
|
|
71
50
|
].join("\n");
|
|
72
51
|
|
|
73
52
|
const DIVIDER = "----------------------------------------------";
|
|
74
|
-
const DEFAULT_DASHBOARD_URL = "https://www.vibeusage.cc";
|
|
75
|
-
|
|
76
53
|
async function cmdInit(argv) {
|
|
77
54
|
const opts = parseArgs(argv);
|
|
78
55
|
const home = os.homedir();
|
|
79
56
|
|
|
80
|
-
const {
|
|
57
|
+
const { trackerDir, binDir } = await resolveTrackerPaths({ home });
|
|
81
58
|
|
|
82
59
|
const configPath = path.join(trackerDir, "config.json");
|
|
83
|
-
const notifyOriginalPath = path.join(trackerDir, "codex_notify_original.json");
|
|
84
60
|
const linkCodeStatePath = path.join(trackerDir, "link_code_state.json");
|
|
85
61
|
|
|
86
62
|
const existingConfig = await readJson(configPath);
|
|
@@ -144,7 +120,6 @@ async function cmdInit(argv) {
|
|
|
144
120
|
trackerDir,
|
|
145
121
|
binDir,
|
|
146
122
|
configPath,
|
|
147
|
-
notifyOriginalPath,
|
|
148
123
|
linkCodeStatePath,
|
|
149
124
|
notifyPath,
|
|
150
125
|
appDir,
|
|
@@ -253,8 +228,14 @@ function shouldUseBrowserAuth({ deviceToken, opts }) {
|
|
|
253
228
|
async function buildDryRunSummary({ opts, home, trackerDir, notifyPath, runtime }) {
|
|
254
229
|
const deviceToken = runtime?.deviceToken || null;
|
|
255
230
|
const pendingBrowserAuth = shouldUseBrowserAuth({ deviceToken, opts });
|
|
256
|
-
const context =
|
|
257
|
-
|
|
231
|
+
const context = await createIntegrationContext({
|
|
232
|
+
home,
|
|
233
|
+
env: process.env,
|
|
234
|
+
trackerPaths: { trackerDir, binDir: path.dirname(notifyPath), rootDir: path.dirname(trackerDir) },
|
|
235
|
+
notifyPath,
|
|
236
|
+
});
|
|
237
|
+
const probe = await probeIntegrations(context);
|
|
238
|
+
const summary = probe.map((item) => summarizeProbeForInitPreview(item));
|
|
258
239
|
return { summary, pendingBrowserAuth, deviceToken };
|
|
259
240
|
}
|
|
260
241
|
|
|
@@ -265,7 +246,6 @@ async function runSetup({
|
|
|
265
246
|
trackerDir,
|
|
266
247
|
binDir,
|
|
267
248
|
configPath,
|
|
268
|
-
notifyOriginalPath,
|
|
269
249
|
linkCodeStatePath,
|
|
270
250
|
notifyPath,
|
|
271
251
|
appDir,
|
|
@@ -341,12 +321,13 @@ async function runSetup({
|
|
|
341
321
|
);
|
|
342
322
|
await fs.chmod(notifyPath, 0o755).catch(() => {});
|
|
343
323
|
|
|
344
|
-
const
|
|
324
|
+
const integrationContext = await createIntegrationContext({
|
|
345
325
|
home,
|
|
346
|
-
|
|
326
|
+
env: process.env,
|
|
327
|
+
trackerPaths: { trackerDir, binDir, rootDir: path.dirname(trackerDir) },
|
|
347
328
|
notifyPath,
|
|
348
|
-
notifyOriginalPath,
|
|
349
329
|
});
|
|
330
|
+
const summary = await installIntegrations(integrationContext);
|
|
350
331
|
|
|
351
332
|
return {
|
|
352
333
|
summary,
|
|
@@ -357,307 +338,6 @@ async function runSetup({
|
|
|
357
338
|
};
|
|
358
339
|
}
|
|
359
340
|
|
|
360
|
-
function buildIntegrationTargets({ home, trackerDir, notifyPath }) {
|
|
361
|
-
const codexHome = process.env.CODEX_HOME || path.join(home, ".codex");
|
|
362
|
-
const codexConfigPath = path.join(codexHome, "config.toml");
|
|
363
|
-
const codeHome = process.env.CODE_HOME || path.join(home, ".code");
|
|
364
|
-
const codeConfigPath = path.join(codeHome, "config.toml");
|
|
365
|
-
const notifyOriginalPath = path.join(trackerDir, "codex_notify_original.json");
|
|
366
|
-
const codeNotifyOriginalPath = path.join(trackerDir, "code_notify_original.json");
|
|
367
|
-
const notifyCmd = ["/usr/bin/env", "node", notifyPath];
|
|
368
|
-
const codeNotifyCmd = ["/usr/bin/env", "node", notifyPath, "--source=every-code"];
|
|
369
|
-
const claudeDir = path.join(home, ".claude");
|
|
370
|
-
const claudeSettingsPath = path.join(claudeDir, "settings.json");
|
|
371
|
-
const claudeHookCommand = buildClaudeHookCommand(notifyPath);
|
|
372
|
-
const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
|
|
373
|
-
const geminiSettingsPath = resolveGeminiSettingsPath({ configDir: geminiConfigDir });
|
|
374
|
-
const geminiHookCommand = buildGeminiHookCommand(notifyPath);
|
|
375
|
-
const opencodeConfigDir = resolveOpencodeConfigDir({ home, env: process.env });
|
|
376
|
-
|
|
377
|
-
return {
|
|
378
|
-
trackerDir,
|
|
379
|
-
codexConfigPath,
|
|
380
|
-
codeConfigPath,
|
|
381
|
-
notifyOriginalPath,
|
|
382
|
-
codeNotifyOriginalPath,
|
|
383
|
-
notifyCmd,
|
|
384
|
-
codeNotifyCmd,
|
|
385
|
-
claudeDir,
|
|
386
|
-
claudeSettingsPath,
|
|
387
|
-
claudeHookCommand,
|
|
388
|
-
geminiConfigDir,
|
|
389
|
-
geminiSettingsPath,
|
|
390
|
-
geminiHookCommand,
|
|
391
|
-
opencodeConfigDir,
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
async function applyIntegrationSetup({ home, trackerDir, notifyPath, notifyOriginalPath }) {
|
|
396
|
-
const context = buildIntegrationTargets({ home, trackerDir, notifyPath });
|
|
397
|
-
context.notifyOriginalPath = notifyOriginalPath;
|
|
398
|
-
|
|
399
|
-
const summary = [];
|
|
400
|
-
|
|
401
|
-
const codexProbe = await probeFile(context.codexConfigPath);
|
|
402
|
-
if (codexProbe.exists) {
|
|
403
|
-
const result = await upsertCodexNotify({
|
|
404
|
-
codexConfigPath: context.codexConfigPath,
|
|
405
|
-
notifyCmd: context.notifyCmd,
|
|
406
|
-
notifyOriginalPath: context.notifyOriginalPath,
|
|
407
|
-
});
|
|
408
|
-
summary.push({
|
|
409
|
-
label: "Codex CLI",
|
|
410
|
-
status: result.changed ? "updated" : "set",
|
|
411
|
-
detail: result.changed ? "Updated config" : "Config already set",
|
|
412
|
-
});
|
|
413
|
-
} else {
|
|
414
|
-
summary.push({ label: "Codex CLI", status: "skipped", detail: renderSkipDetail(codexProbe) });
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const claudeDirExists = await isDir(context.claudeDir);
|
|
418
|
-
if (claudeDirExists) {
|
|
419
|
-
await upsertClaudeHook({
|
|
420
|
-
settingsPath: context.claudeSettingsPath,
|
|
421
|
-
hookCommand: context.claudeHookCommand,
|
|
422
|
-
});
|
|
423
|
-
summary.push({ label: "Claude", status: "installed", detail: "Hooks installed" });
|
|
424
|
-
} else {
|
|
425
|
-
summary.push({ label: "Claude", status: "skipped", detail: "Config not found" });
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const geminiConfigExists = await isDir(context.geminiConfigDir);
|
|
429
|
-
if (geminiConfigExists) {
|
|
430
|
-
await upsertGeminiHook({
|
|
431
|
-
settingsPath: context.geminiSettingsPath,
|
|
432
|
-
hookCommand: context.geminiHookCommand,
|
|
433
|
-
});
|
|
434
|
-
summary.push({ label: "Gemini", status: "installed", detail: "Hooks installed" });
|
|
435
|
-
} else {
|
|
436
|
-
summary.push({ label: "Gemini", status: "skipped", detail: "Config not found" });
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
const opencodeResult = await upsertOpencodePlugin({
|
|
440
|
-
configDir: context.opencodeConfigDir,
|
|
441
|
-
notifyPath,
|
|
442
|
-
});
|
|
443
|
-
if (opencodeResult?.skippedReason === "config-missing") {
|
|
444
|
-
summary.push({ label: "Opencode Plugin", status: "skipped", detail: "Config not found" });
|
|
445
|
-
} else {
|
|
446
|
-
summary.push({
|
|
447
|
-
label: "Opencode Plugin",
|
|
448
|
-
status: opencodeResult?.changed ? "installed" : "set",
|
|
449
|
-
detail: "Plugin installed",
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const openclawBefore = await probeOpenclawSessionPluginState({
|
|
454
|
-
home,
|
|
455
|
-
trackerDir,
|
|
456
|
-
env: process.env,
|
|
457
|
-
});
|
|
458
|
-
const openclawInstall = await installOpenclawSessionPlugin({
|
|
459
|
-
home,
|
|
460
|
-
trackerDir,
|
|
461
|
-
packageName: "vibeusage",
|
|
462
|
-
env: process.env,
|
|
463
|
-
});
|
|
464
|
-
if (openclawInstall?.skippedReason === "openclaw-cli-missing") {
|
|
465
|
-
summary.push({
|
|
466
|
-
label: "OpenClaw Session Plugin",
|
|
467
|
-
status: "skipped",
|
|
468
|
-
detail: "OpenClaw CLI not found",
|
|
469
|
-
});
|
|
470
|
-
} else if (openclawInstall?.skippedReason === "openclaw-plugins-install-failed") {
|
|
471
|
-
summary.push({
|
|
472
|
-
label: "OpenClaw Session Plugin",
|
|
473
|
-
status: "skipped",
|
|
474
|
-
detail: `Install failed${openclawInstall.error ? `: ${openclawInstall.error}` : ""}`,
|
|
475
|
-
});
|
|
476
|
-
} else if (openclawInstall?.skippedReason === "openclaw-config-unreadable") {
|
|
477
|
-
summary.push({
|
|
478
|
-
label: "OpenClaw Session Plugin",
|
|
479
|
-
status: "skipped",
|
|
480
|
-
detail: openclawInstall.error
|
|
481
|
-
? `OpenClaw config unreadable: ${openclawInstall.error}`
|
|
482
|
-
: "OpenClaw config unreadable",
|
|
483
|
-
});
|
|
484
|
-
} else if (openclawInstall?.configured) {
|
|
485
|
-
summary.push({
|
|
486
|
-
label: "OpenClaw Session Plugin",
|
|
487
|
-
status: openclawBefore?.configured ? "set" : "installed",
|
|
488
|
-
detail: openclawBefore?.configured
|
|
489
|
-
? "Session plugin already linked"
|
|
490
|
-
: "Session plugin linked (restart OpenClaw gateway to activate)",
|
|
491
|
-
});
|
|
492
|
-
} else {
|
|
493
|
-
summary.push({
|
|
494
|
-
label: "OpenClaw Session Plugin",
|
|
495
|
-
status: "skipped",
|
|
496
|
-
detail: "OpenClaw session plugin unavailable",
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
const legacyHookState = await probeOpenclawHookState({ home, trackerDir, env: process.env });
|
|
501
|
-
if (legacyHookState?.configured || legacyHookState?.linked || legacyHookState?.enabled) {
|
|
502
|
-
await removeOpenclawHookConfig({ home, trackerDir, env: process.env });
|
|
503
|
-
summary.push({
|
|
504
|
-
label: "OpenClaw Hook (legacy)",
|
|
505
|
-
status: "updated",
|
|
506
|
-
detail: "Removed legacy command hook (migrated to session plugin)",
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const codeProbe = await probeFile(context.codeConfigPath);
|
|
511
|
-
if (codeProbe.exists) {
|
|
512
|
-
const result = await upsertEveryCodeNotify({
|
|
513
|
-
codeConfigPath: context.codeConfigPath,
|
|
514
|
-
notifyCmd: context.codeNotifyCmd,
|
|
515
|
-
notifyOriginalPath: context.codeNotifyOriginalPath,
|
|
516
|
-
});
|
|
517
|
-
summary.push({
|
|
518
|
-
label: "Every Code",
|
|
519
|
-
status: result.changed ? "updated" : "set",
|
|
520
|
-
detail: result.changed ? "Updated config" : "Config already set",
|
|
521
|
-
});
|
|
522
|
-
} else {
|
|
523
|
-
summary.push({ label: "Every Code", status: "skipped", detail: renderSkipDetail(codeProbe) });
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
return summary;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
async function previewIntegrations({ context }) {
|
|
530
|
-
const summary = [];
|
|
531
|
-
const home = os.homedir();
|
|
532
|
-
|
|
533
|
-
const codexProbe = await probeFile(context.codexConfigPath);
|
|
534
|
-
if (codexProbe.exists) {
|
|
535
|
-
const existing = await readCodexNotify(context.codexConfigPath);
|
|
536
|
-
const matches = arraysEqual(existing, context.notifyCmd);
|
|
537
|
-
summary.push({
|
|
538
|
-
label: "Codex CLI",
|
|
539
|
-
status: matches ? "set" : "updated",
|
|
540
|
-
detail: matches ? "Already configured" : "Will update config",
|
|
541
|
-
});
|
|
542
|
-
} else {
|
|
543
|
-
summary.push({ label: "Codex CLI", status: "skipped", detail: renderSkipDetail(codexProbe) });
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
const claudeDirExists = await isDir(context.claudeDir);
|
|
547
|
-
if (claudeDirExists) {
|
|
548
|
-
const configured = await isClaudeHookConfigured({
|
|
549
|
-
settingsPath: context.claudeSettingsPath,
|
|
550
|
-
hookCommand: context.claudeHookCommand,
|
|
551
|
-
});
|
|
552
|
-
summary.push({
|
|
553
|
-
label: "Claude",
|
|
554
|
-
status: "installed",
|
|
555
|
-
detail: configured ? "Hooks already installed" : "Will install hooks",
|
|
556
|
-
});
|
|
557
|
-
} else {
|
|
558
|
-
summary.push({ label: "Claude", status: "skipped", detail: "Config not found" });
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
const geminiConfigExists = await isDir(context.geminiConfigDir);
|
|
562
|
-
if (geminiConfigExists) {
|
|
563
|
-
const configured = await isGeminiHookConfigured({
|
|
564
|
-
settingsPath: context.geminiSettingsPath,
|
|
565
|
-
hookCommand: context.geminiHookCommand,
|
|
566
|
-
});
|
|
567
|
-
summary.push({
|
|
568
|
-
label: "Gemini",
|
|
569
|
-
status: "installed",
|
|
570
|
-
detail: configured ? "Hooks already installed" : "Will install hooks",
|
|
571
|
-
});
|
|
572
|
-
} else {
|
|
573
|
-
summary.push({ label: "Gemini", status: "skipped", detail: "Config not found" });
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
const opencodeDirExists = await isDir(context.opencodeConfigDir);
|
|
577
|
-
const installed = await isOpencodePluginInstalled({ configDir: context.opencodeConfigDir });
|
|
578
|
-
const opencodeDetail = installed
|
|
579
|
-
? "Plugin already installed"
|
|
580
|
-
: opencodeDirExists
|
|
581
|
-
? "Will install plugin"
|
|
582
|
-
: "Will create config and install plugin";
|
|
583
|
-
summary.push({
|
|
584
|
-
label: "Opencode Plugin",
|
|
585
|
-
status: "installed",
|
|
586
|
-
detail: opencodeDetail,
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
const openclawState = await probeOpenclawSessionPluginState({
|
|
590
|
-
home,
|
|
591
|
-
trackerDir: context.trackerDir,
|
|
592
|
-
env: process.env,
|
|
593
|
-
});
|
|
594
|
-
if (openclawState?.skippedReason === "openclaw-config-missing") {
|
|
595
|
-
summary.push({
|
|
596
|
-
label: "OpenClaw Session Plugin",
|
|
597
|
-
status: "skipped",
|
|
598
|
-
detail: "OpenClaw config not found",
|
|
599
|
-
});
|
|
600
|
-
} else if (openclawState?.skippedReason === "openclaw-config-unreadable") {
|
|
601
|
-
summary.push({
|
|
602
|
-
label: "OpenClaw Session Plugin",
|
|
603
|
-
status: "skipped",
|
|
604
|
-
detail: openclawState.error
|
|
605
|
-
? `OpenClaw config unreadable: ${openclawState.error}`
|
|
606
|
-
: "OpenClaw config unreadable",
|
|
607
|
-
});
|
|
608
|
-
} else {
|
|
609
|
-
summary.push({
|
|
610
|
-
label: "OpenClaw Session Plugin",
|
|
611
|
-
status: openclawState?.configured ? "set" : "installed",
|
|
612
|
-
detail: openclawState?.configured
|
|
613
|
-
? "Session plugin already linked"
|
|
614
|
-
: "Will link session plugin (restart OpenClaw gateway to activate)",
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
const legacyHookState = await probeOpenclawHookState({
|
|
619
|
-
home,
|
|
620
|
-
trackerDir: context.trackerDir,
|
|
621
|
-
env: process.env,
|
|
622
|
-
});
|
|
623
|
-
if (legacyHookState?.configured || legacyHookState?.linked || legacyHookState?.enabled) {
|
|
624
|
-
summary.push({
|
|
625
|
-
label: "OpenClaw Hook (legacy)",
|
|
626
|
-
status: "updated",
|
|
627
|
-
detail: "Will remove legacy command hook during migration",
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
const codeProbe = await probeFile(context.codeConfigPath);
|
|
632
|
-
if (codeProbe.exists) {
|
|
633
|
-
const existing = await readEveryCodeNotify(context.codeConfigPath);
|
|
634
|
-
const matches = arraysEqual(existing, context.codeNotifyCmd);
|
|
635
|
-
summary.push({
|
|
636
|
-
label: "Every Code",
|
|
637
|
-
status: matches ? "set" : "updated",
|
|
638
|
-
detail: matches ? "Already configured" : "Will update config",
|
|
639
|
-
});
|
|
640
|
-
} else {
|
|
641
|
-
summary.push({ label: "Every Code", status: "skipped", detail: renderSkipDetail(codeProbe) });
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
return summary;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
function renderSkipDetail(probe) {
|
|
648
|
-
if (!probe || probe.reason === "missing") return "Config not found";
|
|
649
|
-
if (probe.reason === "permission-denied") return "Permission denied";
|
|
650
|
-
if (probe.reason === "not-file") return "Invalid config";
|
|
651
|
-
return "Unavailable";
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function arraysEqual(a, b) {
|
|
655
|
-
if (!Array.isArray(a) || !Array.isArray(b)) return false;
|
|
656
|
-
if (a.length !== b.length) return false;
|
|
657
|
-
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
658
|
-
return true;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
341
|
function parseArgs(argv) {
|
|
662
342
|
const out = {
|
|
663
343
|
baseUrl: null,
|
|
@@ -853,28 +533,6 @@ function isSelfNotify(cmd) {
|
|
|
853
533
|
|
|
854
534
|
module.exports = { cmdInit };
|
|
855
535
|
|
|
856
|
-
async function probeFile(p) {
|
|
857
|
-
try {
|
|
858
|
-
const st = await fs.stat(p);
|
|
859
|
-
if (st.isFile()) return { exists: true, reason: null };
|
|
860
|
-
return { exists: false, reason: "not-file" };
|
|
861
|
-
} catch (e) {
|
|
862
|
-
if (e?.code === "ENOENT" || e?.code === "ENOTDIR") return { exists: false, reason: "missing" };
|
|
863
|
-
if (e?.code === "EACCES" || e?.code === "EPERM")
|
|
864
|
-
return { exists: false, reason: "permission-denied" };
|
|
865
|
-
return { exists: false, reason: "error", code: e?.code || "unknown" };
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
async function isDir(p) {
|
|
870
|
-
try {
|
|
871
|
-
const st = await fs.stat(p);
|
|
872
|
-
return st.isDirectory();
|
|
873
|
-
} catch (_e) {
|
|
874
|
-
return false;
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
536
|
async function installLocalTrackerApp({ appDir }) {
|
|
879
537
|
// Copy the current package's runtime (bin + src) into ~/.vibeusage so notify can run sync without npx.
|
|
880
538
|
const packageRoot = path.resolve(__dirname, "../..");
|