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
@@ -0,0 +1,337 @@
1
+ #!/usr/bin/env bash
2
+ # Claws — update runner
3
+ #
4
+ # Self-contained. Works two ways:
5
+ #
6
+ # # 1. Via curl URL (no prior clone needed if ~/.claws-src is already there):
7
+ # bash <(curl -fsSL https://raw.githubusercontent.com/neunaha/claws/main/scripts/update.sh)
8
+ #
9
+ # # 2. From the local clone:
10
+ # bash ~/.claws-src/scripts/update.sh
11
+ #
12
+ # The /claws-update slash command is a thin wrapper around option 2.
13
+ #
14
+ # New update steps get added to this script and users pick them up on their
15
+ # next update — no per-project re-install of the slash-command markdown.
16
+ #
17
+ # Usage: bash update.sh [project-root] (defaults to current pwd)
18
+
19
+ set -eo pipefail
20
+
21
+ # FINDING-B-5: --dry-run flag — show what would change without applying it.
22
+ DRY_RUN=0
23
+ _update_args=()
24
+ for _arg in "$@"; do
25
+ case "$_arg" in
26
+ --dry-run) DRY_RUN=1 ;;
27
+ *) _update_args+=("$_arg") ;;
28
+ esac
29
+ done
30
+ set -- "${_update_args[@]+"${_update_args[@]}"}"
31
+ unset _update_args _arg
32
+
33
+ INSTALL_DIR="${CLAWS_DIR:-$HOME/.claws-src}"
34
+ # PROJECT_PWD is set by the slash command; fall back to $1, then $PWD.
35
+ PROJECT_ROOT="${1:-${PROJECT_PWD:-$(pwd)}}"
36
+
37
+ # ─── Colors ────────────────────────────────────────────────────────────────
38
+ if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
39
+ C_RESET='\033[0m'; C_BOLD='\033[1m'
40
+ C_GREEN='\033[0;32m'; C_YELLOW='\033[0;33m'; C_DIM='\033[2m'
41
+ else
42
+ C_RESET=''; C_BOLD=''; C_GREEN=''; C_YELLOW=''; C_DIM=''
43
+ fi
44
+
45
+ header() { printf "\n${C_BOLD}═════ %s ═════${C_RESET}\n" "$*"; }
46
+ note() { printf " ${C_DIM}%s${C_RESET}\n" "$*"; }
47
+
48
+ # M-19: Anchor CLAWS_LOG BEFORE install.sh runs so Step 6's warning
49
+ # "see install log: $CLAWS_LOG" references the real log written by install.sh.
50
+ # install.sh uses ${CLAWS_LOG:-...} so it inherits this value when exported.
51
+ CLAWS_LOG="${CLAWS_LOG:-/tmp/claws-install-$(date +%Y%m%d-%H%M%S)-$$.log}"
52
+ export CLAWS_LOG
53
+
54
+ # ─── Step 0: Ensure Claws source + pull latest ─────────────────────────────
55
+ # Runs whether update.sh was invoked via curl URL or from the local clone —
56
+ # this is the one action that can't be delegated to install.sh because
57
+ # install.sh's logic assumes INSTALL_DIR is already up-to-date.
58
+ if [ ! -d "$INSTALL_DIR/.git" ]; then
59
+ if [ -d "$INSTALL_DIR" ]; then
60
+ echo "Claws source at $INSTALL_DIR exists but is not a git clone." >&2
61
+ echo "Remove it or set CLAWS_DIR to a different path, then re-run." >&2
62
+ exit 1
63
+ fi
64
+ echo "Claws not yet installed — running the installer first."
65
+ bash <(curl -fsSL https://raw.githubusercontent.com/neunaha/claws/main/scripts/install.sh)
66
+ exit 0
67
+ fi
68
+
69
+ header "Pulling latest Claws source"
70
+ # M-21: track whether git pull succeeded so install.sh can skip stale-source
71
+ # CLAUDE.md re-injection. GIT_PULL_OK=0 exported on failure → install.sh gates on it.
72
+ GIT_PULL_OK=1
73
+ _claws_prev_sha=$(cd "$INSTALL_DIR" && git rev-parse HEAD 2>/dev/null || echo "unknown")
74
+ if ( cd "$INSTALL_DIR" && git pull --ff-only --quiet origin main 2>/tmp/claws-pull-err.$$ ); then
75
+ _claws_new_sha=$(cd "$INSTALL_DIR" && git rev-parse HEAD 2>/dev/null || echo "unknown")
76
+ if [ "$_claws_prev_sha" = "$_claws_new_sha" ]; then
77
+ note "already up-to-date ($(cd "$INSTALL_DIR" && git log --oneline -1))"
78
+ else
79
+ note "git pull OK (${_claws_prev_sha:0:7} → ${_claws_new_sha:0:7})"
80
+ fi
81
+ rm -f /tmp/claws-pull-err.$$
82
+ else
83
+ GIT_PULL_OK=0
84
+ printf " ${C_YELLOW}!${C_RESET} git pull failed — continuing with existing tree\n"
85
+ if [ -s /tmp/claws-pull-err.$$ ]; then
86
+ sed 's/^/ /' /tmp/claws-pull-err.$$
87
+ fi
88
+ printf " ${C_YELLOW}!${C_RESET} You will be installing from the LOCAL clone (last commit: $(cd "$INSTALL_DIR" && git log --oneline -1 2>/dev/null))\n"
89
+ printf " ${C_YELLOW}!${C_RESET} CLAUDE.md re-injection skipped — stale source would overwrite tool set (M-21)\n"
90
+ rm -f /tmp/claws-pull-err.$$
91
+ fi
92
+ export GIT_PULL_OK
93
+ unset _claws_prev_sha _claws_new_sha
94
+
95
+ # ─── Step 1: Sync marketplace-facing docs ──────────────────────────────────
96
+ # The extension's README and CHANGELOG mirror the repo root so the VSIX (and
97
+ # the installed extension folder) stay current.
98
+ header "Syncing extension docs"
99
+ cp "$INSTALL_DIR/README.md" "$INSTALL_DIR/extension/README.md" 2>/dev/null && note "README.md → extension/" || true
100
+ cp "$INSTALL_DIR/CHANGELOG.md" "$INSTALL_DIR/extension/CHANGELOG.md" 2>/dev/null && note "CHANGELOG.md → extension/" || true
101
+
102
+ # ─── Step 2: Run the installer against the user's project ──────────────────
103
+ # install.sh is the single source of truth for everything the update does:
104
+ # extension build + symlink, project-local files, CLAUDE.md migration +
105
+ # injection, shell hook, verification, banner, install log. Anything new we
106
+ # want the update flow to do — add it to install.sh, not here.
107
+ header "Running installer"
108
+ if [ "$DRY_RUN" = "1" ]; then
109
+ note "--dry-run: skipping installer. Pending git diff from $INSTALL_DIR:"
110
+ ( cd "$INSTALL_DIR" && git log --oneline origin/main..HEAD 2>/dev/null || true )
111
+ ( cd "$INSTALL_DIR" && git diff --stat origin/main 2>/dev/null || true )
112
+ else
113
+ ( cd "$PROJECT_ROOT" && bash "$INSTALL_DIR/scripts/install.sh" )
114
+ fi
115
+
116
+ # ─── Step 3: Print the newest CHANGELOG entry ──────────────────────────────
117
+ header "What's new"
118
+ if [ -f "$INSTALL_DIR/CHANGELOG.md" ]; then
119
+ # Extract exactly the most-recent `## [x.y.z]` section.
120
+ awk '/^## \[/{c++} c==1 && NR>1{print} c==2{exit}' "$INSTALL_DIR/CHANGELOG.md" | head -80
121
+ echo ""
122
+ note "Full CHANGELOG: $INSTALL_DIR/CHANGELOG.md"
123
+ fi
124
+
125
+ # ─── Step 4: Post-update hooks ─────────────────────────────────────────────
126
+ # Any future one-off migrations (cleaning up stale sockets, retiring old
127
+ # config keys, notifying of breaking changes) go here. On first run each
128
+ # migration can write a marker file so it never runs twice.
129
+ #
130
+ # M-26: Socket probe is health-check only. The previous version deleted the
131
+ # socket when the probe failed, which races with VS Code's extension hot-reload
132
+ # after a VSIX install — the extension may be momentarily unreachable while
133
+ # reactivating, so a failed probe does NOT mean the socket is stale.
134
+ # Destructive cleanup is deferred to /claws-fix (user-explicit step).
135
+ if [ -S "$PROJECT_ROOT/.claws/claws.sock" ]; then
136
+ _claws_sock="$PROJECT_ROOT/.claws/claws.sock"
137
+ _claws_alive=0
138
+ if command -v node >/dev/null 2>&1; then
139
+ # M-20: pass path via env var — handles project roots with apostrophes/backslashes
140
+ # without causing JS syntax errors from string interpolation in -e argument.
141
+ if CLAWS_PROBE_PATH="$_claws_sock" node --no-deprecation -e "
142
+ const net = require('net');
143
+ const s = net.createConnection(process.env.CLAWS_PROBE_PATH);
144
+ const t = setTimeout(() => { try { s.destroy(); } catch {} process.exit(1); }, 800);
145
+ s.on('connect', () => { s.write('{\"id\":1,\"cmd\":\"list\"}\n'); });
146
+ s.on('data', () => { clearTimeout(t); try { s.destroy(); } catch {} process.exit(0); });
147
+ s.on('error', () => { clearTimeout(t); process.exit(1); });
148
+ " 2>/dev/null; then
149
+ _claws_alive=1
150
+ fi
151
+ fi
152
+ if [ "$_claws_alive" = "1" ]; then
153
+ note "claws.sock is live (extension responding)"
154
+ else
155
+ note "claws.sock probe returned no response (extension may be reloading after VSIX install)"
156
+ note "If 'claws_*' tools fail in your next Claude Code session, run /claws-fix to repair"
157
+ # Do NOT delete the socket here — defer to user-explicit /claws-fix (M-26)
158
+ fi
159
+ unset _claws_sock _claws_alive
160
+ fi
161
+
162
+ # ─── Step 5: Re-source the shell hook ──────────────────────────────────────
163
+ # Forces the in-terminal CLAWS banner + aliases to refresh without a new
164
+ # shell. Best-effort — won't fail the update if the hook is missing.
165
+ header "Refreshing shell hook"
166
+ unset CLAWS_BANNER_SHOWN 2>/dev/null || true
167
+ # shellcheck disable=SC1090
168
+ source "$INSTALL_DIR/scripts/shell-hook.sh" 2>/dev/null \
169
+ && note "shell hook re-sourced" \
170
+ || note "shell hook not sourced (non-interactive shell)"
171
+
172
+ # ─── Step 6: Post-update health check (v0.7.3) ────────────────────────────
173
+ # Runs the same diagnostic checks /claws-fix would. If any fail, surface
174
+ # them BEFORE the success banner so the user knows recovery steps to take.
175
+ header "Post-update health check"
176
+ _claws_health_ok=1
177
+ _claws_health_warns=()
178
+
179
+ # pty.node ABI parity — the most common silent breakage after an update.
180
+ if [ -f "$INSTALL_DIR/extension/native/.metadata.json" ]; then
181
+ _claws_built_for=$(node -e "try{console.log(require('$INSTALL_DIR/extension/native/.metadata.json').electronVersion||'')}catch(e){}" 2>/dev/null || echo "")
182
+ _claws_using=""
183
+ if [ "$(uname)" = "Darwin" ]; then
184
+ # M-35: prefer the editor that launched this shell ($TERM_PROGRAM) so the
185
+ # user's daily-driver Electron version is checked first, not first-found.
186
+ _claws_u_tp="${TERM_PROGRAM:-}"
187
+ [ "$_claws_u_tp" = "vscode" ] && [ -n "${CURSOR_CHANNEL:-}" ] && _claws_u_tp="cursor"
188
+ _claws_u_tp=$(echo "$_claws_u_tp" | tr '[:upper:]' '[:lower:]')
189
+ case "$_claws_u_tp" in
190
+ cursor) _claws_update_apps=('/Applications/Cursor.app' '/Applications/Visual Studio Code.app' '/Applications/Windsurf.app') ;;
191
+ windsurf) _claws_update_apps=('/Applications/Windsurf.app' '/Applications/Visual Studio Code.app' '/Applications/Cursor.app') ;;
192
+ *) _claws_update_apps=('/Applications/Visual Studio Code.app' '/Applications/Cursor.app' '/Applications/Windsurf.app') ;;
193
+ esac
194
+ for _claws_app in "${_claws_update_apps[@]}"; do
195
+ _claws_plist="$_claws_app/Contents/Frameworks/Electron Framework.framework/Resources/Info.plist"
196
+ [ -f "$_claws_plist" ] && _claws_using=$(plutil -extract CFBundleVersion raw "$_claws_plist" 2>/dev/null || true) && break
197
+ done
198
+ unset _claws_u_tp _claws_update_apps
199
+ elif [ "$(uname)" = "Linux" ]; then
200
+ # F-5: port Linux Electron detection from install.sh so ABI mismatch is caught on Linux too.
201
+ _claws_u_tp="${TERM_PROGRAM:-}"
202
+ [ "$_claws_u_tp" = "vscode" ] && [ -n "${CURSOR_CHANNEL:-}" ] && _claws_u_tp="cursor"
203
+ _claws_u_tp=$(echo "$_claws_u_tp" | tr '[:upper:]' '[:lower:]')
204
+ case "$_claws_u_tp" in
205
+ cursor)
206
+ _claws_linux_eps="/usr/share/cursor/electron /opt/cursor/electron /snap/cursor/current/usr/share/cursor/electron /usr/share/code/electron /usr/lib/code/electron /opt/visual-studio-code/electron /snap/code/current/usr/share/code/electron /usr/share/windsurf/electron /opt/windsurf/electron"
207
+ ;;
208
+ windsurf)
209
+ _claws_linux_eps="/usr/share/windsurf/electron /opt/windsurf/electron /usr/share/code/electron /usr/lib/code/electron /opt/visual-studio-code/electron /snap/code/current/usr/share/code/electron /usr/share/cursor/electron /opt/cursor/electron"
210
+ ;;
211
+ *)
212
+ _claws_linux_eps="/usr/share/code/electron /usr/lib/code/electron /opt/visual-studio-code/electron /snap/code/current/usr/share/code/electron /usr/share/cursor/electron /opt/cursor/electron /snap/cursor/current/usr/share/cursor/electron /usr/share/windsurf/electron /opt/windsurf/electron"
213
+ ;;
214
+ esac
215
+ for _claws_ep in $_claws_linux_eps; do
216
+ if [ -x "$_claws_ep" ]; then
217
+ _claws_using=$("$_claws_ep" --version 2>/dev/null | sed 's/^v//' | head -1)
218
+ [ -n "$_claws_using" ] && break
219
+ fi
220
+ done
221
+ unset _claws_u_tp _claws_linux_eps _claws_ep
222
+ fi
223
+ if [ -n "$_claws_using" ] && [ -n "$_claws_built_for" ] && [ "$_claws_using" != "$_claws_built_for" ]; then
224
+ _claws_health_ok=0
225
+ _claws_health_warns+=("pty.node was built for Electron $_claws_built_for but VS Code is running $_claws_using — wrapped terminals will fall into pipe-mode")
226
+ _claws_health_warns+=(" fix: CLAWS_FORCE_REBUILD_NPTY=1 bash $INSTALL_DIR/scripts/install.sh")
227
+ else
228
+ note "pty.node ABI matches editor Electron ($_claws_using)"
229
+ fi
230
+ unset _claws_built_for _claws_using _claws_app _claws_plist
231
+ fi
232
+
233
+ # BUG-28: MCP spawn-class PreToolUse hooks — Monitor arm gate
234
+ if [ -f "$HOME/.claude/settings.json" ]; then
235
+ if CLAWS_SETTINGS_CHECK="$HOME/.claude/settings.json" node --no-deprecation -e "
236
+ const s = JSON.parse(require('fs').readFileSync(process.env.CLAWS_SETTINGS_CHECK, 'utf8'));
237
+ const h = (s.hooks && s.hooks.PreToolUse) || [];
238
+ process.exit(h.some(e => e.matcher && e.matcher.includes('mcp__claws__claws_worker')) ? 0 : 1);
239
+ " 2>/dev/null; then
240
+ note "MCP spawn-class PreToolUse hooks registered (Monitor arm gate active)"
241
+ else
242
+ _claws_health_ok=0
243
+ _claws_health_warns+=("MCP spawn-class PreToolUse hooks missing — Monitor arm gate is inactive")
244
+ _claws_health_warns+=(" fix: node $INSTALL_DIR/scripts/inject-settings-hooks.js $INSTALL_DIR/scripts --update")
245
+ fi
246
+ fi
247
+
248
+ # Wave C: PostToolUse spawn-class hooks — monitor race-close
249
+ if [ -f "$HOME/.claude/settings.json" ]; then
250
+ if CLAWS_SETTINGS_CHECK="$HOME/.claude/settings.json" node --no-deprecation -e "
251
+ const s = JSON.parse(require('fs').readFileSync(process.env.CLAWS_SETTINGS_CHECK, 'utf8'));
252
+ const h = (s.hooks && s.hooks.PostToolUse) || [];
253
+ process.exit(h.some(e => e.matcher && e.matcher.includes('mcp__claws__claws_worker')) ? 0 : 1);
254
+ " 2>/dev/null; then
255
+ note "PostToolUse spawn-class hooks registered (Wave C monitor race-close active)"
256
+ else
257
+ _claws_health_ok=0
258
+ _claws_health_warns+=("PostToolUse spawn-class hooks missing — Wave C monitor race-close is inactive")
259
+ _claws_health_warns+=(" fix: node $INSTALL_DIR/scripts/inject-settings-hooks.js $INSTALL_DIR/scripts --update")
260
+ fi
261
+ fi
262
+
263
+ # Project .mcp.json sanity
264
+ # M-47: path passed via env var — handles project roots with apostrophes/backslashes
265
+ # without causing JS syntax errors from string interpolation in -e argument.
266
+ if [ -f "$PROJECT_ROOT/.mcp.json" ]; then
267
+ if CLAWS_MCP_CHECK="$PROJECT_ROOT/.mcp.json" node -e "JSON.parse(require('fs').readFileSync(process.env.CLAWS_MCP_CHECK,'utf8'))" 2>/dev/null; then
268
+ note "project .mcp.json is valid JSON"
269
+ else
270
+ _claws_health_ok=0
271
+ _claws_health_warns+=("$PROJECT_ROOT/.mcp.json is not valid JSON — MCP server will not load")
272
+ _claws_health_warns+=(" fix: review and repair the file, or rm and re-run install.sh")
273
+ fi
274
+ fi
275
+
276
+ # Fix #1 (v0.7.12): ensure .claws-bin/package.json exists with {"type":"commonjs"}.
277
+ # Auto-restores on every update so existing installs without it get fixed automatically.
278
+ if [ -d "$PROJECT_ROOT/.claws-bin" ] && [ ! -f "$PROJECT_ROOT/.claws-bin/package.json" ]; then
279
+ 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' > "$PROJECT_ROOT/.claws-bin/package.json"
280
+ note "wrote .claws-bin/package.json (ESM compat shim)"
281
+ fi
282
+
283
+ # .claws-bin/mcp_server.js exists and starts
284
+ # M-10: up to 3 attempts with exponential timeouts (8s, 12s, 16s); only YELLOW after all exhausted.
285
+ if [ -f "$PROJECT_ROOT/.claws-bin/mcp_server.js" ]; then
286
+ _claws_mcp_ok=0
287
+ _claws_attempt=0
288
+ for _claws_mcp_ms in 8000 12000 16000; do
289
+ [ "$_claws_mcp_ok" = "1" ] && break
290
+ _claws_attempt=$(( _claws_attempt + 1 ))
291
+ [ "$_claws_attempt" -gt 1 ] && note "MCP handshake timeout — retry $_claws_attempt of 3 (${_claws_mcp_ms}ms)..."
292
+ if CLAWS_MCP_PATH="$PROJECT_ROOT/.claws-bin/mcp_server.js" node --no-deprecation -e "
293
+ const { spawn } = require('child_process');
294
+ const p = spawn('node', [process.env.CLAWS_MCP_PATH], { stdio: ['pipe','pipe','ignore'] });
295
+ let out='';
296
+ p.stdout.on('data', d => { out += d; });
297
+ p.stdin.write(JSON.stringify({jsonrpc:'2.0',id:1,method:'initialize',params:{protocolVersion:'2024-11-05',capabilities:{},clientInfo:{name:'health',version:'1'}}}) + '\n');
298
+ setTimeout(() => {
299
+ p.kill('SIGTERM');
300
+ setTimeout(() => { try { p.kill('SIGKILL'); } catch {} }, 500);
301
+ setTimeout(() => { process.exit(out.includes('claws') ? 0 : 1); }, 600);
302
+ }, $_claws_mcp_ms);
303
+ " 2>/dev/null; then
304
+ _claws_mcp_ok=1
305
+ fi
306
+ done
307
+ unset _claws_mcp_ms _claws_attempt
308
+ if [ "$_claws_mcp_ok" = "1" ]; then
309
+ note "MCP server handshake OK"
310
+ else
311
+ _claws_health_ok=0
312
+ _claws_health_warns+=("MCP server failed to respond to initialize — see install log: $CLAWS_LOG")
313
+ _claws_health_warns+=(" fix: bash $INSTALL_DIR/scripts/fix.sh")
314
+ fi
315
+ unset _claws_mcp_ok
316
+ fi
317
+
318
+ # ─── Done ──────────────────────────────────────────────────────────────────
319
+ echo ""
320
+ if [ "$_claws_health_ok" = "1" ]; then
321
+ printf "${C_GREEN}${C_BOLD}Update complete.${C_RESET}\n"
322
+ else
323
+ printf "${C_YELLOW}${C_BOLD}Update completed WITH WARNINGS:${C_RESET}\n"
324
+ for _w in "${_claws_health_warns[@]}"; do
325
+ printf " ${C_YELLOW}!${C_RESET} %s\n" "$_w"
326
+ done
327
+ fi
328
+ echo ""
329
+ printf " ${C_BOLD}Two things to activate:${C_RESET}\n"
330
+ echo " 1. Reload VS Code: Cmd+Shift+P → Developer: Reload Window"
331
+ echo " 2. Restart Claude Code in this project so the new .mcp.json is picked up"
332
+ echo ""
333
+ printf " ${C_BOLD}If anything looks off:${C_RESET}\n"
334
+ echo " /claws-fix — quick auto-diagnosis + auto-repair"
335
+ echo " /claws-report — bundle logs + state into a shareable file"
336
+ echo ""
337
+ unset _claws_health_ok _claws_health_warns _w
@@ -0,0 +1,323 @@
1
+ #!/usr/bin/env bash
2
+ # Run from repo root: bash scripts/verify-release.sh
3
+ # Windows users: run via Git Bash or WSL
4
+
5
+ set -uo pipefail
6
+
7
+ # ── State ──────────────────────────────────────────────────────────────────────
8
+ PASS=0
9
+ FAIL=0
10
+ FAILURES=()
11
+ ROOT_VERSION=""
12
+
13
+ # ── Helpers ────────────────────────────────────────────────────────────────────
14
+ pass() {
15
+ printf '✓ %s\n' "$1"
16
+ PASS=$((PASS + 1))
17
+ }
18
+
19
+ fail() {
20
+ local name="$1" reason="$2"
21
+ printf '✗ %s: %s\n' "$name" "$reason"
22
+ FAIL=$((FAIL + 1))
23
+ FAILURES+=("✗ ${name}: ${reason}")
24
+ }
25
+
26
+ # ── 1) Working tree clean (allow .local/) ──────────────────────────────────────
27
+ check_clean_tree() {
28
+ local dirty
29
+ dirty=$(git status --porcelain 2>/dev/null | grep -v '\.local/' || true)
30
+ if [ -z "$dirty" ]; then
31
+ pass "working-tree-clean"
32
+ else
33
+ local sample
34
+ sample=$(printf '%s\n' "$dirty" | head -3 | tr '\n' ' ')
35
+ fail "working-tree-clean" "uncommitted changes outside .local/: ${sample}"
36
+ fi
37
+ }
38
+
39
+ # ── 2) On branch v0.8-alpha ────────────────────────────────────────────────────
40
+ check_branch() {
41
+ local branch
42
+ branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || printf 'UNKNOWN')
43
+ if [ "$branch" = "v0.8-alpha" ]; then
44
+ pass "on-branch"
45
+ else
46
+ fail "on-branch" "expected v0.8-alpha, got '${branch}'"
47
+ fi
48
+ }
49
+
50
+ # ── 3) Branch pushed to origin ─────────────────────────────────────────────────
51
+ check_pushed() {
52
+ if ! git rev-parse --verify "origin/v0.8-alpha" >/dev/null 2>&1; then
53
+ fail "branch-pushed" "origin/v0.8-alpha not found — run: gh auth refresh -s workflow && git push -u origin v0.8-alpha"
54
+ return
55
+ fi
56
+ local count
57
+ count=$(git rev-list origin/v0.8-alpha..HEAD 2>/dev/null | wc -l | tr -d ' \t')
58
+ if [ "${count}" = "0" ]; then
59
+ pass "branch-pushed"
60
+ else
61
+ fail "branch-pushed" "${count} unpushed commit(s) — run: gh auth refresh -s workflow && git push origin v0.8-alpha"
62
+ fi
63
+ }
64
+
65
+ # ── 4) root package.json sanity ────────────────────────────────────────────────
66
+ check_root_pkg() {
67
+ if [ ! -f "package.json" ]; then
68
+ fail "root-pkg" "package.json not found"
69
+ return
70
+ fi
71
+
72
+ local name version files_count pub_access bin_has_key
73
+ name=$(node -e "process.stdout.write(require('./package.json').name||'')")
74
+ version=$(node -e "process.stdout.write(require('./package.json').version||'')")
75
+ files_count=$(node -e "const p=require('./package.json'); process.stdout.write(String((p.files||[]).length))")
76
+ pub_access=$(node -e "const p=require('./package.json'); process.stdout.write(((p.publishConfig||{}).access)||'')")
77
+ bin_has_key=$(node -e "const p=require('./package.json'); process.stdout.write(Object.prototype.hasOwnProperty.call(p.bin||{},'claws-code')?'yes':'no')")
78
+
79
+ local ok=1
80
+
81
+ if [ "$name" != "claws-code" ]; then
82
+ fail "root-pkg-name" "expected 'claws-code', got '${name}'"
83
+ ok=0
84
+ fi
85
+
86
+ if ! printf '%s' "$version" | grep -qE '^0\.8\.[0-9]+$'; then
87
+ fail "root-pkg-version" "expected v0.8.x semver, got '${version}'"
88
+ ok=0
89
+ else
90
+ ROOT_VERSION="$version"
91
+ fi
92
+
93
+ if [ "${files_count:-0}" -lt 10 ] 2>/dev/null; then
94
+ fail "root-pkg-files" "expected >=10 entries in 'files', got ${files_count}"
95
+ ok=0
96
+ fi
97
+
98
+ if [ "$pub_access" != "public" ]; then
99
+ fail "root-pkg-publishconfig" "publishConfig.access='${pub_access}', expected 'public'"
100
+ ok=0
101
+ fi
102
+
103
+ if [ "$bin_has_key" != "yes" ]; then
104
+ fail "root-pkg-bin" "bin['claws-code'] missing from root package.json"
105
+ ok=0
106
+ fi
107
+
108
+ if [ "$ok" = "1" ]; then
109
+ pass "root-pkg (name=${name}, version=${version}, files=${files_count})"
110
+ fi
111
+ }
112
+
113
+ # ── 5) extension/package.json sanity ───────────────────────────────────────────
114
+ check_ext_pkg() {
115
+ if [ ! -f "extension/package.json" ]; then
116
+ fail "ext-pkg" "extension/package.json not found"
117
+ return
118
+ fi
119
+
120
+ local name publisher version
121
+ name=$(node -e "process.stdout.write(require('./extension/package.json').name||'')")
122
+ publisher=$(node -e "process.stdout.write(require('./extension/package.json').publisher||'')")
123
+ version=$(node -e "process.stdout.write(require('./extension/package.json').version||'')")
124
+
125
+ local ok=1
126
+
127
+ if [ "$name" != "claws" ]; then
128
+ fail "ext-pkg-name" "expected 'claws', got '${name}'"
129
+ ok=0
130
+ fi
131
+
132
+ if [ "$publisher" != "neunaha" ]; then
133
+ fail "ext-pkg-publisher" "expected 'neunaha', got '${publisher}'"
134
+ ok=0
135
+ fi
136
+
137
+ if [ -n "$ROOT_VERSION" ] && [ "$version" != "$ROOT_VERSION" ]; then
138
+ fail "ext-pkg-version" "version '${version}' does not match root '${ROOT_VERSION}'"
139
+ ok=0
140
+ fi
141
+
142
+ if [ "$ok" = "1" ]; then
143
+ pass "ext-pkg (name=${name}, publisher=${publisher}, version=${version})"
144
+ fi
145
+ }
146
+
147
+ # ── 6) bin/cli.js is executable ────────────────────────────────────────────────
148
+ check_cli_executable() {
149
+ if [ -x "bin/cli.js" ]; then
150
+ pass "bin-cli-executable"
151
+ else
152
+ fail "bin-cli-executable" "bin/cli.js is not executable — run: git update-index --chmod=+x bin/cli.js"
153
+ fi
154
+ }
155
+
156
+ # ── 7) extension/LICENSE exists and non-empty ──────────────────────────────────
157
+ check_ext_license() {
158
+ if [ ! -f "extension/LICENSE" ]; then
159
+ fail "ext-license" "extension/LICENSE not found"
160
+ elif [ ! -s "extension/LICENSE" ]; then
161
+ fail "ext-license" "extension/LICENSE is empty"
162
+ else
163
+ pass "ext-license"
164
+ fi
165
+ }
166
+
167
+ # ── 8) extension/dist/extension.js contains AF-AC Phase 1 marker ───────────────
168
+ check_ext_dist_marker() {
169
+ local dist="extension/dist/extension.js"
170
+ if [ ! -f "$dist" ]; then
171
+ fail "ext-dist-marker" "${dist} not found — run: bash scripts/dev-vsix-install.sh"
172
+ return
173
+ fi
174
+ if grep -q "process_exited" "$dist" 2>/dev/null; then
175
+ pass "ext-dist-marker (AF-AC process_exited present)"
176
+ else
177
+ fail "ext-dist-marker" "'process_exited' not found in ${dist} — run: bash scripts/dev-vsix-install.sh"
178
+ fi
179
+ }
180
+
181
+ # ── 9) npm pack --dry-run: size < 20 MB and no forbidden entries ───────────────
182
+ check_npm_pack() {
183
+ local out
184
+ out=$(npm pack --dry-run 2>&1) || { fail "npm-pack" "npm pack --dry-run failed"; return; }
185
+
186
+ # Size check
187
+ local size_line size_val size_unit
188
+ size_line=$(printf '%s\n' "$out" | grep -i "package size:" | sed 's/.*package size:[[:space:]]*//' | tail -1)
189
+ if [ -z "$size_line" ]; then
190
+ fail "npm-pack-size" "could not parse 'package size:' from npm pack output"
191
+ else
192
+ size_val=$(printf '%s\n' "$size_line" | awk '{print $1}')
193
+ size_unit=$(printf '%s\n' "$size_line" | awk '{print $2}')
194
+
195
+ local size_ok
196
+ size_ok=$(awk -v val="$size_val" -v unit="$size_unit" 'BEGIN {
197
+ if (unit ~ /^[Gg]/) { print "NO" }
198
+ else if (unit ~ /^[Mm]/) { print (val + 0 >= 20) ? "NO" : "YES" }
199
+ else { print "YES" }
200
+ }')
201
+
202
+ if [ "$size_ok" = "YES" ]; then
203
+ pass "npm-pack-size (${size_val} ${size_unit})"
204
+ else
205
+ fail "npm-pack-size" "${size_val} ${size_unit} >= 20 MB — check .npmignore chain"
206
+ fi
207
+ fi
208
+
209
+ # Forbidden-entries check
210
+ local bad
211
+ bad=$(printf '%s\n' "$out" | sed 's/^npm notice //' | \
212
+ grep -E "(extension/test/|docs/images/|scripts/ae4-)" | head -5 || true)
213
+ if [ -n "$bad" ]; then
214
+ local sample
215
+ sample=$(printf '%s' "$bad" | tr '\n' ' ')
216
+ fail "npm-pack-exclusions" "forbidden paths in tarball: ${sample}"
217
+ else
218
+ pass "npm-pack-exclusions"
219
+ fi
220
+ }
221
+
222
+ # ── 10) vsce package round-trip ────────────────────────────────────────────────
223
+ check_vsce_package() {
224
+ local vsix_out="/tmp/claws-verify-release.vsix"
225
+ rm -f "$vsix_out"
226
+
227
+ local vsce_out vsce_ok=0
228
+ if vsce_out=$(cd extension && npx @vscode/vsce package --no-yarn --out "$vsix_out" 2>&1); then
229
+ vsce_ok=1
230
+ fi
231
+
232
+ if [ "$vsce_ok" = "0" ]; then
233
+ local tail3
234
+ tail3=$(printf '%s\n' "$vsce_out" | tail -3 | tr '\n' ' ')
235
+ fail "vsce-package" "vsce package failed — ${tail3}"
236
+ return
237
+ fi
238
+
239
+ if [ ! -f "$vsix_out" ]; then
240
+ fail "vsce-package" "VSIX not created at ${vsix_out}"
241
+ return
242
+ fi
243
+
244
+ # vsce renames LICENSE → LICENSE.txt automatically; accept either form.
245
+ # Use a temp file to dodge any pipefail / file-not-found quirks across shells.
246
+ local unzip_listing
247
+ unzip_listing=$(unzip -l "$vsix_out" 2>/dev/null || true)
248
+ if printf '%s\n' "$unzip_listing" | grep -qE 'extension/LICENSE(\.txt)?($|[[:space:]])'; then
249
+ pass "vsce-package (VSIX created, contains extension/LICENSE)"
250
+ else
251
+ fail "vsce-package" "VSIX at ${vsix_out} does not contain extension/LICENSE (or .txt variant)"
252
+ fi
253
+ }
254
+
255
+ # ── 11) CHANGELOG.md has version section ───────────────────────────────────────
256
+ check_changelog() {
257
+ if [ ! -f "CHANGELOG.md" ]; then
258
+ fail "changelog" "CHANGELOG.md not found"
259
+ return
260
+ fi
261
+ if [ -z "$ROOT_VERSION" ]; then
262
+ fail "changelog" "skipped — ROOT_VERSION unknown (root package.json check failed)"
263
+ return
264
+ fi
265
+ if grep -q "## \[${ROOT_VERSION}\]" CHANGELOG.md 2>/dev/null; then
266
+ pass "changelog (## [${ROOT_VERSION}] present)"
267
+ else
268
+ fail "changelog" "no '## [${ROOT_VERSION}]' section in CHANGELOG.md"
269
+ fi
270
+ }
271
+
272
+ # ── 12) git tag v<version>-alpha does NOT yet exist ────────────────────────────
273
+ check_tag_pending() {
274
+ if [ -z "$ROOT_VERSION" ]; then
275
+ fail "tag-pending" "skipped — ROOT_VERSION unknown (root package.json check failed)"
276
+ return
277
+ fi
278
+ local tag="v${ROOT_VERSION}-alpha"
279
+ if git tag --list "$tag" 2>/dev/null | grep -q "^${tag}$"; then
280
+ # Path B: tag is created locally BEFORE the publish gate runs.
281
+ # Verify it points to current HEAD; warn if it's drifted.
282
+ local tag_commit
283
+ tag_commit=$(git rev-parse "${tag}^{commit}" 2>/dev/null)
284
+ local head_commit
285
+ head_commit=$(git rev-parse HEAD 2>/dev/null)
286
+ if [ "$tag_commit" = "$head_commit" ]; then
287
+ pass "tag (${tag} created locally, points to HEAD)"
288
+ else
289
+ fail "tag" "${tag} exists but points to ${tag_commit:0:7}, not HEAD ${head_commit:0:7}"
290
+ fi
291
+ else
292
+ pass "tag-pending (${tag} not yet created — will be tagged later)"
293
+ fi
294
+ }
295
+
296
+ # ── Run all checks ─────────────────────────────────────────────────────────────
297
+ printf '=== Claws v0.8 pre-publish gate ===\n\n'
298
+
299
+ check_clean_tree
300
+ check_branch
301
+ check_pushed
302
+ check_root_pkg
303
+ check_ext_pkg
304
+ check_cli_executable
305
+ check_ext_license
306
+ check_ext_dist_marker
307
+ check_npm_pack
308
+ check_vsce_package
309
+ check_changelog
310
+ check_tag_pending
311
+
312
+ # ── Summary ────────────────────────────────────────────────────────────────────
313
+ printf '\n----- summary -----\n'
314
+ if [ "$FAIL" -gt 0 ]; then
315
+ for f in "${FAILURES[@]}"; do
316
+ printf '%s\n' "$f"
317
+ done
318
+ printf '\nNOT READY (%d blocker(s) above)\n' "$FAIL"
319
+ exit 1
320
+ else
321
+ printf 'READY TO PUBLISH\n'
322
+ exit 0
323
+ fi