claws-code 0.8.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.
Files changed (180) hide show
  1. package/.claude/commands/claws-auto.md +90 -0
  2. package/.claude/commands/claws-bin.md +28 -0
  3. package/.claude/commands/claws-cleanup.md +28 -0
  4. package/.claude/commands/claws-do.md +82 -0
  5. package/.claude/commands/claws-fix.md +40 -0
  6. package/.claude/commands/claws-goal.md +111 -0
  7. package/.claude/commands/claws-help.md +54 -0
  8. package/.claude/commands/claws-plan.md +103 -0
  9. package/.claude/commands/claws-report.md +29 -0
  10. package/.claude/commands/claws-status.md +37 -0
  11. package/.claude/commands/claws-update.md +32 -0
  12. package/.claude/commands/claws.md +64 -0
  13. package/.claude/rules/claws-default-behavior.md +76 -0
  14. package/.claude/settings.json +112 -0
  15. package/.claude/settings.local.json +19 -0
  16. package/.claude/skills/claws-auto-engine/SKILL.md +97 -0
  17. package/.claude/skills/claws-goal-tracker/SKILL.md +106 -0
  18. package/.claude/skills/claws-prompt-templates/SKILL.md +203 -0
  19. package/.claude/skills/claws-wave-lead/SKILL.md +126 -0
  20. package/.claude/skills/claws-wave-subworker/SKILL.md +60 -0
  21. package/CHANGELOG.md +1949 -0
  22. package/LICENSE +21 -0
  23. package/README.md +420 -0
  24. package/bin/cli.js +84 -0
  25. package/cli.js +223 -0
  26. package/docs/ARCHITECTURE.md +511 -0
  27. package/docs/event-protocol.md +588 -0
  28. package/docs/features.md +562 -0
  29. package/docs/guide.md +891 -0
  30. package/docs/index.html +716 -0
  31. package/docs/protocol.md +323 -0
  32. package/extension/.vscodeignore +15 -0
  33. package/extension/CHANGELOG.md +1906 -0
  34. package/extension/LICENSE +21 -0
  35. package/extension/README.md +137 -0
  36. package/extension/docs/features.md +424 -0
  37. package/extension/docs/protocol.md +197 -0
  38. package/extension/esbuild.mjs +25 -0
  39. package/extension/icon.png +0 -0
  40. package/extension/native/.metadata.json +10 -0
  41. package/extension/native/node-pty/LICENSE +69 -0
  42. package/extension/native/node-pty/README.md +165 -0
  43. package/extension/native/node-pty/lib/conpty_console_list_agent.js +16 -0
  44. package/extension/native/node-pty/lib/conpty_console_list_agent.js.map +1 -0
  45. package/extension/native/node-pty/lib/eventEmitter2.js +47 -0
  46. package/extension/native/node-pty/lib/eventEmitter2.js.map +1 -0
  47. package/extension/native/node-pty/lib/index.js +52 -0
  48. package/extension/native/node-pty/lib/index.js.map +1 -0
  49. package/extension/native/node-pty/lib/interfaces.js +7 -0
  50. package/extension/native/node-pty/lib/interfaces.js.map +1 -0
  51. package/extension/native/node-pty/lib/shared/conout.js +11 -0
  52. package/extension/native/node-pty/lib/shared/conout.js.map +1 -0
  53. package/extension/native/node-pty/lib/terminal.js +190 -0
  54. package/extension/native/node-pty/lib/terminal.js.map +1 -0
  55. package/extension/native/node-pty/lib/types.js +7 -0
  56. package/extension/native/node-pty/lib/types.js.map +1 -0
  57. package/extension/native/node-pty/lib/unixTerminal.js +346 -0
  58. package/extension/native/node-pty/lib/unixTerminal.js.map +1 -0
  59. package/extension/native/node-pty/lib/utils.js +39 -0
  60. package/extension/native/node-pty/lib/utils.js.map +1 -0
  61. package/extension/native/node-pty/lib/windowsConoutConnection.js +125 -0
  62. package/extension/native/node-pty/lib/windowsConoutConnection.js.map +1 -0
  63. package/extension/native/node-pty/lib/windowsPtyAgent.js +320 -0
  64. package/extension/native/node-pty/lib/windowsPtyAgent.js.map +1 -0
  65. package/extension/native/node-pty/lib/windowsTerminal.js +199 -0
  66. package/extension/native/node-pty/lib/windowsTerminal.js.map +1 -0
  67. package/extension/native/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  68. package/extension/native/node-pty/lib/worker/conoutSocketWorker.js.map +1 -0
  69. package/extension/native/node-pty/package.json +64 -0
  70. package/extension/native/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  71. package/extension/native/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  72. package/extension/native/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  73. package/extension/native/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  74. package/extension/native/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  75. package/extension/native/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  76. package/extension/native/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  77. package/extension/native/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  78. package/extension/native/node-pty/prebuilds/win32-arm64/pty.node +0 -0
  79. package/extension/native/node-pty/prebuilds/win32-arm64/winpty-agent.exe +0 -0
  80. package/extension/native/node-pty/prebuilds/win32-arm64/winpty.dll +0 -0
  81. package/extension/native/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  82. package/extension/native/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  83. package/extension/native/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  84. package/extension/native/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  85. package/extension/native/node-pty/prebuilds/win32-x64/pty.node +0 -0
  86. package/extension/native/node-pty/prebuilds/win32-x64/winpty-agent.exe +0 -0
  87. package/extension/native/node-pty/prebuilds/win32-x64/winpty.dll +0 -0
  88. package/extension/package-lock.json +605 -0
  89. package/extension/package.json +343 -0
  90. package/extension/scripts/bundle-native.mjs +104 -0
  91. package/extension/scripts/deploy-dev.mjs +60 -0
  92. package/extension/src/ansi-strip.ts +52 -0
  93. package/extension/src/backends/vscode/claws-pty.ts +483 -0
  94. package/extension/src/backends/vscode/status-bar.ts +99 -0
  95. package/extension/src/backends/vscode/vscode-backend.ts +282 -0
  96. package/extension/src/capture-store.ts +125 -0
  97. package/extension/src/event-log.ts +629 -0
  98. package/extension/src/event-schemas.ts +478 -0
  99. package/extension/src/extension.js +492 -0
  100. package/extension/src/extension.ts +873 -0
  101. package/extension/src/lifecycle-engine.ts +60 -0
  102. package/extension/src/lifecycle-rules.ts +171 -0
  103. package/extension/src/lifecycle-store.ts +506 -0
  104. package/extension/src/peer-registry.ts +176 -0
  105. package/extension/src/pipeline-registry.ts +82 -0
  106. package/extension/src/platform.ts +64 -0
  107. package/extension/src/protocol.ts +532 -0
  108. package/extension/src/server-config.ts +98 -0
  109. package/extension/src/server.ts +2210 -0
  110. package/extension/src/task-registry.ts +51 -0
  111. package/extension/src/terminal-backend.ts +211 -0
  112. package/extension/src/terminal-manager.ts +395 -0
  113. package/extension/src/topic-registry.ts +70 -0
  114. package/extension/src/topic-utils.ts +46 -0
  115. package/extension/src/transport.ts +45 -0
  116. package/extension/src/uninstall-cleanup.ts +232 -0
  117. package/extension/src/wave-registry.ts +314 -0
  118. package/extension/src/websocket-transport.ts +153 -0
  119. package/extension/tsconfig.json +23 -0
  120. package/lib/capabilities.js +145 -0
  121. package/lib/dry-run.js +43 -0
  122. package/lib/install.js +1018 -0
  123. package/lib/mcp-setup.js +92 -0
  124. package/lib/platform.js +240 -0
  125. package/lib/preflight.js +152 -0
  126. package/lib/shell-hook.js +343 -0
  127. package/lib/uninstall.js +162 -0
  128. package/lib/verify.js +166 -0
  129. package/mcp_server.js +3529 -0
  130. package/package.json +48 -0
  131. package/rules/claws-default-behavior.md +72 -0
  132. package/scripts/_helpers/atomic-file.mjs +137 -0
  133. package/scripts/_helpers/fix-repair.js +64 -0
  134. package/scripts/_helpers/json-safe.mjs +218 -0
  135. package/scripts/bump-version.sh +84 -0
  136. package/scripts/codegen/gen-docs.mjs +61 -0
  137. package/scripts/codegen/gen-json-schema.mjs +62 -0
  138. package/scripts/codegen/gen-mcp-tools.mjs +358 -0
  139. package/scripts/codegen/gen-types.mjs +172 -0
  140. package/scripts/codegen/index.mjs +42 -0
  141. package/scripts/dev-hooks/check-extension-dirs.js +77 -0
  142. package/scripts/dev-hooks/check-open-claws-terminals.js +70 -0
  143. package/scripts/dev-hooks/check-stale-main.js +55 -0
  144. package/scripts/dev-hooks/check-tag-pushed.js +51 -0
  145. package/scripts/dev-hooks/check-tag-vs-main.js +56 -0
  146. package/scripts/dev-vsix-install.sh +60 -0
  147. package/scripts/fix.sh +702 -0
  148. package/scripts/gen-client-types.mjs +81 -0
  149. package/scripts/git-hooks/pre-commit +31 -0
  150. package/scripts/hooks/lifecycle-state.js +61 -0
  151. package/scripts/hooks/package.json +4 -0
  152. package/scripts/hooks/post-tool-use-claws.js +292 -0
  153. package/scripts/hooks/pre-bash-no-verify-block.js +72 -0
  154. package/scripts/hooks/pre-tool-use-claws.js +206 -0
  155. package/scripts/hooks/session-start-claws.js +97 -0
  156. package/scripts/hooks/stop-claws.js +88 -0
  157. package/scripts/inject-claude-md.js +205 -0
  158. package/scripts/inject-dev-hooks.js +96 -0
  159. package/scripts/inject-global-claude-md.js +140 -0
  160. package/scripts/inject-settings-hooks.js +370 -0
  161. package/scripts/install.ps1 +146 -0
  162. package/scripts/install.sh +1729 -0
  163. package/scripts/monitor-arm-watch.js +155 -0
  164. package/scripts/rebuild-node-pty.sh +245 -0
  165. package/scripts/report.sh +232 -0
  166. package/scripts/shell-hook.fish +164 -0
  167. package/scripts/shell-hook.ps1 +33 -0
  168. package/scripts/shell-hook.sh +232 -0
  169. package/scripts/stream-events.js +399 -0
  170. package/scripts/terminal-wrapper.sh +36 -0
  171. package/scripts/test-enforcement.sh +132 -0
  172. package/scripts/test-install.sh +174 -0
  173. package/scripts/test-installer-parity.sh +135 -0
  174. package/scripts/test-template-enforcement.sh +76 -0
  175. package/scripts/uninstall.sh +143 -0
  176. package/scripts/update.sh +337 -0
  177. package/scripts/verify-release.sh +323 -0
  178. package/scripts/verify-wrapped.sh +194 -0
  179. package/templates/CLAUDE.global.md +135 -0
  180. package/templates/CLAUDE.project.md +37 -0
package/scripts/fix.sh ADDED
@@ -0,0 +1,702 @@
1
+ #!/usr/bin/env bash
2
+ # Claws — auto-diagnosis and repair
3
+ # Checks every piece of the install chain and repairs what it can. Run this
4
+ # when claws_* tools aren't showing up or something feels broken.
5
+ #
6
+ # Usage: bash ~/.claws-src/scripts/fix.sh [project-root]
7
+ #
8
+ # The slash command /claws-fix is a thin dispatcher that calls this script.
9
+ # Add new checks/repairs here — they'll be picked up on the next git pull
10
+ # without any change to the slash-command markdown.
11
+
12
+ set -eo pipefail
13
+
14
+ INSTALL_DIR="${CLAWS_DIR:-$HOME/.claws-src}"
15
+ PROJECT_ROOT="${1:-$(pwd)}"
16
+
17
+ # ─── Colors ────────────────────────────────────────────────────────────────
18
+ if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
19
+ C_RESET='\033[0m'; C_BOLD='\033[1m'
20
+ C_GREEN='\033[0;32m'; C_YELLOW='\033[0;33m'; C_RED='\033[0;31m'; C_DIM='\033[2m'
21
+ else
22
+ C_RESET=''; C_BOLD=''; C_GREEN=''; C_YELLOW=''; C_RED=''; C_DIM=''
23
+ fi
24
+ check() { printf "${C_BOLD}[check]${C_RESET} %s\n" "$*"; }
25
+ ok() { printf " ${C_GREEN}✓${C_RESET} %s\n" "$*"; }
26
+ fix() { printf " ${C_YELLOW}→${C_RESET} %s\n" "$*"; }
27
+ fail() { printf " ${C_RED}✗${C_RESET} %s\n" "$*"; }
28
+
29
+ FIXED=0
30
+ ISSUES=0
31
+
32
+ # ─── 0. System dependencies ────────────────────────────────────────────────
33
+ # Fast precheck so users see a clear answer when something upstream broke
34
+ # (e.g. they nvm'd to an old Node, uninstalled Xcode CLT, etc).
35
+ PLATFORM="$(uname -s)"
36
+ check "System dependencies"
37
+ if command -v git &>/dev/null; then ok "git ($(git --version | awk '{print $3}'))"
38
+ else fail "git not found — install: xcode-select --install (macOS) or sudo apt install git"; ISSUES=$((ISSUES+1)); fi
39
+
40
+ if command -v node &>/dev/null; then
41
+ NODE_MAJOR=$(node -e "console.log(process.versions.node.split('.')[0])" 2>/dev/null || echo "0")
42
+ if [ "$NODE_MAJOR" -lt 18 ] 2>/dev/null; then
43
+ fail "node $(node --version) too old — Claws requires Node 18+"; ISSUES=$((ISSUES+1))
44
+ else
45
+ ok "node ($(node --version))"
46
+ fi
47
+ else
48
+ fail "node not found — required for MCP server"; ISSUES=$((ISSUES+1))
49
+ fi
50
+
51
+ if command -v npm &>/dev/null; then ok "npm ($(npm --version))"
52
+ else fail "npm not found — required for extension build"; ISSUES=$((ISSUES+1)); fi
53
+
54
+ if command -v python3 &>/dev/null; then ok "python3 ($(python3 --version 2>&1 | awk '{print $2}'))"
55
+ else
56
+ # python3 is only actually needed when node-pty needs to compile from
57
+ # source, which only happens if no prebuild matches the current Node
58
+ # version. Inform but don't count as a failure.
59
+ printf " ${C_YELLOW}!${C_RESET} python3 not found — only needed if node-pty needs source compile\n"
60
+ fi
61
+
62
+ case "$PLATFORM" in
63
+ Darwin)
64
+ if xcode-select -p &>/dev/null; then ok "Xcode Command Line Tools"
65
+ else
66
+ printf " ${C_YELLOW}!${C_RESET} Xcode CLT missing — needed only if node-pty must compile (run: xcode-select --install)\n"
67
+ fi
68
+ ;;
69
+ Linux)
70
+ if command -v g++ &>/dev/null; then ok "g++ ($(g++ -dumpversion 2>/dev/null))"
71
+ else printf " ${C_YELLOW}!${C_RESET} g++ missing — needed only for node-pty source compile (run: sudo apt install build-essential)\n"
72
+ fi
73
+ ;;
74
+ esac
75
+
76
+ # ─── 1. Source clone ───────────────────────────────────────────────────────
77
+ check "Claws source clone at $INSTALL_DIR"
78
+ if [ -d "$INSTALL_DIR/.git" ]; then
79
+ ok "clone exists ($(cd "$INSTALL_DIR" && git log --oneline -1 2>/dev/null))"
80
+ else
81
+ fail "no clone at $INSTALL_DIR"
82
+ fix "run the installer: bash <(curl -fsSL https://raw.githubusercontent.com/neunaha/claws/main/scripts/install.sh)"
83
+ ISSUES=$((ISSUES+1))
84
+ exit 1
85
+ fi
86
+
87
+ # ─── 1b. node-pty native binary (ABI-correct for VS Code's Electron) ──────
88
+ # node-pty MUST be compiled against VS Code's Electron-embedded Node, not
89
+ # system Node. A binary built with plain `node-gyp rebuild` against system
90
+ # Node 24 silently fails to load in Electron 39 (Node 22) and the extension
91
+ # falls back to pipe-mode. Use @electron/rebuild to target the correct ABI.
92
+ check "node-pty native binary (for glitch-free wrapped terminals)"
93
+ NPTY_BIN="$INSTALL_DIR/extension/node_modules/node-pty/build/Release/pty.node"
94
+ ELECTRON_ABI_FILE="$INSTALL_DIR/extension/dist/.electron-abi"
95
+ ELECTRON_VERSION=""
96
+ if [ "$(uname)" = "Darwin" ]; then
97
+ # M-32: prefer $TERM_PROGRAM-matching editor; M-33-compat: CURSOR_CHANNEL overrides vscode.
98
+ _fix_tp="${TERM_PROGRAM:-}"
99
+ [ "$_fix_tp" = "vscode" ] && [ -n "${CURSOR_CHANNEL:-}" ] && _fix_tp="cursor"
100
+ _fix_tp=$(echo "$_fix_tp" | tr '[:upper:]' '[:lower:]')
101
+ case "$_fix_tp" in
102
+ cursor) _fix_darwin_apps=('/Applications/Cursor.app' '/Applications/Visual Studio Code.app' '/Applications/Visual Studio Code - Insiders.app' '/Applications/Windsurf.app') ;;
103
+ windsurf) _fix_darwin_apps=('/Applications/Windsurf.app' '/Applications/Visual Studio Code.app' '/Applications/Visual Studio Code - Insiders.app' '/Applications/Cursor.app') ;;
104
+ *) _fix_darwin_apps=('/Applications/Visual Studio Code.app' '/Applications/Visual Studio Code - Insiders.app' '/Applications/Cursor.app' '/Applications/Windsurf.app') ;;
105
+ esac
106
+ for _fix_app in "${_fix_darwin_apps[@]}"; do
107
+ plist="$_fix_app/Contents/Frameworks/Electron Framework.framework/Resources/Info.plist"
108
+ if [ -f "$plist" ]; then
109
+ v=$(plutil -extract CFBundleVersion raw "$plist" 2>/dev/null || true)
110
+ [ -n "$v" ] && ELECTRON_VERSION="$v" && break
111
+ fi
112
+ done
113
+ elif [ "$(uname)" = "Linux" ]; then
114
+ # M-33: Linux Cursor/Windsurf paths + TERM_PROGRAM ordering.
115
+ _fix_tp="${TERM_PROGRAM:-}"
116
+ [ "$_fix_tp" = "vscode" ] && [ -n "${CURSOR_CHANNEL:-}" ] && _fix_tp="cursor"
117
+ _fix_tp=$(echo "$_fix_tp" | tr '[:upper:]' '[:lower:]')
118
+ _fix_linux_vscode=('/usr/share/code/electron' '/usr/lib/code/electron' '/opt/visual-studio-code/electron' '/snap/code/current/electron')
119
+ _fix_linux_cursor=('/usr/share/cursor/electron' '/opt/cursor/electron' '/snap/cursor/current/usr/share/cursor/electron')
120
+ _fix_linux_windsurf=('/usr/share/windsurf/electron' '/opt/windsurf/electron')
121
+ _fix_linux_candidates=()
122
+ case "$_fix_tp" in
123
+ cursor) _fix_linux_candidates=("${_fix_linux_cursor[@]}" "${_fix_linux_vscode[@]}" "${_fix_linux_windsurf[@]}") ;;
124
+ windsurf) _fix_linux_candidates=("${_fix_linux_windsurf[@]}" "${_fix_linux_vscode[@]}" "${_fix_linux_cursor[@]}") ;;
125
+ *) _fix_linux_candidates=("${_fix_linux_vscode[@]}" "${_fix_linux_cursor[@]}" "${_fix_linux_windsurf[@]}") ;;
126
+ esac
127
+ for _fix_ep in "${_fix_linux_candidates[@]}"; do
128
+ if [ -x "$_fix_ep" ]; then
129
+ v=$("$_fix_ep" --version 2>/dev/null | sed 's/^v//' || true)
130
+ if echo "$v" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then ELECTRON_VERSION="$v" && break; fi
131
+ fi
132
+ done
133
+ fi
134
+ [ -z "$ELECTRON_VERSION" ] && ELECTRON_VERSION="39.8.5"
135
+ LAST_ABI=$(cat "$ELECTRON_ABI_FILE" 2>/dev/null || echo "")
136
+
137
+ if [ -f "$NPTY_BIN" ] && [ "$LAST_ABI" = "$ELECTRON_VERSION" ]; then
138
+ ok "node-pty binary OK for Electron $ELECTRON_VERSION ($(wc -c < "$NPTY_BIN" | tr -d ' ') bytes)"
139
+ elif [ -d "$INSTALL_DIR/extension/node_modules/node-pty" ]; then
140
+ if [ -f "$NPTY_BIN" ] && [ "$LAST_ABI" != "$ELECTRON_VERSION" ]; then
141
+ fix "node-pty built for Electron '$LAST_ABI', need '$ELECTRON_VERSION' — rebuilding"
142
+ else
143
+ fix "node-pty binary missing — rebuilding for Electron $ELECTRON_VERSION"
144
+ fi
145
+ if [ "$(uname)" = "Darwin" ] && ! xcode-select -p &>/dev/null; then
146
+ fail "Xcode Command Line Tools required — run: xcode-select --install"
147
+ ISSUES=$((ISSUES+1))
148
+ else
149
+ # M-31: 5-minute timeout ceiling — prevents indefinite hang on slow Electron header fetch.
150
+ _fix_timeout_cmd=""
151
+ if command -v timeout >/dev/null 2>&1; then _fix_timeout_cmd="timeout 300"
152
+ elif command -v gtimeout >/dev/null 2>&1; then _fix_timeout_cmd="gtimeout 300"
153
+ fi
154
+ if ( cd "$INSTALL_DIR/extension" && $_fix_timeout_cmd npx --yes @electron/rebuild --version="$ELECTRON_VERSION" --only=node-pty --force >/dev/null 2>&1 ); then
155
+ _fix_rebuild_rc=0
156
+ else
157
+ _fix_rebuild_rc=$?
158
+ fi
159
+ if [ "$_fix_rebuild_rc" = "124" ]; then
160
+ fail "@electron/rebuild timed out after 5 min — likely a slow Electron headers download. Check network / proxy settings."
161
+ ISSUES=$((ISSUES+1))
162
+ elif [ "$_fix_rebuild_rc" = "0" ] && [ -f "$NPTY_BIN" ]; then
163
+ echo "$ELECTRON_VERSION" > "$ELECTRON_ABI_FILE" 2>/dev/null || true
164
+ ok "node-pty rebuilt for Electron $ELECTRON_VERSION in source clone"
165
+
166
+ # Propagate the rebuilt binary into the source's bundled native/ slot
167
+ # AND into every installed extension directory. Without this, VS Code
168
+ # keeps loading the OLD pty.node from ~/.vscode/extensions/neunaha.claws-X/
169
+ # and the rebuild has no visible effect after reload. (Audit gap #3.)
170
+ SOURCE_NATIVE_DEST="$INSTALL_DIR/extension/native/node-pty/build/Release/pty.node"
171
+ if [ -f "$NPTY_BIN" ] && [ -d "$INSTALL_DIR/extension/native/node-pty/build/Release" ]; then
172
+ cp -f "$NPTY_BIN" "$SOURCE_NATIVE_DEST" 2>/dev/null && ok "propagated to source native/ bundle"
173
+ # Update the metadata.json electronVersion to match
174
+ node --no-deprecation -e "
175
+ const fs=require('fs');
176
+ const p='$INSTALL_DIR/extension/native/.metadata.json';
177
+ try { const m=JSON.parse(fs.readFileSync(p,'utf8')); m.electronVersion='$ELECTRON_VERSION'; m.bundledAt=new Date().toISOString(); fs.writeFileSync(p,JSON.stringify(m,null,2)+'\n'); } catch(e){}
178
+ " 2>/dev/null || true
179
+ fi
180
+ PROPAGATED=0
181
+ for ext_root in "$HOME/.vscode/extensions" "$HOME/.vscode-insiders/extensions" "$HOME/.cursor/extensions" "$HOME/.windsurf/extensions"; do
182
+ [ -d "$ext_root" ] || continue
183
+ for inst in "$ext_root"/neunaha.claws-*; do
184
+ [ -d "$inst" ] || continue
185
+ # Skip if this is a symlink pointing back at the source clone (already updated via source propagation above)
186
+ if [ -L "$inst" ]; then
187
+ ok "skipped $(basename "$inst") — symlink to source"
188
+ PROPAGATED=$((PROPAGATED+1))
189
+ continue
190
+ fi
191
+ target_dir="$inst/native/node-pty/build/Release"
192
+ target="$target_dir/pty.node"
193
+ if [ -d "$target_dir" ]; then
194
+ mkdir -p "$target_dir" 2>/dev/null || true
195
+ if cp -f "$NPTY_BIN" "$target" 2>/dev/null; then
196
+ ok "propagated to $(basename "$inst")"
197
+ PROPAGATED=$((PROPAGATED+1))
198
+ else
199
+ fail "could not write to $target (permissions?)"
200
+ ISSUES=$((ISSUES+1))
201
+ fi
202
+ fi
203
+ done
204
+ done
205
+ if [ "$PROPAGATED" -eq 0 ]; then
206
+ printf " ${C_YELLOW}!${C_RESET} no installed extension dirs found to propagate to — reload VS Code or run install.sh\n"
207
+ fi
208
+ fix "reload VS Code now: Cmd+Shift+P → Developer: Reload Window"
209
+ FIXED=$((FIXED+1))
210
+ else
211
+ fail "@electron/rebuild failed — wrapped terminals will use pipe-mode"
212
+ ISSUES=$((ISSUES+1))
213
+ fi
214
+ fi
215
+ else
216
+ info "node-pty not installed — extension bundle may be missing too (see check 2)"
217
+ fi
218
+
219
+ # ─── 2. Extension bundle ───────────────────────────────────────────────────
220
+ check "Extension bundle"
221
+ if [ -f "$INSTALL_DIR/extension/dist/extension.js" ]; then
222
+ ok "built ($(wc -c < "$INSTALL_DIR/extension/dist/extension.js" | tr -d ' ') bytes)"
223
+ elif [ -f "$INSTALL_DIR/extension/src/extension.js" ]; then
224
+ fix "bundle missing — repointing main to legacy src/extension.js"
225
+ node --no-deprecation -e "const fs=require('fs'),p='$INSTALL_DIR/extension/package.json';const j=JSON.parse(fs.readFileSync(p,'utf8'));j.main='./src/extension.js';fs.writeFileSync(p,JSON.stringify(j,null,2));" 2>/dev/null || true
226
+ FIXED=$((FIXED+1))
227
+ # Try to rebuild
228
+ if command -v npm &>/dev/null; then
229
+ fix "attempting to rebuild with npm"
230
+ ( cd "$INSTALL_DIR/extension" && npm install --no-audit --no-fund --loglevel=error --silent && npm run build --silent ) 2>&1 | tail -3 || true
231
+ [ -f "$INSTALL_DIR/extension/dist/extension.js" ] && ok "rebuilt" && FIXED=$((FIXED+1))
232
+ fi
233
+ else
234
+ fail "no bundle and no legacy JS fallback"
235
+ ISSUES=$((ISSUES+1))
236
+ fi
237
+
238
+ # ─── 3. Extension installed in editors ─────────────────────────────────────
239
+ check "Claws extension installed in VS Code / Cursor / etc"
240
+ EXT_VERSION=$(node --no-deprecation -e "try{console.log(require('$INSTALL_DIR/extension/package.json').version)}catch(e){console.log('0.7.13')}" 2>/dev/null || echo "0.7.13")
241
+
242
+ FOUND_INSTALLS=()
243
+ for dir in "$HOME/.vscode/extensions" "$HOME/.vscode-insiders/extensions" "$HOME/.cursor/extensions" "$HOME/.windsurf/extensions"; do
244
+ [ -d "$dir" ] || continue
245
+ for inst in "$dir"/neunaha.claws-*; do
246
+ [ -d "$inst" ] || [ -L "$inst" ] || continue
247
+ FOUND_INSTALLS+=("$(basename "$dir"): $(basename "$inst")")
248
+ done
249
+ done
250
+
251
+ # Warn about DUPLICATE extensions in the same editor dir (FINDING-C-4)
252
+ declare -A _EDITOR_COUNT
253
+ for entry in "${FOUND_INSTALLS[@]}"; do
254
+ editor="${entry%%:*}"
255
+ _EDITOR_COUNT["$editor"]=$(( ${_EDITOR_COUNT["$editor"]:-0} + 1 ))
256
+ done
257
+ for editor in "${!_EDITOR_COUNT[@]}"; do
258
+ if [ "${_EDITOR_COUNT[$editor]}" -gt 1 ]; then
259
+ fail "DUPLICATE extensions in $editor (${_EDITOR_COUNT[$editor]} copies) — remove old versions to avoid load-order conflict"
260
+ ISSUES=$((ISSUES+1))
261
+ fi
262
+ done
263
+ unset _EDITOR_COUNT
264
+
265
+ if [ "${#FOUND_INSTALLS[@]}" -gt 0 ]; then
266
+ for entry in "${FOUND_INSTALLS[@]}"; do
267
+ ok "installed → $entry"
268
+ done
269
+ else
270
+ fix "no Claws extension found in any editor — packaging VSIX and installing"
271
+ VSIX_PATH="/tmp/claws-$EXT_VERSION.vsix"
272
+ if command -v npx &>/dev/null && ( cd "$INSTALL_DIR/extension" && npx --yes @vscode/vsce package --skip-license --no-git-tag-version --no-update-package-json --out "$VSIX_PATH" >/dev/null 2>&1 ); then
273
+ # Try each editor CLI
274
+ INSTALLED=0
275
+ for pair in \
276
+ "code:/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" \
277
+ "code-insiders:/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders" \
278
+ "cursor:/Applications/Cursor.app/Contents/Resources/app/bin/cursor" \
279
+ "windsurf:/Applications/Windsurf.app/Contents/Resources/app/bin/windsurf"; do
280
+ label="${pair%%:*}"
281
+ bundled="${pair#*:}"
282
+ cli=""
283
+ if command -v "$label" &>/dev/null; then cli="$(command -v "$label")"
284
+ elif [ -x "$bundled" ]; then cli="$bundled"
285
+ else continue
286
+ fi
287
+ if "$cli" --install-extension "$VSIX_PATH" --force >/dev/null 2>&1; then
288
+ ok "installed into $label"
289
+ INSTALLED=$((INSTALLED+1))
290
+ fi
291
+ done
292
+ if [ "$INSTALLED" -gt 0 ]; then
293
+ FIXED=$((FIXED+1))
294
+ else
295
+ # Fall back to symlink
296
+ TARGET_DIR=""
297
+ for dir in "$HOME/.vscode/extensions" "$HOME/.vscode-insiders/extensions" "$HOME/.cursor/extensions" "$HOME/.windsurf/extensions"; do
298
+ [ -d "$dir" ] && TARGET_DIR="$dir" && break
299
+ done
300
+ [ -z "$TARGET_DIR" ] && TARGET_DIR="$HOME/.vscode/extensions" && mkdir -p "$TARGET_DIR"
301
+ rm -f "$TARGET_DIR"/neunaha.claws-* 2>/dev/null || true
302
+ if ln -sf "$INSTALL_DIR/extension" "$TARGET_DIR/neunaha.claws-$EXT_VERSION" 2>/dev/null \
303
+ || sudo ln -sf "$INSTALL_DIR/extension" "$TARGET_DIR/neunaha.claws-$EXT_VERSION" 2>/dev/null; then
304
+ ok "fallback symlink created at $TARGET_DIR/neunaha.claws-$EXT_VERSION"
305
+ FIXED=$((FIXED+1))
306
+ else
307
+ fail "could not install or symlink extension"
308
+ ISSUES=$((ISSUES+1))
309
+ fi
310
+ fi
311
+ else
312
+ fail "npx/vsce unavailable and no existing install found"
313
+ ISSUES=$((ISSUES+1))
314
+ fi
315
+ fi
316
+
317
+ # ─── 4. Project-local MCP registration ─────────────────────────────────────
318
+ check "Project .mcp.json at $PROJECT_ROOT"
319
+ PROJECT_MCP="$PROJECT_ROOT/.mcp.json"
320
+ if [ -f "$PROJECT_MCP" ] && grep -q '"claws"' "$PROJECT_MCP" 2>/dev/null; then
321
+ ok "claws registered in project"
322
+ else
323
+ fix "not registered — adding claws to $PROJECT_MCP"
324
+ # FINDING-B-2 (fix.sh mirror): guard against dangling symlinks before mkdir -p
325
+ if [ -L "$PROJECT_ROOT/.claws-bin" ]; then
326
+ warn ".claws-bin is a symlink — removing before mkdir"
327
+ rm -f "$PROJECT_ROOT/.claws-bin"
328
+ fi
329
+ mkdir -p "$PROJECT_ROOT/.claws-bin"
330
+ cp "$INSTALL_DIR/mcp_server.js" "$PROJECT_ROOT/.claws-bin/mcp_server.js"
331
+ chmod +x "$PROJECT_ROOT/.claws-bin/mcp_server.js"
332
+ # M-45: use fix-repair.js (json-safe.mjs: abort-on-malformed + atomic write).
333
+ # Path passed via env var — no string-interpolation into JS source (M-20).
334
+ if CLAWS_REPAIR_TARGET="$PROJECT_MCP" node --no-deprecation "$INSTALL_DIR/scripts/_helpers/fix-repair.js" mcp 2>&1 | sed 's/^/ /'; then
335
+ ok "wrote $PROJECT_MCP"
336
+ FIXED=$((FIXED+1))
337
+ else
338
+ fail "could not write $PROJECT_MCP — malformed JSON? Check backup above."
339
+ ISSUES=$((ISSUES+1))
340
+ fi
341
+ fi
342
+
343
+ # ─── 4b. Project .vscode/extensions.json recommends claws ─────────────────
344
+ check "Project .vscode/extensions.json recommends neunaha.claws"
345
+ VSCODE_EXT_JSON="$PROJECT_ROOT/.vscode/extensions.json"
346
+ if [ -f "$VSCODE_EXT_JSON" ] && grep -q "neunaha.claws" "$VSCODE_EXT_JSON" 2>/dev/null; then
347
+ ok "already recommended"
348
+ else
349
+ fix "adding neunaha.claws to workspace recommendations"
350
+ mkdir -p "$PROJECT_ROOT/.vscode"
351
+ # M-46: use fix-repair.js (json-safe.mjs: abort-on-malformed + atomic write + JSONC-tolerant).
352
+ # Path passed via env var — no string-interpolation into JS source (M-20).
353
+ if CLAWS_REPAIR_TARGET="$VSCODE_EXT_JSON" node --no-deprecation "$INSTALL_DIR/scripts/_helpers/fix-repair.js" extensions 2>&1 | sed 's/^/ /'; then
354
+ ok "wrote $VSCODE_EXT_JSON"
355
+ FIXED=$((FIXED+1))
356
+ else
357
+ fail "could not write $VSCODE_EXT_JSON — malformed JSON? Check backup above."
358
+ ISSUES=$((ISSUES+1))
359
+ fi
360
+ fi
361
+
362
+ # ─── 4c. .claws-bin integrity (unconditional, independent of .mcp.json) ──────
363
+ check ".claws-bin integrity"
364
+ CLAWS_BIN="$PROJECT_ROOT/.claws-bin"
365
+ CLAWS_BIN_SERVER="$CLAWS_BIN/mcp_server.js"
366
+ BIN_OK=1
367
+ if [ ! -d "$CLAWS_BIN" ]; then
368
+ fix ".claws-bin directory missing — creating and deploying mcp_server.js"
369
+ mkdir -p "$CLAWS_BIN"
370
+ BIN_OK=0
371
+ elif [ -L "$CLAWS_BIN_SERVER" ] && [ ! -e "$CLAWS_BIN_SERVER" ]; then
372
+ fix ".claws-bin/mcp_server.js is a dangling symlink — removing"
373
+ rm -f "$CLAWS_BIN_SERVER"
374
+ BIN_OK=0
375
+ elif [ ! -f "$CLAWS_BIN_SERVER" ]; then
376
+ fix ".claws-bin/mcp_server.js missing"
377
+ BIN_OK=0
378
+ fi
379
+ if [ "$BIN_OK" -eq 0 ]; then
380
+ if cp "$INSTALL_DIR/mcp_server.js" "$CLAWS_BIN_SERVER" 2>/dev/null && chmod +x "$CLAWS_BIN_SERVER"; then
381
+ ok ".claws-bin/mcp_server.js restored from $INSTALL_DIR"
382
+ FIXED=$((FIXED+1))
383
+ else
384
+ fail "could not copy mcp_server.js to $CLAWS_BIN — check permissions"
385
+ ISSUES=$((ISSUES+1))
386
+ fi
387
+ else
388
+ ok ".claws-bin/mcp_server.js present"
389
+ fi
390
+
391
+ # Fix #1 (v0.7.12): ensure .claws-bin/package.json exists with {"type":"commonjs"}.
392
+ # Auto-restores so existing installs without it get fixed by running /claws-fix.
393
+ if [ -d "$CLAWS_BIN" ] && [ ! -f "$CLAWS_BIN/package.json" ]; then
394
+ fix ".claws-bin/package.json missing — writing ESM compat shim"
395
+ printf '{\n "type": "commonjs",\n "_comment": "Forces CommonJS for .js files in .claws-bin/. Required when the parent project has type:module in its package.json (Next.js, Vite, etc.)"\n}\n' > "$CLAWS_BIN/package.json"
396
+ ok "wrote .claws-bin/package.json"
397
+ FIXED=$((FIXED+1))
398
+ else
399
+ ok ".claws-bin/package.json present (ESM compat shim)"
400
+ fi
401
+
402
+ # ─── 5. MCP server handshake ───────────────────────────────────────────────
403
+ check "MCP server handshake"
404
+ MCP_PATH="$INSTALL_DIR/mcp_server.js"
405
+ [ -f "$PROJECT_ROOT/.claws-bin/mcp_server.js" ] && MCP_PATH="$PROJECT_ROOT/.claws-bin/mcp_server.js"
406
+ if command -v node &>/dev/null && [ -f "$MCP_PATH" ]; then
407
+ # M-44: mcp_server.js uses newline-delimited JSON, not Content-Length framing.
408
+ # Full protocolVersion + clientInfo required per MCP 2024-11-05 spec.
409
+ HANDSHAKE=$(node --no-deprecation -e '
410
+ const { spawn } = require("child_process");
411
+ const mcp = spawn("node", [process.argv[1]], { stdio: ["pipe", "pipe", "ignore"] });
412
+ const req = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "claws-fix", version: "1" } } });
413
+ let buf = "";
414
+ const done = (c, o) => { try { mcp.kill(); } catch {} process.stdout.write(o); process.exit(c); };
415
+ const timer = setTimeout(() => done(1, "TIMEOUT"), 4000);
416
+ mcp.stdout.on("data", d => { buf += d.toString("utf8"); if (buf.includes("claws")) { clearTimeout(timer); done(0, buf.slice(0, 200)); } });
417
+ mcp.on("error", e => { clearTimeout(timer); done(1, "SPAWN_ERROR: " + e.message); });
418
+ mcp.stdin.write(req + "\n");
419
+ ' "$MCP_PATH" 2>&1 || echo "FAILED")
420
+ if echo "$HANDSHAKE" | grep -q "claws"; then
421
+ ok "MCP server responds (initialize OK)"
422
+ else
423
+ fail "MCP server failed to respond: ${HANDSHAKE:0:200}"
424
+ fix "refreshing mcp_server.js from $INSTALL_DIR and re-probing"
425
+ if cp "$INSTALL_DIR/mcp_server.js" "$PROJECT_ROOT/.claws-bin/mcp_server.js" 2>/dev/null; then
426
+ chmod +x "$PROJECT_ROOT/.claws-bin/mcp_server.js" 2>/dev/null || true
427
+ HANDSHAKE2=$(node --no-deprecation -e '
428
+ const { spawn } = require("child_process");
429
+ const mcp = spawn("node", [process.argv[1]], { stdio: ["pipe", "pipe", "ignore"] });
430
+ const req = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "claws-fix", version: "1" } } });
431
+ let buf = "";
432
+ const done = (c, o) => { try { mcp.kill(); } catch {} process.stdout.write(o); process.exit(c); };
433
+ const timer = setTimeout(() => done(1, "TIMEOUT"), 4000);
434
+ mcp.stdout.on("data", d => { buf += d.toString("utf8"); if (buf.includes("claws")) { clearTimeout(timer); done(0, buf.slice(0, 200)); } });
435
+ mcp.on("error", e => { clearTimeout(timer); done(1, "SPAWN_ERROR: " + e.message); });
436
+ mcp.stdin.write(req + "\n");
437
+ ' "$PROJECT_ROOT/.claws-bin/mcp_server.js" 2>&1 || echo "FAILED")
438
+ if echo "$HANDSHAKE2" | grep -q "claws"; then
439
+ ok "MCP server responds after refresh"
440
+ FIXED=$((FIXED+1))
441
+ else
442
+ fail "MCP server still unresponsive after refresh: ${HANDSHAKE2:0:200}"
443
+ ISSUES=$((ISSUES+1))
444
+ fi
445
+ else
446
+ fail "could not copy mcp_server.js from $INSTALL_DIR — check permissions"
447
+ ISSUES=$((ISSUES+1))
448
+ fi
449
+ fi
450
+ else
451
+ fail "node or $MCP_PATH missing"
452
+ ISSUES=$((ISSUES+1))
453
+ fi
454
+
455
+ # ─── 6. Extension socket liveness ──────────────────────────────────────────
456
+ check "Extension socket .claws/claws.sock"
457
+ SOCK="$PROJECT_ROOT/.claws/claws.sock"
458
+ if [ -S "$SOCK" ]; then
459
+ if command -v nc &>/dev/null && echo '{"id":1,"cmd":"list"}' | nc -U "$SOCK" 2>/dev/null | head -c 200 | grep -q '"ok"'; then
460
+ ok "socket is LIVE — extension listening"
461
+ # FINDING-C-13: probe wrapped terminals for missing logPath (pipe-mode / script(1) failure)
462
+ LIST_OUT=$(echo '{"id":2,"cmd":"list"}' | nc -U "$SOCK" 2>/dev/null | head -c 4096)
463
+ if echo "$LIST_OUT" | node --no-deprecation -e "
464
+ const lines = require('fs').readFileSync('/dev/stdin','utf8').split('\n');
465
+ let broken = 0;
466
+ for (const l of lines) {
467
+ try {
468
+ const j = JSON.parse(l);
469
+ if (j.ok && Array.isArray(j.terminals)) {
470
+ for (const t of j.terminals) {
471
+ if (t.wrapped && !t.logPath) {
472
+ process.stderr.write(' WRAPPED_NO_LOG: terminal ' + t.id + ' (' + (t.name||'?') + ')\n');
473
+ broken++;
474
+ }
475
+ }
476
+ }
477
+ } catch {}
478
+ }
479
+ if (broken > 0) { process.stdout.write(broken + ''); process.exit(1); }
480
+ process.exit(0);
481
+ " 2>/dev/null; then
482
+ :
483
+ else
484
+ BAD_COUNT=$(echo "$LIST_OUT" | node --no-deprecation -e "
485
+ const ls=require('fs').readFileSync('/dev/stdin','utf8').split('\n');
486
+ let n=0;
487
+ for(const l of ls){try{const j=JSON.parse(l);if(j.ok&&Array.isArray(j.terminals)){for(const t of j.terminals){if(t.wrapped&&!t.logPath)n++;}}}catch{}}
488
+ process.stdout.write(n+'');
489
+ " 2>/dev/null || echo "?")
490
+ fail "$BAD_COUNT wrapped terminal(s) have wrapped=true but no logPath — script(1) may have failed"
491
+ # script(1) availability check (Linux only; macOS always has it)
492
+ if [ "$(uname -s)" = "Linux" ] && ! command -v script &>/dev/null; then
493
+ fix "script(1) not found on Linux — install bsdutils or util-linux: sudo apt-get install bsdutils"
494
+ fi
495
+ ISSUES=$((ISSUES+1))
496
+ fi
497
+ else
498
+ fix "socket is stale — VS Code needs to reload"
499
+ rm -f "$SOCK" 2>/dev/null || true
500
+ fail "after reload the extension will re-create the socket"
501
+ ISSUES=$((ISSUES+1))
502
+ fi
503
+ else
504
+ fail "no socket at $SOCK"
505
+ fix "this is normal if VS Code isn't running this project — reload a VS Code window on this folder"
506
+ ISSUES=$((ISSUES+1))
507
+ fi
508
+
509
+ # ─── 6b. Shell hook sourcing in rc files (FINDING-C-11) ─────────────────────
510
+ check "Shell hook sourcing in rc files (.zshrc / .bashrc)"
511
+ HOOK_SCRIPT="$INSTALL_DIR/scripts/shell-hook.sh"
512
+ RC_SOURCED=0
513
+ RC_CHECKED=0
514
+ for RC in "$HOME/.zshrc" "$HOME/.bashrc"; do
515
+ [ -f "$RC" ] || continue
516
+ RC_CHECKED=$((RC_CHECKED+1))
517
+ if grep -q 'shell-hook.sh' "$RC" 2>/dev/null; then
518
+ ok "shell-hook.sh sourced in $(basename "$RC")"
519
+ RC_SOURCED=$((RC_SOURCED+1))
520
+ fi
521
+ done
522
+ if [ "$RC_SOURCED" -eq 0 ] && [ -f "$HOOK_SCRIPT" ]; then
523
+ TARGET_RC="$HOME/.zshrc"
524
+ [ -f "$HOME/.bashrc" ] && TARGET_RC="$HOME/.bashrc"
525
+ [ -f "$HOME/.zshrc" ] && TARGET_RC="$HOME/.zshrc"
526
+ fix "shell-hook.sh not sourced in any rc file — appending to $(basename "$TARGET_RC")"
527
+ printf '\n# Claws shell functions (claws-ls, claws-new, claws-run, claws-log)\n[ -f "%s" ] && source "%s"\n' \
528
+ "$HOOK_SCRIPT" "$HOOK_SCRIPT" >> "$TARGET_RC"
529
+ ok "appended source line to $(basename "$TARGET_RC") — open a new terminal to activate"
530
+ FIXED=$((FIXED+1))
531
+ elif [ "$RC_CHECKED" -eq 0 ]; then
532
+ fix "no .zshrc or .bashrc found — skipping shell-hook sourcing check"
533
+ fi
534
+
535
+ # ─── 7. Stale global MCP registration in ~/.claude/settings.json ───────────
536
+ check "Global ~/.claude/settings.json (informational)"
537
+ if [ -f "$HOME/.claude/settings.json" ] && grep -q '"claws"' "$HOME/.claude/settings.json" 2>/dev/null; then
538
+ if [ -f "$PROJECT_MCP" ]; then
539
+ fix "global claws entry exists alongside project .mcp.json — project takes precedence (safe to remove global if you want)"
540
+ else
541
+ ok "global claws registration active"
542
+ fi
543
+ fi
544
+
545
+ # ─── 7b. ~/.claude/settings.json JSON validity (FINDING-C-7, P0) ─────────────
546
+ # A malformed settings.json causes EVERY Claude Code hook (SessionStart,
547
+ # PreToolUse, Stop) to fail silently on every tool call. Detect and repair
548
+ # before the stale-paths check below, so check 8 can assume valid JSON.
549
+ check "~/.claude/settings.json is valid JSON"
550
+ if [ -f "$HOME/.claude/settings.json" ]; then
551
+ if ! node --no-deprecation -e "
552
+ const fs=require('fs');
553
+ JSON.parse(fs.readFileSync(process.argv[1],'utf8'));
554
+ " "$HOME/.claude/settings.json" 2>/dev/null; then
555
+ fail "settings.json is malformed JSON — ALL claude hooks will fail silently"
556
+ fix "backing up and re-injecting Claws hooks via inject-settings-hooks.js"
557
+ cp "$HOME/.claude/settings.json" "$HOME/.claude/settings.json.bak.$(date +%s)" 2>/dev/null || true
558
+ if node --no-deprecation "$INSTALL_DIR/scripts/inject-settings-hooks.js" --remove >/dev/null 2>&1 \
559
+ && node --no-deprecation "$INSTALL_DIR/scripts/inject-settings-hooks.js" "$INSTALL_DIR/scripts" >/dev/null 2>&1; then
560
+ ok "hooks re-written into repaired settings.json"
561
+ FIXED=$((FIXED+1))
562
+ else
563
+ fail "automatic repair failed — manually edit $HOME/.claude/settings.json (backup saved)"
564
+ ISSUES=$((ISSUES+1))
565
+ fi
566
+ else
567
+ ok "settings.json is valid JSON"
568
+ fi
569
+ fi
570
+
571
+ # ─── 8. Stale Claws hook script paths in ~/.claude/settings.json (v0.7.3) ──
572
+ # Detects the "SessionStart:startup hook error / non-blocking status code"
573
+ # class of failure: registered hook commands point at a path that no longer
574
+ # exists (install dir moved, sandbox path leaked into settings, prior
575
+ # install was deleted). Re-registers from the current INSTALL_DIR so
576
+ # Claude Code stops reporting hook errors on every tool call.
577
+ check "Hook script paths in ~/.claude/settings.json"
578
+ if [ -f "$HOME/.claude/settings.json" ] && grep -q '_source.*claws' "$HOME/.claude/settings.json" 2>/dev/null; then
579
+ STALE_HOOKS=$(node --no-deprecation -e "
580
+ const fs=require('fs');
581
+ const j=JSON.parse(fs.readFileSync('$HOME/.claude/settings.json','utf8'));
582
+ const stale=[];
583
+ for(const ev of Object.keys(j.hooks||{})){
584
+ for(const e of (j.hooks[ev]||[])){
585
+ if(e._source!=='claws')continue;
586
+ if(!e.hooks||!e.hooks[0])continue;
587
+ const cmd=e.hooks[0].command||'';
588
+ // Extract the .js path from either: plain 'node \"<path>\"' OR
589
+ // wrapped 'sh -c \"...\" \"<path>\"' (path is the LAST quoted token).
590
+ const matches=[...cmd.matchAll(/\"([^\"]+\\.js)\"/g)];
591
+ if(!matches.length)continue;
592
+ const scriptPath=matches[matches.length-1][1];
593
+ if(!fs.existsSync(scriptPath)) stale.push({event:ev,path:scriptPath});
594
+ }
595
+ }
596
+ if(stale.length===0){console.log('OK')}
597
+ else for(const s of stale)console.log(s.event+'\t'+s.path);
598
+ " 2>/dev/null)
599
+ if [ "$STALE_HOOKS" = "OK" ]; then
600
+ ok "all registered Claws hook paths resolve"
601
+ else
602
+ fail "stale hook path(s) detected:"
603
+ echo "$STALE_HOOKS" | sed 's/^/ /'
604
+ fix "re-registering hooks against $INSTALL_DIR/scripts/hooks/"
605
+ if node --no-deprecation "$INSTALL_DIR/scripts/inject-settings-hooks.js" --remove >/dev/null 2>&1 \
606
+ && node --no-deprecation "$INSTALL_DIR/scripts/inject-settings-hooks.js" "$INSTALL_DIR/scripts" >/dev/null 2>&1; then
607
+ ok "hooks re-registered from $INSTALL_DIR/scripts/hooks/"
608
+ FIXED=$((FIXED+1))
609
+ else
610
+ fail "could not re-register hooks — check $INSTALL_DIR/scripts/inject-settings-hooks.js exists"
611
+ ISSUES=$((ISSUES+1))
612
+ fi
613
+ fi
614
+ fi
615
+
616
+ # ─── 8b. PostToolUse spawn-class hooks registered (Wave C) ──────────────────
617
+ # Detect the case where settings.json has valid Claws hooks but no PostToolUse
618
+ # entries — typical after upgrading from pre-Wave C install. Check 8 only detects
619
+ # stale paths; it cannot detect entirely absent entries.
620
+ check "PostToolUse spawn-class hooks registered (Wave C monitor race-close)"
621
+ if [ -f "$HOME/.claude/settings.json" ]; then
622
+ if CLAWS_SETTINGS_CHECK="$HOME/.claude/settings.json" node --no-deprecation -e "
623
+ const s = JSON.parse(require('fs').readFileSync(process.env.CLAWS_SETTINGS_CHECK, 'utf8'));
624
+ const h = (s.hooks && s.hooks.PostToolUse) || [];
625
+ process.exit(h.some(e => e.matcher && e.matcher.includes('mcp__claws__claws_worker')) ? 0 : 1);
626
+ " 2>/dev/null; then
627
+ ok "PostToolUse spawn-class hooks registered"
628
+ else
629
+ fail "PostToolUse spawn-class hooks missing — Wave C monitor race-close inactive"
630
+ fix "re-registering all Claws hooks via inject-settings-hooks.js --update"
631
+ if node --no-deprecation "$INSTALL_DIR/scripts/inject-settings-hooks.js" "$INSTALL_DIR/scripts" --update 2>/dev/null; then
632
+ ok "hooks re-registered (PostToolUse entries added)"
633
+ FIXED=$((FIXED+1))
634
+ else
635
+ fail "could not re-register hooks — check $INSTALL_DIR/scripts/inject-settings-hooks.js"
636
+ ISSUES=$((ISSUES+1))
637
+ fi
638
+ fi
639
+ fi
640
+
641
+ # ─── 9. Hook script execution probe (v0.7.3) ──────────────────────────────
642
+ # Defense in depth: even if the path resolves, the script might crash on
643
+ # load (ESM vs CJS, missing deps, etc). Invoke each hook with synthetic
644
+ # stdin and confirm exit 0. Hooks were hardened in v0.7.3 to never crash,
645
+ # but if a user is running pre-v0.7.3 hook scripts, we surface the gap.
646
+ check "Hook scripts execute cleanly"
647
+ HOOK_DIR="$INSTALL_DIR/scripts/hooks"
648
+ HOOKS_PRESENT=0
649
+ HOOKS_OK=0
650
+ HOOKS_FAIL=0
651
+ HOOKS_FAILED_NAMES=""
652
+ for hook in session-start-claws.js pre-tool-use-claws.js post-tool-use-claws.js stop-claws.js; do
653
+ [ -f "$HOOK_DIR/$hook" ] || continue
654
+ HOOKS_PRESENT=$((HOOKS_PRESENT+1))
655
+ # Use a Node-based 5s ceiling instead of `timeout` (not on macOS by default).
656
+ # The hook should exit in <100ms anyway; this just guards against pathological
657
+ # hangs in pre-v0.7.3 hook scripts.
658
+ if echo '{"cwd":"/tmp","tool_name":"Bash","tool_input":{"command":"ls"}}' \
659
+ | node --no-deprecation -e "
660
+ const { spawn } = require('child_process');
661
+ let buf = '';
662
+ process.stdin.on('data', d => buf += d);
663
+ process.stdin.on('end', () => {
664
+ const ch = spawn('node', ['$HOOK_DIR/$hook'], { stdio: ['pipe','pipe','pipe'] });
665
+ const t = setTimeout(() => { try { ch.kill('SIGKILL'); } catch {} process.exit(124); }, 5000);
666
+ ch.stdin.write(buf); ch.stdin.end();
667
+ ch.on('exit', c => { clearTimeout(t); process.exit(c||0); });
668
+ ch.on('error', () => { clearTimeout(t); process.exit(127); });
669
+ });
670
+ " >/dev/null 2>&1; then
671
+ HOOKS_OK=$((HOOKS_OK+1))
672
+ else
673
+ HOOKS_FAIL=$((HOOKS_FAIL+1))
674
+ HOOKS_FAILED_NAMES="$HOOKS_FAILED_NAMES $hook"
675
+ fi
676
+ done
677
+ if [ "$HOOKS_PRESENT" -eq 0 ]; then
678
+ fail "no hook scripts found at $HOOK_DIR — run install.sh"
679
+ ISSUES=$((ISSUES+1))
680
+ elif [ "$HOOKS_FAIL" -eq 0 ]; then
681
+ ok "all $HOOKS_OK hook script(s) probe clean"
682
+ else
683
+ fail "$HOOKS_FAIL of $HOOKS_PRESENT hook(s) exited non-zero on probe:$HOOKS_FAILED_NAMES"
684
+ fix "if you have an older Claws version, run install.sh to refresh hooks (v0.7.3+ hardening)"
685
+ ISSUES=$((ISSUES+1))
686
+ fi
687
+
688
+ # ─── Summary ──────────────────────────────────────────────────────────────
689
+ echo ""
690
+ if [ "$ISSUES" -eq 0 ]; then
691
+ printf "${C_GREEN}${C_BOLD}All checks passed${C_RESET} — ${FIXED} auto-repairs applied.\n"
692
+ else
693
+ printf "${C_YELLOW}${C_BOLD}${FIXED} fixed, ${ISSUES} still open.${C_RESET}\n"
694
+ fi
695
+ echo ""
696
+ printf "${C_BOLD}Activate changes:${C_RESET}\n"
697
+ echo " 1. Reload VS Code: Cmd+Shift+P → Developer: Reload Window"
698
+ echo " 2. Restart Claude Code: exit this session and re-open 'claude' in this project"
699
+ echo ""
700
+ printf "${C_BOLD}Still broken after that?${C_RESET}\n"
701
+ echo " Run /claws-report to bundle logs + state for a support request."
702
+ echo ""