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 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** | Global plugins | Message parser plugin |
144
- | **Claude Code** | `~/.claude/hooks/` | Hook configuration |
145
- | **OpenClaw** | Auto-links when installed | Gateway hook (requires restart) |
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] -->|Gateway Hook| G
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** | Gateway hook integration | - |
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
- ### Architecture Validation
382
+ ### Repository Navigation
378
383
 
379
384
  ```bash
380
- # Validate Copy Registry
381
- npm run validate:copy
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/hooks/` | 钩子配置 |
145
- | **OpenClaw** | 安装时自动链接 | Gateway 钩子(需要重启) |
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.2.22",
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 hook auto-links when OpenClaw is installed (requires gateway restart).",
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 hooks.",
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"),
@@ -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 { rootDir, trackerDir, binDir } = await resolveTrackerPaths({ home });
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 = buildIntegrationTargets({ home, trackerDir, notifyPath });
257
- const summary = await previewIntegrations({ context });
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 summary = await applyIntegrationSetup({
324
+ const integrationContext = await createIntegrationContext({
345
325
  home,
346
- trackerDir,
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, "../..");