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.
- package/.claude/commands/claws-auto.md +90 -0
- package/.claude/commands/claws-bin.md +28 -0
- package/.claude/commands/claws-cleanup.md +28 -0
- package/.claude/commands/claws-do.md +82 -0
- package/.claude/commands/claws-fix.md +40 -0
- package/.claude/commands/claws-goal.md +111 -0
- package/.claude/commands/claws-help.md +54 -0
- package/.claude/commands/claws-plan.md +103 -0
- package/.claude/commands/claws-report.md +29 -0
- package/.claude/commands/claws-status.md +37 -0
- package/.claude/commands/claws-update.md +32 -0
- package/.claude/commands/claws.md +64 -0
- package/.claude/rules/claws-default-behavior.md +76 -0
- package/.claude/settings.json +112 -0
- package/.claude/settings.local.json +19 -0
- package/.claude/skills/claws-auto-engine/SKILL.md +97 -0
- package/.claude/skills/claws-goal-tracker/SKILL.md +106 -0
- package/.claude/skills/claws-prompt-templates/SKILL.md +203 -0
- package/.claude/skills/claws-wave-lead/SKILL.md +126 -0
- package/.claude/skills/claws-wave-subworker/SKILL.md +60 -0
- package/CHANGELOG.md +1949 -0
- package/LICENSE +21 -0
- package/README.md +420 -0
- package/bin/cli.js +84 -0
- package/cli.js +223 -0
- package/docs/ARCHITECTURE.md +511 -0
- package/docs/event-protocol.md +588 -0
- package/docs/features.md +562 -0
- package/docs/guide.md +891 -0
- package/docs/index.html +716 -0
- package/docs/protocol.md +323 -0
- package/extension/.vscodeignore +15 -0
- package/extension/CHANGELOG.md +1906 -0
- package/extension/LICENSE +21 -0
- package/extension/README.md +137 -0
- package/extension/docs/features.md +424 -0
- package/extension/docs/protocol.md +197 -0
- package/extension/esbuild.mjs +25 -0
- package/extension/icon.png +0 -0
- package/extension/native/.metadata.json +10 -0
- package/extension/native/node-pty/LICENSE +69 -0
- package/extension/native/node-pty/README.md +165 -0
- package/extension/native/node-pty/lib/conpty_console_list_agent.js +16 -0
- package/extension/native/node-pty/lib/conpty_console_list_agent.js.map +1 -0
- package/extension/native/node-pty/lib/eventEmitter2.js +47 -0
- package/extension/native/node-pty/lib/eventEmitter2.js.map +1 -0
- package/extension/native/node-pty/lib/index.js +52 -0
- package/extension/native/node-pty/lib/index.js.map +1 -0
- package/extension/native/node-pty/lib/interfaces.js +7 -0
- package/extension/native/node-pty/lib/interfaces.js.map +1 -0
- package/extension/native/node-pty/lib/shared/conout.js +11 -0
- package/extension/native/node-pty/lib/shared/conout.js.map +1 -0
- package/extension/native/node-pty/lib/terminal.js +190 -0
- package/extension/native/node-pty/lib/terminal.js.map +1 -0
- package/extension/native/node-pty/lib/types.js +7 -0
- package/extension/native/node-pty/lib/types.js.map +1 -0
- package/extension/native/node-pty/lib/unixTerminal.js +346 -0
- package/extension/native/node-pty/lib/unixTerminal.js.map +1 -0
- package/extension/native/node-pty/lib/utils.js +39 -0
- package/extension/native/node-pty/lib/utils.js.map +1 -0
- package/extension/native/node-pty/lib/windowsConoutConnection.js +125 -0
- package/extension/native/node-pty/lib/windowsConoutConnection.js.map +1 -0
- package/extension/native/node-pty/lib/windowsPtyAgent.js +320 -0
- package/extension/native/node-pty/lib/windowsPtyAgent.js.map +1 -0
- package/extension/native/node-pty/lib/windowsTerminal.js +199 -0
- package/extension/native/node-pty/lib/windowsTerminal.js.map +1 -0
- package/extension/native/node-pty/lib/worker/conoutSocketWorker.js +22 -0
- package/extension/native/node-pty/lib/worker/conoutSocketWorker.js.map +1 -0
- package/extension/native/node-pty/package.json +64 -0
- package/extension/native/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
- package/extension/native/node-pty/prebuilds/darwin-x64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/winpty-agent.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/winpty.dll +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/winpty-agent.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/winpty.dll +0 -0
- package/extension/package-lock.json +605 -0
- package/extension/package.json +343 -0
- package/extension/scripts/bundle-native.mjs +104 -0
- package/extension/scripts/deploy-dev.mjs +60 -0
- package/extension/src/ansi-strip.ts +52 -0
- package/extension/src/backends/vscode/claws-pty.ts +483 -0
- package/extension/src/backends/vscode/status-bar.ts +99 -0
- package/extension/src/backends/vscode/vscode-backend.ts +282 -0
- package/extension/src/capture-store.ts +125 -0
- package/extension/src/event-log.ts +629 -0
- package/extension/src/event-schemas.ts +478 -0
- package/extension/src/extension.js +492 -0
- package/extension/src/extension.ts +873 -0
- package/extension/src/lifecycle-engine.ts +60 -0
- package/extension/src/lifecycle-rules.ts +171 -0
- package/extension/src/lifecycle-store.ts +506 -0
- package/extension/src/peer-registry.ts +176 -0
- package/extension/src/pipeline-registry.ts +82 -0
- package/extension/src/platform.ts +64 -0
- package/extension/src/protocol.ts +532 -0
- package/extension/src/server-config.ts +98 -0
- package/extension/src/server.ts +2210 -0
- package/extension/src/task-registry.ts +51 -0
- package/extension/src/terminal-backend.ts +211 -0
- package/extension/src/terminal-manager.ts +395 -0
- package/extension/src/topic-registry.ts +70 -0
- package/extension/src/topic-utils.ts +46 -0
- package/extension/src/transport.ts +45 -0
- package/extension/src/uninstall-cleanup.ts +232 -0
- package/extension/src/wave-registry.ts +314 -0
- package/extension/src/websocket-transport.ts +153 -0
- package/extension/tsconfig.json +23 -0
- package/lib/capabilities.js +145 -0
- package/lib/dry-run.js +43 -0
- package/lib/install.js +1018 -0
- package/lib/mcp-setup.js +92 -0
- package/lib/platform.js +240 -0
- package/lib/preflight.js +152 -0
- package/lib/shell-hook.js +343 -0
- package/lib/uninstall.js +162 -0
- package/lib/verify.js +166 -0
- package/mcp_server.js +3529 -0
- package/package.json +48 -0
- package/rules/claws-default-behavior.md +72 -0
- package/scripts/_helpers/atomic-file.mjs +137 -0
- package/scripts/_helpers/fix-repair.js +64 -0
- package/scripts/_helpers/json-safe.mjs +218 -0
- package/scripts/bump-version.sh +84 -0
- package/scripts/codegen/gen-docs.mjs +61 -0
- package/scripts/codegen/gen-json-schema.mjs +62 -0
- package/scripts/codegen/gen-mcp-tools.mjs +358 -0
- package/scripts/codegen/gen-types.mjs +172 -0
- package/scripts/codegen/index.mjs +42 -0
- package/scripts/dev-hooks/check-extension-dirs.js +77 -0
- package/scripts/dev-hooks/check-open-claws-terminals.js +70 -0
- package/scripts/dev-hooks/check-stale-main.js +55 -0
- package/scripts/dev-hooks/check-tag-pushed.js +51 -0
- package/scripts/dev-hooks/check-tag-vs-main.js +56 -0
- package/scripts/dev-vsix-install.sh +60 -0
- package/scripts/fix.sh +702 -0
- package/scripts/gen-client-types.mjs +81 -0
- package/scripts/git-hooks/pre-commit +31 -0
- package/scripts/hooks/lifecycle-state.js +61 -0
- package/scripts/hooks/package.json +4 -0
- package/scripts/hooks/post-tool-use-claws.js +292 -0
- package/scripts/hooks/pre-bash-no-verify-block.js +72 -0
- package/scripts/hooks/pre-tool-use-claws.js +206 -0
- package/scripts/hooks/session-start-claws.js +97 -0
- package/scripts/hooks/stop-claws.js +88 -0
- package/scripts/inject-claude-md.js +205 -0
- package/scripts/inject-dev-hooks.js +96 -0
- package/scripts/inject-global-claude-md.js +140 -0
- package/scripts/inject-settings-hooks.js +370 -0
- package/scripts/install.ps1 +146 -0
- package/scripts/install.sh +1729 -0
- package/scripts/monitor-arm-watch.js +155 -0
- package/scripts/rebuild-node-pty.sh +245 -0
- package/scripts/report.sh +232 -0
- package/scripts/shell-hook.fish +164 -0
- package/scripts/shell-hook.ps1 +33 -0
- package/scripts/shell-hook.sh +232 -0
- package/scripts/stream-events.js +399 -0
- package/scripts/terminal-wrapper.sh +36 -0
- package/scripts/test-enforcement.sh +132 -0
- package/scripts/test-install.sh +174 -0
- package/scripts/test-installer-parity.sh +135 -0
- package/scripts/test-template-enforcement.sh +76 -0
- package/scripts/uninstall.sh +143 -0
- package/scripts/update.sh +337 -0
- package/scripts/verify-release.sh +323 -0
- package/scripts/verify-wrapped.sh +194 -0
- package/templates/CLAUDE.global.md +135 -0
- package/templates/CLAUDE.project.md +37 -0
|
@@ -0,0 +1,1729 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Claws — project-local installer
|
|
3
|
+
# Usage: cd /path/to/project && bash <(curl -fsSL https://raw.githubusercontent.com/neunaha/claws/main/scripts/install.sh)
|
|
4
|
+
#
|
|
5
|
+
# Env overrides:
|
|
6
|
+
# CLAWS_DIR=/path Where to clone the source (default: ~/.claws-src)
|
|
7
|
+
# CLAWS_EDITOR=cursor|insiders|windsurf|skip Which editor's extensions dir to use
|
|
8
|
+
# CLAWS_SKIP_MCP=1 Don't write .mcp.json
|
|
9
|
+
# CLAWS_GLOBAL_MCP=1 Also register globally in ~/.claude/settings.json
|
|
10
|
+
# CLAWS_GLOBAL_CONFIG=1 Also write commands/skills/rules into ~/.claude/
|
|
11
|
+
# CLAWS_DEBUG=1 Enable bash -x trace
|
|
12
|
+
# CLAWS_NO_LOG=1 Disable the /tmp/claws-install-*.log file
|
|
13
|
+
# CLAWS_STRICT=1 Hard-block long-running Bash via PreToolUse hook (v0.6.4+)
|
|
14
|
+
# CLAWS_NO_GLOBAL_HOOKS=1 Skip ~/.claude/settings.json hook registration (test/CI safe)
|
|
15
|
+
|
|
16
|
+
# ─── Strict-ish mode ────────────────────────────────────────────────────────
|
|
17
|
+
# -e: exit on unhandled error
|
|
18
|
+
# -o pipefail: catch errors inside pipes
|
|
19
|
+
# We do NOT use -u because optional env vars are allowed to be unset.
|
|
20
|
+
set -eo pipefail
|
|
21
|
+
|
|
22
|
+
# If CLAWS_DEBUG=1, trace every line.
|
|
23
|
+
# Unset any env vars that may contain secrets before enabling xtrace
|
|
24
|
+
# so they don't appear in the trace log.
|
|
25
|
+
if [ "${CLAWS_DEBUG:-0}" = "1" ]; then
|
|
26
|
+
{ unset AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_ACCESS_KEY_ID \
|
|
27
|
+
GITHUB_TOKEN NPM_TOKEN ANTHROPIC_API_KEY OPENAI_API_KEY \
|
|
28
|
+
CLAWS_TOKEN HOMEBREW_GITHUB_API_TOKEN; } 2>/dev/null || true
|
|
29
|
+
set -x
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# ─── Logging ────────────────────────────────────────────────────────────────
|
|
33
|
+
CLAWS_LOG="${CLAWS_LOG:-/tmp/claws-install-$(date +%Y%m%d-%H%M%S)-$$.log}"
|
|
34
|
+
if [ "${CLAWS_NO_LOG:-0}" != "1" ]; then
|
|
35
|
+
# Tee all stdout and stderr through the log file.
|
|
36
|
+
# Using process substitution so the log captures both the script's own
|
|
37
|
+
# output and anything child processes emit.
|
|
38
|
+
exec > >(tee -a "$CLAWS_LOG") 2> >(tee -a "$CLAWS_LOG" >&2)
|
|
39
|
+
trap 'printf "\n\nInstall log saved to: %s\n" "$CLAWS_LOG" >&2' EXIT
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# ─── Colors and progress helpers ────────────────────────────────────────────
|
|
43
|
+
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
|
|
44
|
+
C_RESET='\033[0m'; C_BOLD='\033[1m'
|
|
45
|
+
C_BLUE='\033[0;34m'; C_GREEN='\033[0;32m'; C_YELLOW='\033[0;33m'; C_RED='\033[0;31m'; C_DIM='\033[2m'
|
|
46
|
+
else
|
|
47
|
+
C_RESET=''; C_BOLD=''; C_BLUE=''; C_GREEN=''; C_YELLOW=''; C_RED=''; C_DIM=''
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
STEP_NUM=0
|
|
51
|
+
STEP_TOTAL=8
|
|
52
|
+
step() { STEP_NUM=$((STEP_NUM+1)); printf "\n${C_BOLD}${C_BLUE}[%d/%d]${C_RESET} %s\n" "$STEP_NUM" "$STEP_TOTAL" "$*"; }
|
|
53
|
+
ok() { printf " ${C_GREEN}✓${C_RESET} %s\n" "$*"; }
|
|
54
|
+
warn() { printf " ${C_YELLOW}!${C_RESET} %s\n" "$*"; }
|
|
55
|
+
bad() { printf " ${C_RED}✗${C_RESET} %s\n" "$*"; }
|
|
56
|
+
info() { printf " ${C_DIM}%s${C_RESET}\n" "$*"; }
|
|
57
|
+
die() { bad "$*"; exit 1; }
|
|
58
|
+
|
|
59
|
+
# Treat any unhandled error as fatal with a clear message.
|
|
60
|
+
trap 'ec=$?; if [ $ec -ne 0 ]; then printf "\n${C_RED}${C_BOLD}INSTALL FAILED${C_RESET} at line $LINENO (exit $ec). See log: %s\n" "$CLAWS_LOG" >&2; fi' ERR
|
|
61
|
+
|
|
62
|
+
# ─── Globals ────────────────────────────────────────────────────────────────
|
|
63
|
+
REPO="https://github.com/neunaha/claws.git"
|
|
64
|
+
INSTALL_DIR="${CLAWS_DIR:-$HOME/.claws-src}"
|
|
65
|
+
CLAWS_REF="${CLAWS_REF:-main}"
|
|
66
|
+
USER_PWD="$(pwd)"
|
|
67
|
+
PLATFORM="$(uname -s)"
|
|
68
|
+
case "$PLATFORM" in
|
|
69
|
+
MINGW*|MSYS*|CYGWIN*)
|
|
70
|
+
die "Windows (Git Bash/MSYS/Cygwin) not supported — use WSL2: https://aka.ms/wslinstall" ;;
|
|
71
|
+
esac
|
|
72
|
+
|
|
73
|
+
# ─── Banner ─────────────────────────────────────────────────────────────────
|
|
74
|
+
cat <<BANNER
|
|
75
|
+
|
|
76
|
+
${C_BOLD}╔═══════════════════════════════════════════╗
|
|
77
|
+
║ ║
|
|
78
|
+
║ CLAWS — Terminal Control Bridge ║
|
|
79
|
+
║ Project-local orchestration setup ║
|
|
80
|
+
║ ║
|
|
81
|
+
╚═══════════════════════════════════════════╝${C_RESET}
|
|
82
|
+
|
|
83
|
+
BANNER
|
|
84
|
+
|
|
85
|
+
# ─── Project safety check ──────────────────────────────────────────────────
|
|
86
|
+
is_safe_project_dir() {
|
|
87
|
+
case "$1" in
|
|
88
|
+
"" | "/" | "$HOME" | "/tmp" | "/tmp/" | "/var" | "/var/" \
|
|
89
|
+
| "/opt" | "/opt/" | "/Users" | "/Users/" | "/etc" | "/etc/") return 1 ;;
|
|
90
|
+
esac
|
|
91
|
+
return 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if is_safe_project_dir "$USER_PWD"; then
|
|
95
|
+
PROJECT_ROOT="$USER_PWD"
|
|
96
|
+
PROJECT_INSTALL=1
|
|
97
|
+
echo " Installing into project: $PROJECT_ROOT"
|
|
98
|
+
else
|
|
99
|
+
PROJECT_ROOT=""
|
|
100
|
+
PROJECT_INSTALL=0
|
|
101
|
+
warn "$USER_PWD is not a safe project dir — skipping project-local install."
|
|
102
|
+
info "For a full per-project setup: cd into your project and re-run."
|
|
103
|
+
fi
|
|
104
|
+
echo ""
|
|
105
|
+
|
|
106
|
+
# ─── Preflight: dependencies ───────────────────────────────────────────────
|
|
107
|
+
# Every dependency the installer or the extension's build chain touches is
|
|
108
|
+
# checked here with a specific install command for the ones that are missing.
|
|
109
|
+
# Fatal checks (die) are things Claws literally cannot work without. Warning
|
|
110
|
+
# checks are things that degrade specific features.
|
|
111
|
+
echo "Checking dependencies..."
|
|
112
|
+
# Disk space: clone + npm install + native build + VSIX needs ~500MB
|
|
113
|
+
_avail_kb=$(df -k "$HOME" 2>/dev/null | awk 'NR==2{print $4}' || echo "")
|
|
114
|
+
if [ -n "$_avail_kb" ] && [ "$_avail_kb" -lt 512000 ] 2>/dev/null; then
|
|
115
|
+
warn "Low disk space: $(( _avail_kb / 1024 ))MB free in \$HOME — Claws needs ~500MB for clone, build, and VSIX packaging"
|
|
116
|
+
fi
|
|
117
|
+
unset _avail_kb
|
|
118
|
+
|
|
119
|
+
# ── Required: git ──────────────────────────────────────────────────────────
|
|
120
|
+
if command -v git &>/dev/null; then
|
|
121
|
+
GIT_VERSION_STR="$(git --version | awk '{print $3}')"
|
|
122
|
+
GIT_MAJOR="$(echo "$GIT_VERSION_STR" | cut -d. -f1)"
|
|
123
|
+
if [ "${GIT_MAJOR:-0}" -lt 2 ] 2>/dev/null; then
|
|
124
|
+
die "git $GIT_VERSION_STR too old — Claws requires git 2+. Upgrade via your package manager."
|
|
125
|
+
fi
|
|
126
|
+
ok "git ($GIT_VERSION_STR)"
|
|
127
|
+
else
|
|
128
|
+
case "$PLATFORM" in
|
|
129
|
+
Darwin) die "git not found — install with: xcode-select --install" ;;
|
|
130
|
+
Linux) die "git not found — install with: sudo apt install git (or your distro's package manager)" ;;
|
|
131
|
+
*) die "git not found — install from https://git-scm.com/" ;;
|
|
132
|
+
esac
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# ── Required: Node.js 18+ (for MCP server + extension build) ───────────────
|
|
136
|
+
if ! command -v node &>/dev/null; then
|
|
137
|
+
[ -d "$HOME/.nvm" ] && info "nvm detected — run: nvm use --lts then re-run this installer."
|
|
138
|
+
[ -d "$HOME/.fnm" ] && info "fnm detected — run: fnm use --lts then re-run this installer."
|
|
139
|
+
case "$PLATFORM" in
|
|
140
|
+
Darwin) die "node not found — install with: brew install node (or from https://nodejs.org/)" ;;
|
|
141
|
+
Linux) die "node not found — install with: sudo apt install nodejs (or use nvm: https://github.com/nvm-sh/nvm)" ;;
|
|
142
|
+
*) die "node not found — install from https://nodejs.org/" ;;
|
|
143
|
+
esac
|
|
144
|
+
fi
|
|
145
|
+
NODE_MAJOR=$(node -e "console.log(process.versions.node.split('.')[0])" 2>/dev/null || echo "0")
|
|
146
|
+
if [ "$NODE_MAJOR" -lt 18 ] 2>/dev/null; then
|
|
147
|
+
die "node $(node --version) is too old — Claws requires Node 18+. Upgrade via nvm or your package manager."
|
|
148
|
+
fi
|
|
149
|
+
ok "node ($(node --version))"
|
|
150
|
+
|
|
151
|
+
# ── Required: npm (bundled with node, but verify separately) ───────────────
|
|
152
|
+
if ! command -v npm &>/dev/null; then
|
|
153
|
+
die "npm not found — should ship with Node. Reinstall Node or install npm separately."
|
|
154
|
+
fi
|
|
155
|
+
ok "npm ($(npm --version))"
|
|
156
|
+
NPM_MAJOR=$(npm --version 2>/dev/null | cut -d. -f1 || echo "0")
|
|
157
|
+
if [ "$NPM_MAJOR" -lt 7 ] 2>/dev/null; then
|
|
158
|
+
die "npm $(npm --version) too old — requires npm 7+. Upgrade: npm install -g npm"
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# ── Optional but strongly recommended: C++ toolchain for native modules ────
|
|
162
|
+
# node-pty (optional dep) compiles C++ when no prebuilt binary matches the
|
|
163
|
+
# user's Node version. Without it, wrapped terminals fall back to pipe-mode
|
|
164
|
+
# which doesn't render TUIs correctly. Warn proactively so the user can fix
|
|
165
|
+
# it BEFORE npm install rather than discovering the silent failure later.
|
|
166
|
+
TOOLCHAIN_OK=1
|
|
167
|
+
case "$PLATFORM" in
|
|
168
|
+
Darwin)
|
|
169
|
+
if xcode-select -p &>/dev/null; then
|
|
170
|
+
ok "Xcode Command Line Tools ($(xcode-select -p))"
|
|
171
|
+
else
|
|
172
|
+
warn "Xcode Command Line Tools not installed"
|
|
173
|
+
info "install with: xcode-select --install"
|
|
174
|
+
info "without this, wrapped terminals will use degraded pipe-mode (TUIs render poorly)"
|
|
175
|
+
TOOLCHAIN_OK=0
|
|
176
|
+
fi
|
|
177
|
+
;;
|
|
178
|
+
Linux)
|
|
179
|
+
if command -v g++ &>/dev/null && command -v make &>/dev/null; then
|
|
180
|
+
ok "C++ toolchain (g++ $(g++ -dumpversion 2>/dev/null), make $(make --version | head -1 | awk '{print $3}'))"
|
|
181
|
+
else
|
|
182
|
+
warn "C++ toolchain missing — wrapped terminals may fall back to pipe-mode"
|
|
183
|
+
info "install with: sudo apt install build-essential (Debian/Ubuntu)"
|
|
184
|
+
info " or: sudo dnf install gcc-c++ make (Fedora/RHEL)"
|
|
185
|
+
TOOLCHAIN_OK=0
|
|
186
|
+
fi
|
|
187
|
+
;;
|
|
188
|
+
esac
|
|
189
|
+
|
|
190
|
+
# ── Optional: python3 for node-gyp ─────────────────────────────────────────
|
|
191
|
+
# node-gyp spawns python3 to run its gyp files. Missing python3 means
|
|
192
|
+
# node-pty source build will fail even if the C++ toolchain is present.
|
|
193
|
+
if command -v python3 &>/dev/null; then
|
|
194
|
+
ok "python3 ($(python3 --version 2>&1 | awk '{print $2}'))"
|
|
195
|
+
elif command -v python &>/dev/null && python --version 2>&1 | grep -q "Python 3"; then
|
|
196
|
+
ok "python ($(python --version 2>&1 | awk '{print $2}'))"
|
|
197
|
+
else
|
|
198
|
+
warn "python3 not found — needed by node-gyp for native module compilation"
|
|
199
|
+
case "$PLATFORM" in
|
|
200
|
+
Darwin) info "install with: brew install python3 (or xcode-select --install provides it)" ;;
|
|
201
|
+
Linux) info "install with: sudo apt install python3" ;;
|
|
202
|
+
esac
|
|
203
|
+
TOOLCHAIN_OK=0
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# ── Optional: bundled VS Code CLI (for VSIX install in step 2c) ────────────
|
|
207
|
+
# We don't fail if absent — the installer falls back to a symlink. But log
|
|
208
|
+
# what we'd use so /claws-report shows the editor state.
|
|
209
|
+
FOUND_EDITOR_CLIS=()
|
|
210
|
+
for pair in \
|
|
211
|
+
"code:/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" \
|
|
212
|
+
"code-insiders:/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders" \
|
|
213
|
+
"cursor:/Applications/Cursor.app/Contents/Resources/app/bin/cursor" \
|
|
214
|
+
"windsurf:/Applications/Windsurf.app/Contents/Resources/app/bin/windsurf"; do
|
|
215
|
+
label="${pair%%:*}"
|
|
216
|
+
bundled="${pair#*:}"
|
|
217
|
+
if command -v "$label" &>/dev/null; then
|
|
218
|
+
FOUND_EDITOR_CLIS+=("$label (PATH)")
|
|
219
|
+
elif [ -x "$bundled" ]; then
|
|
220
|
+
FOUND_EDITOR_CLIS+=("$label (bundled)")
|
|
221
|
+
fi
|
|
222
|
+
done
|
|
223
|
+
if [ "${#FOUND_EDITOR_CLIS[@]}" -gt 0 ]; then
|
|
224
|
+
ok "editor CLI(s): ${FOUND_EDITOR_CLIS[*]}"
|
|
225
|
+
else
|
|
226
|
+
warn "no editor CLI detected — extension will install via symlink fallback"
|
|
227
|
+
info "to enable VSIX install on macOS: open VS Code and run 'Shell Command: Install code command in PATH'"
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
info "Platform: $PLATFORM $(uname -r)"
|
|
231
|
+
ARCH="$(uname -m)"
|
|
232
|
+
info "Architecture: $ARCH"
|
|
233
|
+
|
|
234
|
+
# ─── Platform/arch → prebuild key ──────────────────────────────────────────
|
|
235
|
+
# Maps uname -s / uname -m to the node-pty prebuild directory name used by
|
|
236
|
+
# bundle-native.mjs: darwin-x64 | darwin-arm64 | linux-x64 | linux-arm64.
|
|
237
|
+
# Mirrors lib/platform.js detectPlatformArch(); used by prebuilt-pty consumers.
|
|
238
|
+
CLAWS_PLATFORM_ARCH=""
|
|
239
|
+
case "$PLATFORM" in
|
|
240
|
+
Darwin)
|
|
241
|
+
case "$ARCH" in
|
|
242
|
+
arm64) CLAWS_PLATFORM_ARCH="darwin-arm64" ;;
|
|
243
|
+
*) CLAWS_PLATFORM_ARCH="darwin-x64" ;;
|
|
244
|
+
esac ;;
|
|
245
|
+
Linux)
|
|
246
|
+
case "$ARCH" in
|
|
247
|
+
aarch64|arm64) CLAWS_PLATFORM_ARCH="linux-arm64" ;;
|
|
248
|
+
*) CLAWS_PLATFORM_ARCH="linux-x64" ;;
|
|
249
|
+
esac ;;
|
|
250
|
+
*) CLAWS_PLATFORM_ARCH="linux-x64" ;;
|
|
251
|
+
esac
|
|
252
|
+
info "Prebuild key: $CLAWS_PLATFORM_ARCH"
|
|
253
|
+
|
|
254
|
+
info "Shell: $SHELL ($BASH_VERSION)"
|
|
255
|
+
info "Install log: $CLAWS_LOG"
|
|
256
|
+
[ "$TOOLCHAIN_OK" = "0" ] && warn "toolchain issues above — install will still run, but node-pty may not compile"
|
|
257
|
+
echo ""
|
|
258
|
+
|
|
259
|
+
# ─── Pre-install: Kill stale worker processes from prior sessions ──────────
|
|
260
|
+
# Returns true when a process has CLAWS_TERMINAL_CORR_ID in its environment.
|
|
261
|
+
# Worker children are spawned with that var; the outer orchestrator running
|
|
262
|
+
# this install does NOT have it — only worker children are killed.
|
|
263
|
+
_has_corr_id() {
|
|
264
|
+
local pid="$1"
|
|
265
|
+
case "$PLATFORM" in
|
|
266
|
+
Darwin)
|
|
267
|
+
# ps -E on macOS appends environment variables after each process line
|
|
268
|
+
ps -E -p "$pid" 2>/dev/null | grep -q "CLAWS_TERMINAL_CORR_ID" ;;
|
|
269
|
+
Linux)
|
|
270
|
+
# /proc/<pid>/environ is NUL-delimited; tr replaces NULs with newlines
|
|
271
|
+
tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep -q "CLAWS_TERMINAL_CORR_ID" ;;
|
|
272
|
+
*) return 1 ;;
|
|
273
|
+
esac
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
_claws_cleanup_stale_workers() {
|
|
277
|
+
local killed=0 cleared=0
|
|
278
|
+
|
|
279
|
+
if command -v pgrep &>/dev/null; then
|
|
280
|
+
# Kill stale mcp_server.js processes that match the project path + CORR_ID filter
|
|
281
|
+
if [ "$PROJECT_INSTALL" = "1" ]; then
|
|
282
|
+
local target_mcp="$PROJECT_ROOT/.claws-bin/mcp_server.js"
|
|
283
|
+
while IFS= read -r line; do
|
|
284
|
+
[ -z "$line" ] && continue
|
|
285
|
+
local pid="${line%% *}"
|
|
286
|
+
local cmdline="${line#* }"
|
|
287
|
+
# Full path match prevents collateral kills of other projects' MCP servers
|
|
288
|
+
case "$cmdline" in
|
|
289
|
+
*"$target_mcp"*) ;;
|
|
290
|
+
*) continue ;;
|
|
291
|
+
esac
|
|
292
|
+
# CORR_ID filter: only kill worker children, not the outer orchestrator
|
|
293
|
+
_has_corr_id "$pid" || continue
|
|
294
|
+
info "[claws-clean] kill PID $pid (stale worker mcp_server.js)"
|
|
295
|
+
kill -TERM "$pid" 2>/dev/null || true
|
|
296
|
+
sleep 2
|
|
297
|
+
kill -KILL "$pid" 2>/dev/null || true
|
|
298
|
+
killed=$((killed + 1))
|
|
299
|
+
done < <(pgrep -af 'mcp_server\.js' 2>/dev/null || true)
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
# Kill stale spawn-helper processes (Mac/Linux only — Windows uses winpty/conpty)
|
|
303
|
+
while IFS= read -r line; do
|
|
304
|
+
[ -z "$line" ] && continue
|
|
305
|
+
local pid="${line%% *}"
|
|
306
|
+
_has_corr_id "$pid" || continue
|
|
307
|
+
info "[claws-clean] kill spawn-helper PID $pid"
|
|
308
|
+
kill -TERM "$pid" 2>/dev/null || true
|
|
309
|
+
killed=$((killed + 1))
|
|
310
|
+
done < <(pgrep -af 'spawn-helper' 2>/dev/null || true)
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
# Clear stale .claws/sidecar.pid if the recorded PID is dead
|
|
314
|
+
if [ "$PROJECT_INSTALL" = "1" ] && [ -f "$PROJECT_ROOT/.claws/sidecar.pid" ]; then
|
|
315
|
+
local spid
|
|
316
|
+
spid="$(tr -d '[:space:]' < "$PROJECT_ROOT/.claws/sidecar.pid" 2>/dev/null || true)"
|
|
317
|
+
if [ -n "$spid" ] && ! kill -0 "$spid" 2>/dev/null; then
|
|
318
|
+
rm -f "$PROJECT_ROOT/.claws/sidecar.pid"
|
|
319
|
+
info "[claws-clean] cleared stale sidecar.pid (PID $spid dead)"
|
|
320
|
+
cleared=$((cleared + 1))
|
|
321
|
+
fi
|
|
322
|
+
fi
|
|
323
|
+
|
|
324
|
+
ok "[claws-clean] killed $killed stale processes, cleared $cleared stale pid files"
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
_claws_cleanup_stale_workers
|
|
328
|
+
|
|
329
|
+
# ─── Step 1: Clone or update ───────────────────────────────────────────────
|
|
330
|
+
step "Fetching Claws source"
|
|
331
|
+
if [ -d "$INSTALL_DIR/.git" ]; then
|
|
332
|
+
info "updating existing clone at $INSTALL_DIR (ref: $CLAWS_REF)"
|
|
333
|
+
PREV_SHA="$(cd "$INSTALL_DIR" && git rev-parse HEAD 2>/dev/null || echo 'unknown')"
|
|
334
|
+
# v0.7.11: install.sh OWNS $INSTALL_DIR (~/.claws-src by default). It is the
|
|
335
|
+
# script's working dir, not a user dev clone. Hard-reset is the right default —
|
|
336
|
+
# auto-generated build artifacts (.metadata.json bundledAt, etc.) and any other
|
|
337
|
+
# transient cruft just get wiped. Install Just Works(TM) in any folder.
|
|
338
|
+
#
|
|
339
|
+
# Contributors who want to use $INSTALL_DIR as a dev clone should set CLAWS_DIR
|
|
340
|
+
# to a different path (e.g., CLAWS_DIR=/Users/me/dev/claws bash <(curl ...)).
|
|
341
|
+
# That keeps their work isolated from the install script's working dir.
|
|
342
|
+
#
|
|
343
|
+
# CLAWS_DEV_PROTECT=1 opt-in re-enables the v0.7.9 dirty-tree guard for users
|
|
344
|
+
# who want explicit safety on $INSTALL_DIR (rare — they should be using
|
|
345
|
+
# CLAWS_DIR= override instead).
|
|
346
|
+
if [ "${CLAWS_DEV_PROTECT:-0}" = "1" ]; then
|
|
347
|
+
_claws_dirty="$(cd "$INSTALL_DIR" && git status --porcelain 2>/dev/null | head -c 1)"
|
|
348
|
+
if [ -n "$_claws_dirty" ] && [ "${CLAWS_FORCE_RESET:-0}" != "1" ]; then
|
|
349
|
+
bad "uncommitted changes — refusing to git reset ($INSTALL_DIR has local edits; CLAWS_DEV_PROTECT=1 active)."
|
|
350
|
+
bad "Choose one:"
|
|
351
|
+
bad " 1. Commit or stash your changes, then re-run."
|
|
352
|
+
bad " 2. Re-run with CLAWS_FORCE_RESET=1 to discard local changes."
|
|
353
|
+
bad " 3. Unset CLAWS_DEV_PROTECT (default: hard-reset is safe)."
|
|
354
|
+
bad " 4. Use a separate clone path: CLAWS_DIR=/tmp/claws-fresh bash <(curl ...)"
|
|
355
|
+
exit 1
|
|
356
|
+
fi
|
|
357
|
+
fi
|
|
358
|
+
if ( cd "$INSTALL_DIR" && git fetch origin "$CLAWS_REF" 2>>"$CLAWS_LOG" \
|
|
359
|
+
&& git reset --hard --quiet "origin/$CLAWS_REF" ); then
|
|
360
|
+
NEW_SHA="$(cd "$INSTALL_DIR" && git rev-parse HEAD 2>/dev/null || echo 'unknown')"
|
|
361
|
+
if [ "$PREV_SHA" = "$NEW_SHA" ]; then
|
|
362
|
+
ok "already at origin/$CLAWS_REF (${NEW_SHA:0:7})"
|
|
363
|
+
else
|
|
364
|
+
ok "updated ${PREV_SHA:0:7} → ${NEW_SHA:0:7}"
|
|
365
|
+
fi
|
|
366
|
+
# GAP-3 (v0.7.8): if update.sh's --ff-only pull diverged and set GIT_PULL_OK=0,
|
|
367
|
+
# but our force-reset above succeeded, the source is now fresh — flip the flag back
|
|
368
|
+
# so CLAUDE.md re-injection (gated on GIT_PULL_OK) runs. Without this, users with a
|
|
369
|
+
# modified ~/.claws-src clone get fresh source but stale CLAUDE.md tool lists.
|
|
370
|
+
if [ "${GIT_PULL_OK:-1}" = "0" ]; then
|
|
371
|
+
info "force-reset succeeded after ff-only divergence — re-enabling CLAUDE.md injection"
|
|
372
|
+
GIT_PULL_OK=1
|
|
373
|
+
export GIT_PULL_OK
|
|
374
|
+
fi
|
|
375
|
+
git -C "$INSTALL_DIR" fsck --no-dangling 2>/dev/null || warn "clone integrity check failed — consider: rm -rf $INSTALL_DIR && re-run installer"
|
|
376
|
+
else
|
|
377
|
+
bad "failed to update $INSTALL_DIR (network error or corrupted clone)."
|
|
378
|
+
bad "Fix: rm -rf $INSTALL_DIR && re-run this installer."
|
|
379
|
+
exit 1
|
|
380
|
+
fi
|
|
381
|
+
elif [ -d "$INSTALL_DIR" ]; then
|
|
382
|
+
die "$INSTALL_DIR exists but is not a git clone — remove it or set CLAWS_DIR to a different path"
|
|
383
|
+
else
|
|
384
|
+
info "cloning $REPO → $INSTALL_DIR (ref: $CLAWS_REF, shallow)"
|
|
385
|
+
git clone --depth 1 --branch "$CLAWS_REF" "$REPO" "$INSTALL_DIR" 2>>"$CLAWS_LOG" \
|
|
386
|
+
|| git clone --depth 1 "$REPO" "$INSTALL_DIR" 2>>"$CLAWS_LOG"
|
|
387
|
+
ok "cloned to $INSTALL_DIR (${CLAWS_REF})"
|
|
388
|
+
git -C "$INSTALL_DIR" fsck --no-dangling 2>/dev/null || warn "clone integrity check failed — consider: rm -rf $INSTALL_DIR && re-run installer"
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
# ─── Step 2: Build + symlink extension ─────────────────────────────────────
|
|
392
|
+
step "Installing extension"
|
|
393
|
+
|
|
394
|
+
# 2a. Build
|
|
395
|
+
# Since Claws isn't on the VS Code Marketplace, the install script IS the
|
|
396
|
+
# release mechanism. Every run must produce a bundle that reflects whatever
|
|
397
|
+
# `extension/src/` is currently on main. Rebuild whenever:
|
|
398
|
+
# - the bundle is missing, OR
|
|
399
|
+
# - any `src/` file is newer than the bundle, OR
|
|
400
|
+
# - the current git HEAD doesn't match the SHA the bundle was built from,
|
|
401
|
+
# OR the user explicitly sets CLAWS_FORCE_BUILD=1.
|
|
402
|
+
# Build is ~seconds on a primed node_modules — rebuilding conservatively is
|
|
403
|
+
# cheaper than a stale bundle.
|
|
404
|
+
BUILD_OK=0
|
|
405
|
+
BUNDLE="$INSTALL_DIR/extension/dist/extension.js"
|
|
406
|
+
BUILD_SHA_FILE="$INSTALL_DIR/extension/dist/.build-sha"
|
|
407
|
+
CURRENT_SHA="$(cd "$INSTALL_DIR" && git rev-parse HEAD 2>/dev/null || echo 'nogit')"
|
|
408
|
+
LAST_BUILD_SHA="$(cat "$BUILD_SHA_FILE" 2>/dev/null || echo '')"
|
|
409
|
+
|
|
410
|
+
needs_build() {
|
|
411
|
+
[ "${CLAWS_FORCE_BUILD:-0}" = "1" ] && return 0
|
|
412
|
+
[ ! -f "$BUNDLE" ] && return 0
|
|
413
|
+
[ "$CURRENT_SHA" != "$LAST_BUILD_SHA" ] && return 0
|
|
414
|
+
# Any TS source newer than the bundle — catches local edits and any file
|
|
415
|
+
# changed by git pull, not just extension.ts.
|
|
416
|
+
if find "$INSTALL_DIR/extension/src" -type f \( -name '*.ts' -o -name '*.js' \) -newer "$BUNDLE" 2>/dev/null | grep -q .; then
|
|
417
|
+
return 0
|
|
418
|
+
fi
|
|
419
|
+
return 1
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if command -v npm &>/dev/null && [ -f "$INSTALL_DIR/extension/package.json" ]; then
|
|
423
|
+
# ── Canonical build path ─────────────────────────────────────────────────
|
|
424
|
+
# `npm run build` = esbuild (TS → dist/extension.js) + bundle-native.mjs
|
|
425
|
+
# (detects the user's VS Code Electron version, runs @electron/rebuild
|
|
426
|
+
# against node_modules/node-pty, and copies the ABI-correct binary into
|
|
427
|
+
# extension/native/node-pty/). That native/ directory is what the VSIX
|
|
428
|
+
# ships (via .vscodeignore's `!native/**`) and what claws-pty.ts loads
|
|
429
|
+
# at runtime. ONE build step owns both outputs — there is no second
|
|
430
|
+
# rebuild, and there is no fallback to a stale binary.
|
|
431
|
+
#
|
|
432
|
+
# If @electron/rebuild fails (missing Xcode CLT, node-gyp/Python issue,
|
|
433
|
+
# network failure while fetching Electron headers), the build fails
|
|
434
|
+
# loud and the installer aborts. A silently-broken binary would ship a
|
|
435
|
+
# VSIX that lands but can't load node-pty — the exact pipe-mode bug
|
|
436
|
+
# we're trying to kill.
|
|
437
|
+
NATIVE_PTY_BIN="$INSTALL_DIR/extension/native/node-pty/build/Release/pty.node"
|
|
438
|
+
|
|
439
|
+
# Pre-flight: on macOS, @electron/rebuild needs Xcode Command Line Tools
|
|
440
|
+
# to compile node-pty. Check up front so the error is obvious, not
|
|
441
|
+
# buried 200 lines into npm output.
|
|
442
|
+
if [ "$PLATFORM" = "Darwin" ] && ! xcode-select -p &>/dev/null; then
|
|
443
|
+
bad "Xcode Command Line Tools required to build node-pty."
|
|
444
|
+
bad "Fix: run 'xcode-select --install', wait for it to finish, then re-run this installer."
|
|
445
|
+
exit 1
|
|
446
|
+
fi
|
|
447
|
+
|
|
448
|
+
# Pre-detect Electron version from installed editors before building.
|
|
449
|
+
# bundle-native.mjs detects this too, but surfacing it here lets users
|
|
450
|
+
# see the ABI target before a potentially long compile.
|
|
451
|
+
_ELECTRON_PRE=""
|
|
452
|
+
case "$PLATFORM" in
|
|
453
|
+
Darwin)
|
|
454
|
+
for _plist in \
|
|
455
|
+
"/Applications/Visual Studio Code.app/Contents/Resources/app/package.json" \
|
|
456
|
+
"/Applications/Cursor.app/Contents/Resources/app/package.json" \
|
|
457
|
+
"/Applications/Windsurf.app/Contents/Resources/app/package.json"; do
|
|
458
|
+
if [ -f "$_plist" ]; then
|
|
459
|
+
_v=$(node -e "try{console.log(require('$_plist').electronVersion||'')}catch{}" 2>/dev/null || true)
|
|
460
|
+
if [ -n "$_v" ]; then
|
|
461
|
+
_label=$(basename "$(dirname "$(dirname "$(dirname "$_plist")")")" | sed 's/\.app//')
|
|
462
|
+
info "Detected Electron $_v from $_label"
|
|
463
|
+
_ELECTRON_PRE="$_v"
|
|
464
|
+
break
|
|
465
|
+
fi
|
|
466
|
+
fi
|
|
467
|
+
done
|
|
468
|
+
;;
|
|
469
|
+
Linux)
|
|
470
|
+
for _ep in /usr/share/code/electron /snap/code/current/usr/share/code/electron /opt/visual-studio-code/electron; do
|
|
471
|
+
if [ -x "$_ep" ]; then
|
|
472
|
+
_v=$("$_ep" --version 2>/dev/null | sed 's/^v//' || true)
|
|
473
|
+
if [ -n "$_v" ]; then
|
|
474
|
+
info "Detected Electron $_v from $_ep"
|
|
475
|
+
_ELECTRON_PRE="$_v"
|
|
476
|
+
break
|
|
477
|
+
fi
|
|
478
|
+
fi
|
|
479
|
+
done
|
|
480
|
+
;;
|
|
481
|
+
esac
|
|
482
|
+
[ -z "$_ELECTRON_PRE" ] && info "Electron version not pre-detected — bundle-native.mjs will detect at build time"
|
|
483
|
+
|
|
484
|
+
needs_rebuild_native=0
|
|
485
|
+
[ ! -f "$NATIVE_PTY_BIN" ] && needs_rebuild_native=1
|
|
486
|
+
[ "${CLAWS_FORCE_REBUILD_NPTY:-0}" = "1" ] && needs_rebuild_native=1
|
|
487
|
+
if needs_build; then needs_rebuild_native=1; fi
|
|
488
|
+
|
|
489
|
+
# Electron-ABI drift detection (v0.7.3). If the user updated VS Code/Cursor/
|
|
490
|
+
# Windsurf to a newer Electron version since the last build, the bundled
|
|
491
|
+
# pty.node is now ABI-mismatched and the extension will silently fall into
|
|
492
|
+
# pipe-mode. Read native/.metadata.json's electronVersion (written by
|
|
493
|
+
# bundle-native.mjs at build time) and compare with the currently-installed
|
|
494
|
+
# editor's Electron version. If they differ, force a rebuild.
|
|
495
|
+
if [ -f "$NATIVE_PTY_BIN" ] && [ -f "$INSTALL_DIR/extension/native/.metadata.json" ] && [ "$needs_rebuild_native" = "0" ]; then
|
|
496
|
+
_claws_last_elec=$(node -e "try{console.log(require('$INSTALL_DIR/extension/native/.metadata.json').electronVersion||'')}catch(e){}" 2>/dev/null || echo "")
|
|
497
|
+
_claws_curr_elec=""
|
|
498
|
+
if [ "$PLATFORM" = "Darwin" ]; then
|
|
499
|
+
# M-22: build candidate list with TERM_PROGRAM-matching editor first so the
|
|
500
|
+
# user's daily-driver wins the ABI check instead of the hardcoded VS Code path.
|
|
501
|
+
_tp="${TERM_PROGRAM:-}"
|
|
502
|
+
# F2: use bash array — avoids eval footgun while keeping TERM_PROGRAM ordering.
|
|
503
|
+
case "$_tp" in
|
|
504
|
+
cursor) _claws_darwin_apps=('/Applications/Cursor.app' '/Applications/Visual Studio Code.app' '/Applications/Visual Studio Code - Insiders.app' '/Applications/Windsurf.app') ;;
|
|
505
|
+
windsurf) _claws_darwin_apps=('/Applications/Windsurf.app' '/Applications/Visual Studio Code.app' '/Applications/Visual Studio Code - Insiders.app' '/Applications/Cursor.app') ;;
|
|
506
|
+
*) _claws_darwin_apps=('/Applications/Visual Studio Code.app' '/Applications/Visual Studio Code - Insiders.app' '/Applications/Cursor.app' '/Applications/Windsurf.app') ;;
|
|
507
|
+
esac
|
|
508
|
+
for _claws_app in "${_claws_darwin_apps[@]}"; do
|
|
509
|
+
_claws_plist="$_claws_app/Contents/Frameworks/Electron Framework.framework/Resources/Info.plist"
|
|
510
|
+
if [ -f "$_claws_plist" ]; then
|
|
511
|
+
_claws_curr_elec=$(plutil -extract CFBundleVersion raw "$_claws_plist" 2>/dev/null || true)
|
|
512
|
+
[ -n "$_claws_curr_elec" ] && break
|
|
513
|
+
fi
|
|
514
|
+
done
|
|
515
|
+
unset _tp _claws_darwin_apps
|
|
516
|
+
elif [ "$PLATFORM" = "Linux" ]; then
|
|
517
|
+
# M-22: prefer TERM_PROGRAM editor on Linux too.
|
|
518
|
+
# M-25: add Cursor + Windsurf Linux paths.
|
|
519
|
+
_tp="${TERM_PROGRAM:-}"
|
|
520
|
+
case "$_tp" in
|
|
521
|
+
cursor)
|
|
522
|
+
_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"
|
|
523
|
+
;;
|
|
524
|
+
windsurf)
|
|
525
|
+
_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"
|
|
526
|
+
;;
|
|
527
|
+
*)
|
|
528
|
+
_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"
|
|
529
|
+
;;
|
|
530
|
+
esac
|
|
531
|
+
for _claws_ep in $_claws_linux_eps; do
|
|
532
|
+
if [ -x "$_claws_ep" ]; then
|
|
533
|
+
_claws_curr_elec=$("$_claws_ep" --version 2>/dev/null | sed 's/^v//' | head -1)
|
|
534
|
+
[ -n "$_claws_curr_elec" ] && break
|
|
535
|
+
fi
|
|
536
|
+
done
|
|
537
|
+
unset _tp _claws_linux_eps
|
|
538
|
+
fi
|
|
539
|
+
# M-23: warn when detection returns empty — don't silently skip drift check.
|
|
540
|
+
if [ -z "$_claws_curr_elec" ] && [ -n "$_claws_last_elec" ]; then
|
|
541
|
+
warn "Could not detect the current VS Code/Cursor/Windsurf Electron version."
|
|
542
|
+
warn "Set CLAWS_ELECTRON_VERSION=<version> to specify it explicitly."
|
|
543
|
+
warn "(run: plutil -extract CFBundleVersion raw '/Applications/Cursor.app/Contents/Frameworks/Electron Framework.framework/Resources/Info.plist')"
|
|
544
|
+
fi
|
|
545
|
+
if [ -n "$_claws_curr_elec" ] && [ -n "$_claws_last_elec" ] && [ "$_claws_curr_elec" != "$_claws_last_elec" ]; then
|
|
546
|
+
info "Electron version changed since last build ($_claws_last_elec → $_claws_curr_elec) — forcing pty.node rebuild"
|
|
547
|
+
info "without this rebuild, the extension would silently fall into pipe-mode"
|
|
548
|
+
needs_rebuild_native=1
|
|
549
|
+
fi
|
|
550
|
+
unset _claws_last_elec _claws_curr_elec _claws_app _claws_plist _claws_ep
|
|
551
|
+
fi
|
|
552
|
+
|
|
553
|
+
if [ "$needs_rebuild_native" = "1" ]; then
|
|
554
|
+
if [ ! -f "$NATIVE_PTY_BIN" ]; then
|
|
555
|
+
info "building extension + native node-pty (binary missing)"
|
|
556
|
+
elif [ "$CURRENT_SHA" != "$LAST_BUILD_SHA" ] && [ -n "$LAST_BUILD_SHA" ]; then
|
|
557
|
+
info "rebuilding extension (git HEAD ${LAST_BUILD_SHA:0:7} → ${CURRENT_SHA:0:7})"
|
|
558
|
+
else
|
|
559
|
+
info "building extension bundle + rebuilding node-pty for current Electron"
|
|
560
|
+
fi
|
|
561
|
+
|
|
562
|
+
# Network pre-check: @electron/rebuild fetches Electron headers from GitHub.
|
|
563
|
+
# Fail fast on air-gapped machines before a 3-minute build that will hang.
|
|
564
|
+
info "checking network connectivity for Electron headers fetch..."
|
|
565
|
+
if curl --silent --head --max-time 5 "https://github.com" >/dev/null 2>&1 \
|
|
566
|
+
|| wget --spider --quiet --timeout=5 "https://github.com" >/dev/null 2>&1; then
|
|
567
|
+
info "network reachable — Electron headers fetch should succeed"
|
|
568
|
+
else
|
|
569
|
+
warn "network unreachable (GitHub) — @electron/rebuild may fail fetching Electron headers"
|
|
570
|
+
warn "If you are on an air-gapped machine, set CLAWS_ELECTRON_VERSION=<version> and ensure"
|
|
571
|
+
warn "headers are available at a local mirror, or use CLAWS_FORCE_REBUILD_NPTY=0 to skip."
|
|
572
|
+
fi
|
|
573
|
+
|
|
574
|
+
# Run with visible output — the user needs to see @electron/rebuild
|
|
575
|
+
# progress and any compile errors. --silent here hides the exact
|
|
576
|
+
# diagnostic that tells them what to fix.
|
|
577
|
+
if ( cd "$INSTALL_DIR/extension" \
|
|
578
|
+
&& npm install --no-audit --no-fund --loglevel=error \
|
|
579
|
+
&& { node -e "require.resolve('@electron/rebuild')" >/dev/null 2>&1 \
|
|
580
|
+
|| warn "@electron/rebuild not found after npm install — pty.node build will likely fail"; true; } \
|
|
581
|
+
&& npm run build ); then
|
|
582
|
+
echo "$CURRENT_SHA" > "$BUILD_SHA_FILE" 2>/dev/null || true
|
|
583
|
+
# F-11: write .electron-abi so fix.sh skips unnecessary rebuilds on next run.
|
|
584
|
+
mkdir -p "$INSTALL_DIR/extension/dist" 2>/dev/null || true
|
|
585
|
+
node --no-deprecation -e "try{const m=require('$INSTALL_DIR/extension/native/.metadata.json');if(m.electronVersion){require('fs').writeFileSync('$INSTALL_DIR/extension/dist/.electron-abi',m.electronVersion)}}catch(e){}" 2>/dev/null || true
|
|
586
|
+
BUILD_OK=1
|
|
587
|
+
else
|
|
588
|
+
bad "extension build failed — see $CLAWS_LOG for the full compile log."
|
|
589
|
+
# Scan the log to give a targeted hint
|
|
590
|
+
if grep -qi "xcode\|xcrun\|CLT\|command line tools" "$CLAWS_LOG" 2>/dev/null; then
|
|
591
|
+
bad "Likely cause: Xcode Command Line Tools missing or incomplete — run: xcode-select --install"
|
|
592
|
+
elif grep -qi "electron.*header\|ENOTFOUND\|ETIMEDOUT\|network\|fetch" "$CLAWS_LOG" 2>/dev/null; then
|
|
593
|
+
bad "Likely cause: network error fetching Electron headers — check internet connectivity and proxy settings"
|
|
594
|
+
elif grep -qi "python\|gyp" "$CLAWS_LOG" 2>/dev/null; then
|
|
595
|
+
bad "Likely cause: Python 3 or node-gyp issue — run: brew install python3 OR sudo apt install python3"
|
|
596
|
+
else
|
|
597
|
+
bad "Common causes: Xcode CLT missing, Python 3 missing, or network error during @electron/rebuild's Electron headers fetch"
|
|
598
|
+
fi
|
|
599
|
+
bad "After fixing, re-run: bash <(curl -fsSL https://raw.githubusercontent.com/neunaha/claws/main/scripts/install.sh)"
|
|
600
|
+
exit 1
|
|
601
|
+
fi
|
|
602
|
+
else
|
|
603
|
+
BUILD_OK=1
|
|
604
|
+
ok "extension bundle up to date (SHA ${CURRENT_SHA:0:7}, $(wc -c < "$BUNDLE" | tr -d ' ') bytes)"
|
|
605
|
+
fi
|
|
606
|
+
|
|
607
|
+
# Hard verification — the VSIX in step 2c packages from extension/, and
|
|
608
|
+
# without this binary present, wrapped terminals fall back to pipe-mode.
|
|
609
|
+
# Refusing to continue is the correct behavior; a silent pipe-mode
|
|
610
|
+
# install is worse than an explicit failure the user can fix.
|
|
611
|
+
if [ ! -f "$NATIVE_PTY_BIN" ]; then
|
|
612
|
+
bad "native/node-pty/build/Release/pty.node missing after build."
|
|
613
|
+
bad "This means @electron/rebuild failed silently or bundle-native.mjs didn't copy the output."
|
|
614
|
+
bad "Diagnostic: ( cd $INSTALL_DIR/extension && npm run bundle-native )"
|
|
615
|
+
bad "See $CLAWS_LOG for the bundle-native output."
|
|
616
|
+
exit 1
|
|
617
|
+
fi
|
|
618
|
+
NATIVE_PTY_SIZE=$(wc -c < "$NATIVE_PTY_BIN" | tr -d ' ')
|
|
619
|
+
NATIVE_PTY_ELECTRON=$(node -e "try{console.log(require('$INSTALL_DIR/extension/native/.metadata.json').electronVersion||'?')}catch(e){console.log('?')}" 2>/dev/null || echo '?')
|
|
620
|
+
ok "native node-pty ready (${NATIVE_PTY_SIZE} bytes, Electron $NATIVE_PTY_ELECTRON) — VSIX will ship this binary"
|
|
621
|
+
if command -v file &>/dev/null; then
|
|
622
|
+
# M-34: when bash runs under Rosetta 2 (x64 shell on Apple Silicon), uname -m returns
|
|
623
|
+
# x86_64 but bundle-native.mjs (M-05) builds for arm64. Detect Rosetta via sysctl so
|
|
624
|
+
# the expected arch is arm64, not x86_64 — prevents a spurious arch mismatch warning.
|
|
625
|
+
# Linux x86_64 false-positive: `uname -m` returns `x86_64` (underscore),
|
|
626
|
+
# but `file(1)` describes ELF binaries as `x86-64` (hyphen). Match both
|
|
627
|
+
# spellings so legitimate x86_64 bundles don't trigger the warning.
|
|
628
|
+
# Audit 1 finding H-1.
|
|
629
|
+
_claws_expected_arch="$(uname -m)"
|
|
630
|
+
if [ "$_claws_expected_arch" = "x86_64" ] && [ "$(uname -s)" = "Darwin" ]; then
|
|
631
|
+
_claws_rosetta=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo "0")
|
|
632
|
+
[ "$_claws_rosetta" = "1" ] && _claws_expected_arch="arm64"
|
|
633
|
+
fi
|
|
634
|
+
_claws_arch_alt="$(echo "$_claws_expected_arch" | sed 's/_/-/g')"
|
|
635
|
+
file "$NATIVE_PTY_BIN" 2>/dev/null | grep -qiE "$_claws_expected_arch|$_claws_arch_alt" \
|
|
636
|
+
|| warn "pty.node architecture may not match current machine ($(uname -m) → expected $_claws_expected_arch) — check bundle-native.mjs output in $CLAWS_LOG"
|
|
637
|
+
unset _claws_arch_alt _claws_rosetta _claws_expected_arch
|
|
638
|
+
fi
|
|
639
|
+
|
|
640
|
+
# R3.7: Check if other installed editors use a different Electron version.
|
|
641
|
+
# The VSIX ships ONE binary built for one Electron ABI. If Cursor/Windsurf
|
|
642
|
+
# ship a different Electron than VS Code, the binary may load in pipe-mode
|
|
643
|
+
# for those editors. Warn so the user knows and can rebuild with CLAWS_ELECTRON_VERSION.
|
|
644
|
+
if [ -n "$NATIVE_PTY_ELECTRON" ] && [ "$NATIVE_PTY_ELECTRON" != "?" ]; then
|
|
645
|
+
_check_editor_electron() {
|
|
646
|
+
local app_label="$1"
|
|
647
|
+
local pkg_json="$2"
|
|
648
|
+
if [ -f "$pkg_json" ]; then
|
|
649
|
+
local editor_ver
|
|
650
|
+
editor_ver=$(node -e "try{console.log(require('$pkg_json').electronVersion||'')}catch{}" 2>/dev/null || true)
|
|
651
|
+
if [ -n "$editor_ver" ] && [ "$editor_ver" != "$NATIVE_PTY_ELECTRON" ]; then
|
|
652
|
+
warn "$app_label uses Electron $editor_ver but pty.node was built for Electron $NATIVE_PTY_ELECTRON"
|
|
653
|
+
warn " node-pty will load in pipe-mode in $app_label — rebuild with: CLAWS_ELECTRON_VERSION=$editor_ver bash <(curl -fsSL https://raw.githubusercontent.com/neunaha/claws/main/scripts/install.sh)"
|
|
654
|
+
fi
|
|
655
|
+
fi
|
|
656
|
+
}
|
|
657
|
+
case "$PLATFORM" in
|
|
658
|
+
Darwin)
|
|
659
|
+
_check_editor_electron "Cursor" "/Applications/Cursor.app/Contents/Resources/app/package.json"
|
|
660
|
+
_check_editor_electron "Windsurf" "/Applications/Windsurf.app/Contents/Resources/app/package.json"
|
|
661
|
+
_check_editor_electron "VS Code Insiders" "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/package.json"
|
|
662
|
+
;;
|
|
663
|
+
esac
|
|
664
|
+
unset -f _check_editor_electron
|
|
665
|
+
fi
|
|
666
|
+
else
|
|
667
|
+
bad "npm or extension/package.json missing — cannot build extension."
|
|
668
|
+
bad "Install Node.js 18+ and re-run: bash <(curl -fsSL https://raw.githubusercontent.com/neunaha/claws/main/scripts/install.sh)"
|
|
669
|
+
exit 1
|
|
670
|
+
fi
|
|
671
|
+
|
|
672
|
+
# 2b. Read extension version from manifest so the symlink matches.
|
|
673
|
+
# EXPECTED_MIN_VERSION is hardcoded at script-release time. EXT_VERSION is
|
|
674
|
+
# read from the clone at runtime. If the clone is behind EXPECTED_MIN_VERSION,
|
|
675
|
+
# the working tree is stale and the installer aborts — that was the v0.5.1 bug
|
|
676
|
+
# where users saw "v0.4.0 — installed" because their ~/.claws-src/ was stale.
|
|
677
|
+
EXPECTED_MIN_VERSION=$(node -e "try{console.log(require('$INSTALL_DIR/extension/package.json').version)}catch(e){console.log('0.8.0')}" 2>/dev/null || echo "0.8.0")
|
|
678
|
+
EXT_VERSION=$(node -e "try{console.log(require('$INSTALL_DIR/extension/package.json').version)}catch(e){console.log('0.0.0')}" 2>/dev/null || echo "0.0.0")
|
|
679
|
+
|
|
680
|
+
# Flag stale clones loudly so users don't silently run on an old version.
|
|
681
|
+
if [ "$EXT_VERSION" != "$EXPECTED_MIN_VERSION" ]; then
|
|
682
|
+
if ! node -e "
|
|
683
|
+
const [a,b]=[process.argv[1],process.argv[2]].map(s=>s.split('.').map(Number));
|
|
684
|
+
for (let i=0;i<3;i++){ if((a[i]||0)<(b[i]||0)) process.exit(1); if((a[i]||0)>(b[i]||0)) process.exit(0); }
|
|
685
|
+
process.exit(0);
|
|
686
|
+
" "$EXT_VERSION" "$EXPECTED_MIN_VERSION" 2>/dev/null; then
|
|
687
|
+
bad "extension version $EXT_VERSION < expected $EXPECTED_MIN_VERSION — clone is stale."
|
|
688
|
+
bad "Fix: rm -rf $INSTALL_DIR && re-run this installer."
|
|
689
|
+
exit 1
|
|
690
|
+
fi
|
|
691
|
+
fi
|
|
692
|
+
|
|
693
|
+
# 2c. Install the extension into every detected editor.
|
|
694
|
+
#
|
|
695
|
+
# Strategy (v0.5.3+):
|
|
696
|
+
# 1. VSIX install via `code --install-extension` — the proper way.
|
|
697
|
+
# VS Code registers the extension in its extensions.json, extracts
|
|
698
|
+
# to the extensions dir, and (if a window is open) shows a
|
|
699
|
+
# "Reload to activate?" toast automatically. For a closed VS Code,
|
|
700
|
+
# next window-open loads it with zero clicks.
|
|
701
|
+
# 2. Symlink fallback — used when vsce packaging or the editor CLI
|
|
702
|
+
# isn't available (network error, no `code` binary). Also what
|
|
703
|
+
# CLAWS_DEV_SYMLINK=1 forces.
|
|
704
|
+
#
|
|
705
|
+
# Why VSIX works now when it didn't before: Phase 2 moved node-pty out
|
|
706
|
+
# of node_modules/ into native/node-pty/. .vscodeignore excludes
|
|
707
|
+
# node_modules/** but un-ignores !native/**, so the packaged VSIX
|
|
708
|
+
# contains the ABI-correct binary at the exact path the runtime loader
|
|
709
|
+
# expects (<ext>/native/node-pty/).
|
|
710
|
+
|
|
711
|
+
INSTALLED_EDITORS=()
|
|
712
|
+
VSIX_INSTALL_METHOD="" # "vsix" or "symlink" — reported in the banner
|
|
713
|
+
|
|
714
|
+
# Zed uses a proprietary extension format (.zedbundle) — not VSIX-compatible.
|
|
715
|
+
# Claws extension is VS Code-only. If Zed is detected, inform the user.
|
|
716
|
+
if command -v zed &>/dev/null; then
|
|
717
|
+
info "Zed editor detected — Claws extension is VS Code/Cursor/Windsurf-only (VSIX format). Zed is not supported."
|
|
718
|
+
fi
|
|
719
|
+
|
|
720
|
+
# Editor CLI discovery. Checks $PATH first, then macOS app bundle paths.
|
|
721
|
+
_find_editor_cli() {
|
|
722
|
+
local label="$1"
|
|
723
|
+
# In $PATH?
|
|
724
|
+
if command -v "$label" &>/dev/null; then
|
|
725
|
+
command -v "$label"
|
|
726
|
+
return 0
|
|
727
|
+
fi
|
|
728
|
+
# Bundled in an app (macOS). Windows/Linux paths fall through.
|
|
729
|
+
case "$PLATFORM" in
|
|
730
|
+
Darwin)
|
|
731
|
+
case "$label" in
|
|
732
|
+
code) [ -x "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" ] && echo "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" && return 0 ;;
|
|
733
|
+
code-insiders) [ -x "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders" ] && echo "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders" && return 0 ;;
|
|
734
|
+
cursor) [ -x "/Applications/Cursor.app/Contents/Resources/app/bin/cursor" ] && echo "/Applications/Cursor.app/Contents/Resources/app/bin/cursor" && return 0 ;;
|
|
735
|
+
windsurf) [ -x "/Applications/Windsurf.app/Contents/Resources/app/bin/windsurf" ] && echo "/Applications/Windsurf.app/Contents/Resources/app/bin/windsurf" && return 0 ;;
|
|
736
|
+
esac
|
|
737
|
+
;;
|
|
738
|
+
esac
|
|
739
|
+
return 1
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
# Fallback: symlink the source clone into the editor's extensions dir.
|
|
743
|
+
_link_extension_into() {
|
|
744
|
+
local label="$1"
|
|
745
|
+
local ext_dir="$2"
|
|
746
|
+
[ ! -d "$ext_dir" ] && return 1
|
|
747
|
+
local link="$ext_dir/neunaha.claws-$EXT_VERSION"
|
|
748
|
+
rm -rf "$ext_dir"/neunaha.claws-* 2>/dev/null \
|
|
749
|
+
|| sudo rm -rf "$ext_dir"/neunaha.claws-* 2>/dev/null \
|
|
750
|
+
|| true
|
|
751
|
+
if ln -sf "$INSTALL_DIR/extension" "$link" 2>/dev/null \
|
|
752
|
+
|| sudo ln -sf "$INSTALL_DIR/extension" "$link" 2>/dev/null; then
|
|
753
|
+
ok "linked Claws into $label ($link)"
|
|
754
|
+
INSTALLED_EDITORS+=("$label")
|
|
755
|
+
return 0
|
|
756
|
+
fi
|
|
757
|
+
bad "could not link Claws into $label"
|
|
758
|
+
return 1
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
# VSIX install path: package once, install into every editor CLI we find.
|
|
762
|
+
_install_via_vsix() {
|
|
763
|
+
local VSIX_PATH="/tmp/claws-$EXT_VERSION.vsix"
|
|
764
|
+
info "packaging VSIX for VS Code install"
|
|
765
|
+
|
|
766
|
+
# Sanity-check publisher field — vsce fails silently if it's missing.
|
|
767
|
+
local pub
|
|
768
|
+
pub=$(node -e "try{console.log(require('$INSTALL_DIR/extension/package.json').publisher||'')}catch(e){console.log('')}" 2>/dev/null || echo "")
|
|
769
|
+
if [ -z "$pub" ]; then
|
|
770
|
+
warn "extension/package.json missing 'publisher' field — vsce will fail"
|
|
771
|
+
return 1
|
|
772
|
+
fi
|
|
773
|
+
|
|
774
|
+
# Package. vsce reads package.json + .vscodeignore to produce the VSIX.
|
|
775
|
+
if ! ( cd "$INSTALL_DIR/extension" \
|
|
776
|
+
&& rm -f "$VSIX_PATH" 2>/dev/null \
|
|
777
|
+
&& npx --yes @vscode/vsce package --skip-license --no-git-tag-version --no-update-package-json --out "$VSIX_PATH" ) >>"$CLAWS_LOG" 2>&1; then
|
|
778
|
+
warn "vsce package failed — see $CLAWS_LOG for details"
|
|
779
|
+
info "diagnostic: cd $INSTALL_DIR/extension && npx @vscode/vsce package --out $VSIX_PATH"
|
|
780
|
+
return 1
|
|
781
|
+
fi
|
|
782
|
+
|
|
783
|
+
if [ ! -f "$VSIX_PATH" ]; then
|
|
784
|
+
warn "vsce reported success but VSIX missing — falling back to symlink"
|
|
785
|
+
return 1
|
|
786
|
+
fi
|
|
787
|
+
|
|
788
|
+
local vsix_size; vsix_size=$(wc -c < "$VSIX_PATH" | tr -d ' ')
|
|
789
|
+
if [ "$vsix_size" -lt 50000 ] 2>/dev/null; then
|
|
790
|
+
warn "VSIX suspiciously small (${vsix_size} bytes < 50KB) — native binary may be missing from package"
|
|
791
|
+
warn "Check .vscodeignore includes !native/** and re-run installer"
|
|
792
|
+
return 1
|
|
793
|
+
fi
|
|
794
|
+
ok "packaged $VSIX_PATH ($(numfmt --to=iec-i --suffix=B "$vsix_size" 2>/dev/null || echo "${vsix_size} bytes"))"
|
|
795
|
+
|
|
796
|
+
# Install into every detected editor CLI.
|
|
797
|
+
local any_installed=0
|
|
798
|
+
for label in code code-insiders cursor windsurf; do
|
|
799
|
+
local cli
|
|
800
|
+
cli="$(_find_editor_cli "$label")" || continue
|
|
801
|
+
info "installing into $label via $cli"
|
|
802
|
+
|
|
803
|
+
# Try normal install, then sudo on permission failure (R4.7/B7).
|
|
804
|
+
local install_ok=0
|
|
805
|
+
if "$cli" --install-extension "$VSIX_PATH" --force >/dev/null 2>&1; then
|
|
806
|
+
install_ok=1
|
|
807
|
+
elif sudo "$cli" --install-extension "$VSIX_PATH" --force >/dev/null 2>&1; then
|
|
808
|
+
info " installed via sudo (extensions dir required elevated permissions)"
|
|
809
|
+
install_ok=1
|
|
810
|
+
fi
|
|
811
|
+
|
|
812
|
+
if [ "$install_ok" = "1" ]; then
|
|
813
|
+
# R4.10: Verify the extension actually landed in the extensions directory
|
|
814
|
+
# rather than trusting the exit code alone (VS Code exit codes are undocumented).
|
|
815
|
+
local ext_dir="$HOME/.vscode/extensions"
|
|
816
|
+
case "$label" in
|
|
817
|
+
code-insiders) ext_dir="$HOME/.vscode-insiders/extensions" ;;
|
|
818
|
+
cursor) ext_dir="$HOME/.cursor/extensions" ;;
|
|
819
|
+
windsurf) ext_dir="$HOME/.windsurf/extensions" ;;
|
|
820
|
+
esac
|
|
821
|
+
if ls "$ext_dir"/neunaha.claws-* 2>/dev/null | grep -q .; then
|
|
822
|
+
ok "Claws extension installed in $label (verified in $ext_dir)"
|
|
823
|
+
# Clean stale older-version directories. VS Code itself usually does
|
|
824
|
+
# this on VSIX install, but if a prior install hit a lock or used a
|
|
825
|
+
# different CLI, old <publisher>.<name>-X.Y.Z dirs can linger and
|
|
826
|
+
# confuse VS Code's extension picker. Keep only the just-installed
|
|
827
|
+
# version (matches EXT_VERSION).
|
|
828
|
+
# M-06: gate cleanup on kept_dir existing first. VS Code extracts VSIX
|
|
829
|
+
# asynchronously — if kept_dir hasn't appeared yet, the safety guard
|
|
830
|
+
# ([ "$stale" = "$kept_dir" ]) never matches and the loop would delete
|
|
831
|
+
# every installed version. Skip and warn instead of destroying all installs.
|
|
832
|
+
local kept_dir="$ext_dir/neunaha.claws-$EXT_VERSION"
|
|
833
|
+
# FINDING-B-4: VS Code extracts VSIX asynchronously — poll up to 1s
|
|
834
|
+
# (5×200ms) for kept_dir to appear before deciding it's absent.
|
|
835
|
+
for _poll in 1 2 3 4 5; do
|
|
836
|
+
[ -d "$kept_dir" ] && break
|
|
837
|
+
sleep 0.2
|
|
838
|
+
done
|
|
839
|
+
unset _poll
|
|
840
|
+
if [ -d "$kept_dir" ]; then
|
|
841
|
+
for stale in "$ext_dir"/neunaha.claws-*; do
|
|
842
|
+
[ -d "$stale" ] || continue
|
|
843
|
+
[ "$stale" = "$kept_dir" ] && continue
|
|
844
|
+
if rm -rf "$stale" 2>/dev/null || sudo rm -rf "$stale" 2>/dev/null; then
|
|
845
|
+
info " removed stale install $(basename "$stale")"
|
|
846
|
+
fi
|
|
847
|
+
done
|
|
848
|
+
else
|
|
849
|
+
warn " kept_dir not yet present ($kept_dir) — skipping stale cleanup to avoid removing all versions"
|
|
850
|
+
warn " (VS Code may still be extracting the VSIX — stale dirs will be cleaned on next install)"
|
|
851
|
+
fi
|
|
852
|
+
else
|
|
853
|
+
ok "Claws extension installed in $label (via VSIX — extensions dir not found for verification)"
|
|
854
|
+
fi
|
|
855
|
+
INSTALLED_EDITORS+=("$label")
|
|
856
|
+
any_installed=1
|
|
857
|
+
else
|
|
858
|
+
# Non-zero exit and sudo also failed — likely a running window holds an
|
|
859
|
+
# exclusive lock on the .node binary. The VSIX is staged in /tmp; VS Code
|
|
860
|
+
# will pick it up on next Reload Window.
|
|
861
|
+
warn "$label --install-extension refused (likely a running window holds the current version)"
|
|
862
|
+
info " this is fine — the new VSIX is staged; Reload Window activates it"
|
|
863
|
+
# R4.10: Still verify — the extensions dir may already have the new version
|
|
864
|
+
local ext_dir2="$HOME/.vscode/extensions"
|
|
865
|
+
case "$label" in
|
|
866
|
+
code-insiders) ext_dir2="$HOME/.vscode-insiders/extensions" ;;
|
|
867
|
+
cursor) ext_dir2="$HOME/.cursor/extensions" ;;
|
|
868
|
+
windsurf) ext_dir2="$HOME/.windsurf/extensions" ;;
|
|
869
|
+
esac
|
|
870
|
+
if ls "$ext_dir2"/neunaha.claws-* 2>/dev/null | grep -q .; then
|
|
871
|
+
INSTALLED_EDITORS+=("$label (pending reload)")
|
|
872
|
+
any_installed=1
|
|
873
|
+
else
|
|
874
|
+
warn "$label extensions dir has no neunaha.claws-* entry — install may have failed"
|
|
875
|
+
fi
|
|
876
|
+
fi
|
|
877
|
+
done
|
|
878
|
+
|
|
879
|
+
[ "$any_installed" = "1" ] && return 0
|
|
880
|
+
return 1
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
# Choose install path.
|
|
884
|
+
if [ "${CLAWS_DEV_SYMLINK:-0}" = "1" ]; then
|
|
885
|
+
info "CLAWS_DEV_SYMLINK=1 — using symlink install (live-edit dev workflow)"
|
|
886
|
+
VSIX_INSTALL_METHOD="symlink"
|
|
887
|
+
elif [ "${BUILD_OK:-0}" = "1" ] && command -v npx &>/dev/null; then
|
|
888
|
+
if _install_via_vsix; then
|
|
889
|
+
VSIX_INSTALL_METHOD="vsix"
|
|
890
|
+
else
|
|
891
|
+
warn "VSIX install failed — falling back to symlink"
|
|
892
|
+
VSIX_INSTALL_METHOD="symlink"
|
|
893
|
+
fi
|
|
894
|
+
else
|
|
895
|
+
info "no npx or build failed — using symlink install"
|
|
896
|
+
VSIX_INSTALL_METHOD="symlink"
|
|
897
|
+
fi
|
|
898
|
+
|
|
899
|
+
# Symlink fallback — only runs if VSIX path didn't cover us.
|
|
900
|
+
if [ "$VSIX_INSTALL_METHOD" = "symlink" ] || [ "${#INSTALLED_EDITORS[@]}" -eq 0 ]; then
|
|
901
|
+
for pair in \
|
|
902
|
+
"vscode:$HOME/.vscode/extensions" \
|
|
903
|
+
"vscode-insiders:$HOME/.vscode-insiders/extensions" \
|
|
904
|
+
"cursor:$HOME/.cursor/extensions" \
|
|
905
|
+
"windsurf:$HOME/.windsurf/extensions"; do
|
|
906
|
+
label="${pair%%:*}"
|
|
907
|
+
dir="${pair#*:}"
|
|
908
|
+
[ -d "$dir" ] && _link_extension_into "$label" "$dir" || true
|
|
909
|
+
done
|
|
910
|
+
|
|
911
|
+
# If no editor dir existed at all, create the default one and link there.
|
|
912
|
+
if [ "${#INSTALLED_EDITORS[@]}" -eq 0 ] && [ "$CLAWS_EDITOR" != "skip" ]; then
|
|
913
|
+
mkdir -p "$HOME/.vscode/extensions" 2>/dev/null
|
|
914
|
+
_link_extension_into "vscode (new)" "$HOME/.vscode/extensions" || true
|
|
915
|
+
fi
|
|
916
|
+
fi
|
|
917
|
+
|
|
918
|
+
if [ "${#INSTALLED_EDITORS[@]}" -eq 0 ]; then
|
|
919
|
+
warn "extension not installed in any editor — check CLAWS_EDITOR env var"
|
|
920
|
+
fi
|
|
921
|
+
|
|
922
|
+
# ─── Step 3: Script permissions ────────────────────────────────────────────
|
|
923
|
+
step "Setting file permissions"
|
|
924
|
+
chmod +x "$INSTALL_DIR"/scripts/*.sh 2>/dev/null || true
|
|
925
|
+
chmod +x "$INSTALL_DIR/mcp_server.js" 2>/dev/null || true
|
|
926
|
+
ok "scripts executable"
|
|
927
|
+
|
|
928
|
+
# ─── Step 4: Runtime check ─────────────────────────────────────────────────
|
|
929
|
+
step "Runtime check"
|
|
930
|
+
# Verify Node.js is still reachable (a PATH change mid-install would break MCP)
|
|
931
|
+
if command -v node &>/dev/null; then
|
|
932
|
+
ok "Node.js reachable at $(node -e 'process.stdout.write(process.execPath)' 2>/dev/null) ($(node --version))"
|
|
933
|
+
else
|
|
934
|
+
bad "node not reachable in current PATH — MCP server and extension build will fail"
|
|
935
|
+
die "Install Node.js 18+ and re-run: bash <(curl -fsSL https://raw.githubusercontent.com/neunaha/claws/main/scripts/install.sh)"
|
|
936
|
+
fi
|
|
937
|
+
# Verify mcp_server.js is present in the clone before we try to copy it in step 5
|
|
938
|
+
if [ ! -f "$INSTALL_DIR/mcp_server.js" ]; then
|
|
939
|
+
bad "$INSTALL_DIR/mcp_server.js missing — clone may be incomplete"
|
|
940
|
+
die "Fix: rm -rf $INSTALL_DIR && re-run this installer"
|
|
941
|
+
fi
|
|
942
|
+
ok "runtime ready"
|
|
943
|
+
|
|
944
|
+
# ─── Step 5: MCP server (project-local primary, global opt-in) ─────────────
|
|
945
|
+
step "Configuring MCP server"
|
|
946
|
+
|
|
947
|
+
MCP_PATH="$INSTALL_DIR/mcp_server.js"
|
|
948
|
+
if [ "${CLAWS_SKIP_MCP:-0}" = "1" ]; then
|
|
949
|
+
warn "CLAWS_SKIP_MCP=1 — skipping MCP registration"
|
|
950
|
+
else
|
|
951
|
+
if [ "$PROJECT_INSTALL" = "1" ]; then
|
|
952
|
+
# FINDING-B-2: guard against dangling / loop / unexpected symlinks at .claws-bin
|
|
953
|
+
# before mkdir -p, which would silently create files at the symlink target.
|
|
954
|
+
if [ -L "$PROJECT_ROOT/.claws-bin" ]; then
|
|
955
|
+
[ -e "$PROJECT_ROOT/.claws-bin" ] \
|
|
956
|
+
&& warn ".claws-bin is a symlink → $(readlink "$PROJECT_ROOT/.claws-bin") — removing and replacing with directory" \
|
|
957
|
+
|| warn ".claws-bin is a dangling symlink — removing"
|
|
958
|
+
rm -f "$PROJECT_ROOT/.claws-bin"
|
|
959
|
+
fi
|
|
960
|
+
mkdir -p "$PROJECT_ROOT/.claws-bin"
|
|
961
|
+
# Fix #1 (v0.7.12): write package.json with {"type":"commonjs"} to override the parent
|
|
962
|
+
# project's ESM default. Without this, mcp_server.js and hooks/*.js crash with
|
|
963
|
+
# "require is not defined in ES module scope" in projects that have "type":"module"
|
|
964
|
+
# in their root package.json (Next.js, Vite, ESM-default tooling — ~50% of modern projects).
|
|
965
|
+
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"
|
|
966
|
+
if [ "$(realpath "$INSTALL_DIR" 2>/dev/null)" = "$(realpath "$PROJECT_ROOT" 2>/dev/null)" ]; then
|
|
967
|
+
# Dev mode: source repo IS the install target — use symlinks to eliminate drift.
|
|
968
|
+
ln -sf ../mcp_server.js "$INSTALL_DIR/.claws-bin/mcp_server.js"
|
|
969
|
+
ln -sf ../scripts/shell-hook.sh "$INSTALL_DIR/.claws-bin/shell-hook.sh"
|
|
970
|
+
ln -sf ../scripts/stream-events.js "$INSTALL_DIR/.claws-bin/stream-events.js"
|
|
971
|
+
ln -sf ../scripts/monitor-arm-watch.js "$INSTALL_DIR/.claws-bin/monitor-arm-watch.js"
|
|
972
|
+
rm -rf "$INSTALL_DIR/.claws-bin/hooks"
|
|
973
|
+
ln -sf ../scripts/hooks "$INSTALL_DIR/.claws-bin/hooks"
|
|
974
|
+
echo '[install] dev mode: symlinked .claws-bin → source'
|
|
975
|
+
else
|
|
976
|
+
cp "$INSTALL_DIR/mcp_server.js" "$PROJECT_ROOT/.claws-bin/mcp_server.js"
|
|
977
|
+
chmod +x "$PROJECT_ROOT/.claws-bin/mcp_server.js"
|
|
978
|
+
# Copy generated schema artifacts: mcp-tools.json (consumed by mcp_server.js
|
|
979
|
+
# at startup) + json/ (20 per-topic JSON Schemas) + types/ (TS .d.ts).
|
|
980
|
+
# External schema consumers (worker SDKs, validators, IDE hints) need the
|
|
981
|
+
# json/ and types/ files even though mcp_server.js itself only needs
|
|
982
|
+
# mcp-tools.json.
|
|
983
|
+
if [ -d "$INSTALL_DIR/schemas" ]; then
|
|
984
|
+
mkdir -p "$PROJECT_ROOT/.claws-bin/schemas/json" "$PROJECT_ROOT/.claws-bin/schemas/types"
|
|
985
|
+
cp "$INSTALL_DIR/schemas/mcp-tools.json" "$PROJECT_ROOT/.claws-bin/schemas/" 2>/dev/null || true
|
|
986
|
+
# P3-1: deploy client-types.d.ts for typed SDK consumers
|
|
987
|
+
cp "$INSTALL_DIR/schemas/client-types.d.ts" "$PROJECT_ROOT/.claws-bin/schemas/" 2>/dev/null || true
|
|
988
|
+
cp "$INSTALL_DIR"/schemas/json/*.json "$PROJECT_ROOT/.claws-bin/schemas/json/" 2>/dev/null || true
|
|
989
|
+
cp "$INSTALL_DIR"/schemas/types/*.d.ts "$PROJECT_ROOT/.claws-bin/schemas/types/" 2>/dev/null || true
|
|
990
|
+
fi
|
|
991
|
+
# Copy Claws SDK (v0.7.0+) for typed publish helpers in worker scripts
|
|
992
|
+
if [ -f "$INSTALL_DIR/claws-sdk.js" ]; then
|
|
993
|
+
cp "$INSTALL_DIR/claws-sdk.js" "$PROJECT_ROOT/.claws-bin/claws-sdk.js"
|
|
994
|
+
chmod +x "$PROJECT_ROOT/.claws-bin/claws-sdk.js" 2>/dev/null || true
|
|
995
|
+
else
|
|
996
|
+
warn "claws-sdk.js not found in $INSTALL_DIR — SDK helpers unavailable (P3-3)"
|
|
997
|
+
fi
|
|
998
|
+
cp "$INSTALL_DIR/scripts/shell-hook.sh" "$PROJECT_ROOT/.claws-bin/shell-hook.sh"
|
|
999
|
+
# Copy event-streaming sidecar (v0.6.2+) so Bash run_in_background + Monitor can stream pub/sub frames
|
|
1000
|
+
if [ -f "$INSTALL_DIR/scripts/stream-events.js" ]; then
|
|
1001
|
+
cp "$INSTALL_DIR/scripts/stream-events.js" "$PROJECT_ROOT/.claws-bin/stream-events.js"
|
|
1002
|
+
chmod +x "$PROJECT_ROOT/.claws-bin/stream-events.js" 2>/dev/null || true
|
|
1003
|
+
fi
|
|
1004
|
+
# Copy per-spawn Monitor-arm watcher (v0.7.14+) — invoked by post-tool-use-claws.js
|
|
1005
|
+
if [ -f "$INSTALL_DIR/scripts/monitor-arm-watch.js" ]; then
|
|
1006
|
+
cp "$INSTALL_DIR/scripts/monitor-arm-watch.js" "$PROJECT_ROOT/.claws-bin/monitor-arm-watch.js"
|
|
1007
|
+
chmod +x "$PROJECT_ROOT/.claws-bin/monitor-arm-watch.js" 2>/dev/null || true
|
|
1008
|
+
fi
|
|
1009
|
+
# Copy lifecycle hook scripts for inject-settings-hooks.js to reference.
|
|
1010
|
+
# Source-of-truth is $INSTALL_DIR/scripts/hooks/ (committed to git).
|
|
1011
|
+
# Previously this read from $INSTALL_DIR/.claws-bin/hooks/, which is
|
|
1012
|
+
# gitignored and therefore missing on every fresh clone — silent skip.
|
|
1013
|
+
#
|
|
1014
|
+
# Wipe-then-copy: removed-in-newer-release files (e.g. post-tool-use-claws.js
|
|
1015
|
+
# deprecated in v0.6.5) used to survive in users' .claws-bin/hooks/ and the
|
|
1016
|
+
# Claude Code hook runner would still try to invoke them. Audit 4 finding I.
|
|
1017
|
+
#
|
|
1018
|
+
# We also ship a package.json shim with {"type":"commonjs"} alongside the
|
|
1019
|
+
# hooks. Without it, projects whose root package.json declares
|
|
1020
|
+
# "type":"module" (modern Node/TS projects) load the hook scripts as ESM,
|
|
1021
|
+
# and the CommonJS require() call at the top of each hook crashes.
|
|
1022
|
+
# Reported by user (Miles) on v0.7.0.
|
|
1023
|
+
if [ -d "$INSTALL_DIR/scripts/hooks" ]; then
|
|
1024
|
+
# M-09: atomic rename pattern — copy to tmp dir first, then swap into place.
|
|
1025
|
+
# Prevents kill-window leaving an empty hooks dir that breaks every Bash hook.
|
|
1026
|
+
# F1: set +e around heredoc so $? is readable (under set -eo pipefail the script
|
|
1027
|
+
# would abort at the heredoc before reaching any if [ $? ] check).
|
|
1028
|
+
set +e
|
|
1029
|
+
node --no-deprecation --input-type=module <<HOOKSATOMICEOF
|
|
1030
|
+
import { copyDirAtomic } from '${INSTALL_DIR}/scripts/_helpers/atomic-file.mjs';
|
|
1031
|
+
try {
|
|
1032
|
+
await copyDirAtomic('${INSTALL_DIR}/scripts/hooks', '${PROJECT_ROOT}/.claws-bin/hooks');
|
|
1033
|
+
} catch (e) {
|
|
1034
|
+
process.stderr.write('[M-09] atomic hooks copy failed: ' + e.message + '\\n');
|
|
1035
|
+
process.exit(1);
|
|
1036
|
+
}
|
|
1037
|
+
HOOKSATOMICEOF
|
|
1038
|
+
_hooks_exit=$?
|
|
1039
|
+
set -e
|
|
1040
|
+
if [ "$_hooks_exit" -ne 0 ]; then
|
|
1041
|
+
warn "hooks dir copy failed — .claws-bin/hooks may be incomplete"
|
|
1042
|
+
fi
|
|
1043
|
+
# package.json shim: write if not present after copy (older hooks dirs omit it).
|
|
1044
|
+
if [ ! -f "$PROJECT_ROOT/.claws-bin/hooks/package.json" ]; then
|
|
1045
|
+
printf '{"type":"commonjs","private":true}\n' > "$PROJECT_ROOT/.claws-bin/hooks/package.json"
|
|
1046
|
+
fi
|
|
1047
|
+
fi
|
|
1048
|
+
fi
|
|
1049
|
+
ok "vendored $PROJECT_ROOT/.claws-bin/"
|
|
1050
|
+
|
|
1051
|
+
# ── Copy the built VS Code extension into the project for visibility ────
|
|
1052
|
+
# VS Code still loads the extension from the user-level install
|
|
1053
|
+
# (~/.vscode/extensions/neunaha.claws-<version>). This project-local
|
|
1054
|
+
# copy is purely for visibility + portability — teammates who clone the
|
|
1055
|
+
# project can SEE what's installed, and users running `ls .claws-bin/`
|
|
1056
|
+
# can confirm Claws is present.
|
|
1057
|
+
#
|
|
1058
|
+
# Opt out with CLAWS_SKIP_EXTENSION_COPY=1.
|
|
1059
|
+
if [ "${CLAWS_SKIP_EXTENSION_COPY:-0}" != "1" ]; then
|
|
1060
|
+
PROJECT_EXT_DIR="$PROJECT_ROOT/.claws-bin/extension"
|
|
1061
|
+
rm -rf "$PROJECT_EXT_DIR" 2>/dev/null || true
|
|
1062
|
+
mkdir -p "$PROJECT_EXT_DIR"
|
|
1063
|
+
|
|
1064
|
+
# Copy the runtime-required pieces only — skip src/, test/, scripts/,
|
|
1065
|
+
# tsconfig, esbuild, node_modules (we have native/ as the bundle).
|
|
1066
|
+
for entry in dist native package.json package-lock.json README.md CHANGELOG.md icon.png .vscodeignore; do
|
|
1067
|
+
if [ -e "$INSTALL_DIR/extension/$entry" ]; then
|
|
1068
|
+
cp -R "$INSTALL_DIR/extension/$entry" "$PROJECT_EXT_DIR/" 2>/dev/null || true
|
|
1069
|
+
fi
|
|
1070
|
+
done
|
|
1071
|
+
|
|
1072
|
+
EXT_COPY_BYTES=$(du -sk "$PROJECT_EXT_DIR" 2>/dev/null | awk '{print $1*1024}')
|
|
1073
|
+
ok "copied extension → $PROJECT_EXT_DIR ($(numfmt --to=iec-i --suffix=B "$EXT_COPY_BYTES" 2>/dev/null || echo "${EXT_COPY_BYTES} bytes"))"
|
|
1074
|
+
fi
|
|
1075
|
+
|
|
1076
|
+
# ── Write a visible README.md in .claws-bin/ so teammates see what's there ──
|
|
1077
|
+
cat > "$PROJECT_ROOT/.claws-bin/README.md" <<CLAWSBIN
|
|
1078
|
+
# .claws-bin/
|
|
1079
|
+
|
|
1080
|
+
Project-local Claws runtime and artifacts. Auto-generated by the Claws
|
|
1081
|
+
installer — don't edit by hand; it's refreshed on every \`/claws-update\`.
|
|
1082
|
+
|
|
1083
|
+
## Contents
|
|
1084
|
+
|
|
1085
|
+
| File / dir | Role |
|
|
1086
|
+
|---|---|
|
|
1087
|
+
| \`mcp_server.js\` | Node MCP server. Spawned by Claude Code when it reads \`../.mcp.json\`. Bridges MCP protocol ⇄ Claws socket. |
|
|
1088
|
+
| \`shell-hook.sh\` | Shell initialization hook — copied from \`scripts/shell-hook.sh\` for reference. Actual hook lives in your \`~/.zshrc\`/\`~/.bashrc\` (appended by the installer). |
|
|
1089
|
+
| \`extension/\` | Full built copy of the VS Code extension (dist + native node-pty binary + manifest). **For visibility only** — VS Code itself loads the extension from the user-level install at \`~/.vscode/extensions/neunaha.claws-<version>\`, not from here. |
|
|
1090
|
+
| \`README.md\` | This file. |
|
|
1091
|
+
|
|
1092
|
+
## How the extension actually loads
|
|
1093
|
+
|
|
1094
|
+
The VS Code extension is installed at **user scope**, not per-project —
|
|
1095
|
+
that's how every VS Code extension works (Python, ESLint, Prettier, etc.).
|
|
1096
|
+
The symlinked location is:
|
|
1097
|
+
|
|
1098
|
+
\`\`\`
|
|
1099
|
+
~/.vscode/extensions/neunaha.claws-<version> → ~/.claws-src/extension
|
|
1100
|
+
\`\`\`
|
|
1101
|
+
|
|
1102
|
+
The \`extension/\` directory inside \`.claws-bin/\` is a **reference copy** that
|
|
1103
|
+
lets teammates see "Claws is installed" in the project. It's also useful if
|
|
1104
|
+
you want to version-pin the extension files alongside your project's git
|
|
1105
|
+
history.
|
|
1106
|
+
|
|
1107
|
+
## Recommended .gitignore
|
|
1108
|
+
|
|
1109
|
+
The \`extension/\` copy is ~300–400 KB. Common choices:
|
|
1110
|
+
|
|
1111
|
+
- **Commit it** if you want teammates who clone the repo to see the exact
|
|
1112
|
+
Claws version that's active, without running the installer.
|
|
1113
|
+
- **Ignore it** (\`.claws-bin/extension/\`) if you treat it as an install
|
|
1114
|
+
artifact that regenerates on demand.
|
|
1115
|
+
|
|
1116
|
+
\`.mcp.json\` is machine-specific (contains absolute paths) and is gitignored.
|
|
1117
|
+
Do not commit it. Re-run the installer to regenerate it if node or the
|
|
1118
|
+
project moves.
|
|
1119
|
+
|
|
1120
|
+
## Installing Claws from this project
|
|
1121
|
+
|
|
1122
|
+
If a teammate clones this project without Claws installed, they just run:
|
|
1123
|
+
|
|
1124
|
+
\`\`\`bash
|
|
1125
|
+
bash <(curl -fsSL https://raw.githubusercontent.com/neunaha/claws/main/scripts/install.sh)
|
|
1126
|
+
\`\`\`
|
|
1127
|
+
|
|
1128
|
+
That installs the extension at user scope, registers the MCP server, and
|
|
1129
|
+
refreshes these project-local files.
|
|
1130
|
+
|
|
1131
|
+
## Updating
|
|
1132
|
+
|
|
1133
|
+
\`\`\`bash
|
|
1134
|
+
bash <(curl -fsSL https://raw.githubusercontent.com/neunaha/claws/main/scripts/update.sh)
|
|
1135
|
+
\`\`\`
|
|
1136
|
+
|
|
1137
|
+
Or from inside a Claude Code session: \`/claws-update\`.
|
|
1138
|
+
|
|
1139
|
+
---
|
|
1140
|
+
|
|
1141
|
+
Claws docs: https://github.com/neunaha/claws
|
|
1142
|
+
CLAWSBIN
|
|
1143
|
+
ok "wrote $PROJECT_ROOT/.claws-bin/README.md"
|
|
1144
|
+
|
|
1145
|
+
# Write or merge .mcp.json with absolute args path so Claude Code can start
|
|
1146
|
+
# the server regardless of its cwd. __dirname walk-up in mcp_server.js
|
|
1147
|
+
# handles socket discovery once the server is running — no CLAWS_SOCKET needed.
|
|
1148
|
+
#
|
|
1149
|
+
# M-02: use json-safe.mjs mergeIntoFile — JSONC-tolerant, never resets cfg to {}
|
|
1150
|
+
# on parse error (which would silently wipe the user's other MCP servers).
|
|
1151
|
+
# F5: PROJECT_MCP and PROJECT_ROOT passed as env vars (not string-literal-embedded in JS)
|
|
1152
|
+
# to avoid JS SyntaxError when paths contain single-quotes or backslashes.
|
|
1153
|
+
PROJECT_MCP="$PROJECT_ROOT/.mcp.json"
|
|
1154
|
+
# F1: set +e around heredoc so _mcp_exit is readable before set -e is restored.
|
|
1155
|
+
set +e
|
|
1156
|
+
PROJECT_MCP="$PROJECT_MCP" PROJECT_ROOT="$PROJECT_ROOT" INSTALL_DIR="$INSTALL_DIR" \
|
|
1157
|
+
node --no-deprecation --input-type=module <<MCPMERGEEOF
|
|
1158
|
+
const { mergeIntoFile } = await import(process.env.INSTALL_DIR + '/scripts/_helpers/json-safe.mjs');
|
|
1159
|
+
const mcpPath = process.env.PROJECT_MCP;
|
|
1160
|
+
const projectRoot = process.env.PROJECT_ROOT;
|
|
1161
|
+
const result = await mergeIntoFile(mcpPath, cfg => {
|
|
1162
|
+
if (!cfg.mcpServers) cfg.mcpServers = {};
|
|
1163
|
+
cfg.mcpServers.claws = { command: 'node', args: [projectRoot + '/.claws-bin/mcp_server.js'] };
|
|
1164
|
+
});
|
|
1165
|
+
if (!result.ok) {
|
|
1166
|
+
const e = result.error;
|
|
1167
|
+
process.stderr.write('[M-02] .mcp.json merge failed: ' + e.message + '\\n');
|
|
1168
|
+
if (e.backupSavedAt) {
|
|
1169
|
+
process.stderr.write('[M-02] Malformed original backed up to: ' + e.backupSavedAt + '\\n');
|
|
1170
|
+
process.stderr.write('[M-02] Fix the JSON then re-run /claws-update\\n');
|
|
1171
|
+
}
|
|
1172
|
+
process.exit(1);
|
|
1173
|
+
}
|
|
1174
|
+
MCPMERGEEOF
|
|
1175
|
+
_mcp_exit=$?
|
|
1176
|
+
set -e
|
|
1177
|
+
if [ "$_mcp_exit" -ne 0 ]; then
|
|
1178
|
+
die ".mcp.json merge failed — original preserved. Fix $PROJECT_MCP then re-run /claws-update"
|
|
1179
|
+
fi
|
|
1180
|
+
ok "wrote $PROJECT_MCP"
|
|
1181
|
+
if ! node -e "JSON.parse(require('fs').readFileSync('$PROJECT_ROOT/.mcp.json','utf8'))" 2>/dev/null; then
|
|
1182
|
+
bad ".mcp.json written to $PROJECT_ROOT but is not valid JSON — MCP server will fail to load"
|
|
1183
|
+
bad "Check $CLAWS_LOG for jq/cat errors above"
|
|
1184
|
+
fi
|
|
1185
|
+
touch "$PROJECT_ROOT/.gitignore" 2>/dev/null || true
|
|
1186
|
+
if ! grep -q "^\.claws/" "$PROJECT_ROOT/.gitignore" 2>/dev/null; then
|
|
1187
|
+
echo ".claws/" >> "$PROJECT_ROOT/.gitignore"
|
|
1188
|
+
ok "added .claws/ to $PROJECT_ROOT/.gitignore"
|
|
1189
|
+
fi
|
|
1190
|
+
if ! grep -q "^\.mcp\.json" "$PROJECT_ROOT/.gitignore" 2>/dev/null; then
|
|
1191
|
+
echo ".mcp.json" >> "$PROJECT_ROOT/.gitignore"
|
|
1192
|
+
ok "added .mcp.json to $PROJECT_ROOT/.gitignore"
|
|
1193
|
+
fi
|
|
1194
|
+
if ! grep -q "^\.claws-bin/" "$PROJECT_ROOT/.gitignore" 2>/dev/null; then
|
|
1195
|
+
echo ".claws-bin/" >> "$PROJECT_ROOT/.gitignore"
|
|
1196
|
+
ok "added .claws-bin/ to $PROJECT_ROOT/.gitignore"
|
|
1197
|
+
fi
|
|
1198
|
+
|
|
1199
|
+
# Write/merge .vscode/extensions.json so VS Code prompts anyone who opens
|
|
1200
|
+
# this project without Claws installed. Pins `neunaha.claws` as a
|
|
1201
|
+
# workspace-recommended extension. Doesn't force-install — just shows
|
|
1202
|
+
# the standard "this workspace recommends installing these extensions"
|
|
1203
|
+
# prompt.
|
|
1204
|
+
if [ "${CLAWS_SKIP_VSCODE_RECOMMEND:-0}" != "1" ]; then
|
|
1205
|
+
VSCODE_DIR="$PROJECT_ROOT/.vscode"
|
|
1206
|
+
VSCODE_EXT_JSON="$VSCODE_DIR/extensions.json"
|
|
1207
|
+
mkdir -p "$VSCODE_DIR"
|
|
1208
|
+
node --no-deprecation -e "
|
|
1209
|
+
const fs = require('fs');
|
|
1210
|
+
const p = process.argv[1];
|
|
1211
|
+
let cfg = {};
|
|
1212
|
+
let parseError = false;
|
|
1213
|
+
try {
|
|
1214
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
1215
|
+
// Strip JSONC line + block comments so existing commented files merge
|
|
1216
|
+
// cleanly instead of being overwritten.
|
|
1217
|
+
const stripped = raw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
1218
|
+
cfg = JSON.parse(stripped);
|
|
1219
|
+
} catch (e) {
|
|
1220
|
+
if (fs.existsSync(p)) parseError = true;
|
|
1221
|
+
}
|
|
1222
|
+
if (parseError) {
|
|
1223
|
+
console.log('existing extensions.json has non-standard syntax — leaving it untouched');
|
|
1224
|
+
process.exit(0);
|
|
1225
|
+
}
|
|
1226
|
+
if (!Array.isArray(cfg.recommendations)) cfg.recommendations = [];
|
|
1227
|
+
if (!cfg.recommendations.includes('neunaha.claws')) {
|
|
1228
|
+
cfg.recommendations.push('neunaha.claws');
|
|
1229
|
+
fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + '\n');
|
|
1230
|
+
console.log('added neunaha.claws to workspace recommendations');
|
|
1231
|
+
} else {
|
|
1232
|
+
console.log('neunaha.claws already in workspace recommendations');
|
|
1233
|
+
}
|
|
1234
|
+
" "$VSCODE_EXT_JSON" | sed 's/^/ /' || true
|
|
1235
|
+
ok "wrote $VSCODE_EXT_JSON"
|
|
1236
|
+
fi
|
|
1237
|
+
else
|
|
1238
|
+
warn "no safe project dir — skipping project .mcp.json and .vscode/extensions.json"
|
|
1239
|
+
fi
|
|
1240
|
+
|
|
1241
|
+
if [ "${CLAWS_GLOBAL_MCP:-0}" = "1" ]; then
|
|
1242
|
+
mkdir -p "$HOME/.claude"
|
|
1243
|
+
node --no-deprecation -e "
|
|
1244
|
+
const fs = require('fs');
|
|
1245
|
+
const p = '$HOME/.claude/settings.json';
|
|
1246
|
+
const mcpServerPath = process.argv[1];
|
|
1247
|
+
let cfg = {};
|
|
1248
|
+
try { cfg = JSON.parse(fs.readFileSync(p, 'utf8')); } catch {}
|
|
1249
|
+
if (!cfg.mcpServers) cfg.mcpServers = {};
|
|
1250
|
+
cfg.mcpServers.claws = { command: 'node', args: [mcpServerPath] };
|
|
1251
|
+
fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + '\n');
|
|
1252
|
+
" "$INSTALL_DIR/mcp_server.js"
|
|
1253
|
+
ok "global MCP registered in ~/.claude/settings.json"
|
|
1254
|
+
fi
|
|
1255
|
+
fi
|
|
1256
|
+
|
|
1257
|
+
# ─── Step 6: Claude Code capabilities (commands/rules/skills/CLAUDE.md) ────
|
|
1258
|
+
step "Installing Claude Code capabilities"
|
|
1259
|
+
|
|
1260
|
+
install_capabilities_into() {
|
|
1261
|
+
local TARGET="$1"
|
|
1262
|
+
local LABEL="$2"
|
|
1263
|
+
local CMD_DIR="$TARGET/.claude/commands"
|
|
1264
|
+
mkdir -p "$CMD_DIR" "$TARGET/.claude/rules" "$TARGET/.claude/skills"
|
|
1265
|
+
|
|
1266
|
+
local cmd_count=0
|
|
1267
|
+
if [ -d "$INSTALL_DIR/.claude/commands" ]; then
|
|
1268
|
+
# v0.7.14 — Bug 1 fix: sweep prior-version Claws commands before copying.
|
|
1269
|
+
# The Claws prefix is owned by the installer; user-added commands (anything
|
|
1270
|
+
# without the claws- prefix) are untouched.
|
|
1271
|
+
# Known gap: dev-protocol-* commands are not swept (no such commands ship currently).
|
|
1272
|
+
local _swept_cmds=0
|
|
1273
|
+
for stale in "$CMD_DIR"/claws-*.md "$CMD_DIR"/claws.md; do
|
|
1274
|
+
[ -f "$stale" ] || continue
|
|
1275
|
+
rm -f "$stale" && _swept_cmds=$((_swept_cmds+1))
|
|
1276
|
+
done
|
|
1277
|
+
[ "$_swept_cmds" -gt 0 ] && warn "swept $_swept_cmds stale Claws commands from $CMD_DIR"
|
|
1278
|
+
for cmd in "$INSTALL_DIR/.claude/commands"/claws*.md; do
|
|
1279
|
+
[ -f "$cmd" ] || continue
|
|
1280
|
+
cp "$cmd" "$CMD_DIR/" && cmd_count=$((cmd_count+1))
|
|
1281
|
+
done
|
|
1282
|
+
fi
|
|
1283
|
+
|
|
1284
|
+
# Self-referential /claws-install command (points at GitHub so it works in any project)
|
|
1285
|
+
cat > "$CMD_DIR/claws-install.md" <<'CLAWSCMD'
|
|
1286
|
+
---
|
|
1287
|
+
name: claws-install
|
|
1288
|
+
description: Install or update Claws — Terminal Control Bridge for VS Code. Runs the installer inside the current project so this workspace gets the full project-local setup.
|
|
1289
|
+
---
|
|
1290
|
+
|
|
1291
|
+
# /claws-install
|
|
1292
|
+
|
|
1293
|
+
Install or update Claws in THIS project from https://github.com/neunaha/claws
|
|
1294
|
+
|
|
1295
|
+
Run this from the project root:
|
|
1296
|
+
|
|
1297
|
+
```bash
|
|
1298
|
+
bash <(curl -fsSL https://raw.githubusercontent.com/neunaha/claws/main/scripts/install.sh)
|
|
1299
|
+
```
|
|
1300
|
+
|
|
1301
|
+
After the script completes:
|
|
1302
|
+
1. Reload VS Code: Cmd+Shift+P → Developer: Reload Window
|
|
1303
|
+
2. Restart Claude Code in this project so the project-local `.mcp.json` is picked up.
|
|
1304
|
+
3. Try `/claws-help` or `/claws-status`.
|
|
1305
|
+
|
|
1306
|
+
If MCP tools don't appear after restart, run `/claws-fix` or `/claws-report`.
|
|
1307
|
+
CLAWSCMD
|
|
1308
|
+
cmd_count=$((cmd_count+1))
|
|
1309
|
+
|
|
1310
|
+
rm -f "$TARGET/.claude/rules/claws-default-behavior.md" 2>/dev/null || true
|
|
1311
|
+
[ -f "$INSTALL_DIR/rules/claws-default-behavior.md" ] \
|
|
1312
|
+
&& cp "$INSTALL_DIR/rules/claws-default-behavior.md" "$TARGET/.claude/rules/" || true
|
|
1313
|
+
|
|
1314
|
+
# P3-2: glob all claws-* and dev-protocol-* skills so new skills are picked up
|
|
1315
|
+
# without editing this script. Current skills: claws-prompt-templates,
|
|
1316
|
+
# claws-wave-lead, claws-wave-subworker.
|
|
1317
|
+
# SELF-COLLISION GUARD: when TARGET == INSTALL_DIR (e.g. ~/.claws-src is a symlink
|
|
1318
|
+
# to the project root on dev machines), src and dest resolve to the SAME inode.
|
|
1319
|
+
# Without the -ef guard, `rm -rf $dest` would wipe the source before `cp` could
|
|
1320
|
+
# read it, aborting install.sh at this step. -ef is a bash test for same-inode.
|
|
1321
|
+
# v0.7.14 — Bug 2 fix: sweep prior-version Claws skill directories.
|
|
1322
|
+
# Only directories starting with "claws-" — preserves user-added skills and
|
|
1323
|
+
# dev-protocol-* skills (none currently ship with Claws, but any present are
|
|
1324
|
+
# treated as user-added and left untouched).
|
|
1325
|
+
local _swept_skills=0
|
|
1326
|
+
for stale_skill in "$TARGET/.claude/skills"/claws-*; do
|
|
1327
|
+
[ -d "$stale_skill" ] || continue
|
|
1328
|
+
rm -rf "$stale_skill" && _swept_skills=$((_swept_skills+1))
|
|
1329
|
+
done
|
|
1330
|
+
[ "$_swept_skills" -gt 0 ] && warn "swept $_swept_skills stale Claws skills from $TARGET/.claude/skills"
|
|
1331
|
+
for _skill_src in "$INSTALL_DIR/.claude/skills"/claws-* "$INSTALL_DIR/.claude/skills"/dev-protocol-*; do
|
|
1332
|
+
[ -d "$_skill_src" ] || continue
|
|
1333
|
+
_skill_name="$(basename "$_skill_src")"
|
|
1334
|
+
if [ "$_skill_src" -ef "$TARGET/.claude/skills/$_skill_name" ]; then continue; fi
|
|
1335
|
+
rm -rf "$TARGET/.claude/skills/$_skill_name" 2>/dev/null || true
|
|
1336
|
+
cp -r "$_skill_src" "$TARGET/.claude/skills/$_skill_name"
|
|
1337
|
+
done
|
|
1338
|
+
unset _skill_src _skill_name
|
|
1339
|
+
|
|
1340
|
+
# CLAUDE.md injection (project scope only — never inside $HOME)
|
|
1341
|
+
# M-21: GIT_PULL_OK=0 means git pull failed in update.sh — skip re-injection to
|
|
1342
|
+
# avoid overwriting the user's CLAUDE.md tool set with stale source.
|
|
1343
|
+
if [ "$TARGET" != "$HOME" ]; then
|
|
1344
|
+
if [ "${GIT_PULL_OK:-1}" = "0" ]; then
|
|
1345
|
+
note "CLAUDE.md injection skipped — git pull failed, stale source (M-21)"
|
|
1346
|
+
elif [ ! -f "$INSTALL_DIR/scripts/inject-claude-md.js" ] && [ ! -f "$INSTALL_DIR/.claws-bin/inject-claude-md.js" ]; then
|
|
1347
|
+
warn "inject-claude-md.js not found — CLAUDE.md injection skipped. Clone may be incomplete."
|
|
1348
|
+
else
|
|
1349
|
+
node --no-deprecation "$INSTALL_DIR/scripts/inject-claude-md.js" "$TARGET" 2>&1 | sed 's/^/ /' || warn "CLAUDE.md injector failed — see $CLAWS_LOG for details"
|
|
1350
|
+
fi
|
|
1351
|
+
# Global ~/.claude/CLAUDE.md injection (machine-wide Claws policy)
|
|
1352
|
+
# F2/M-21: same GIT_PULL_OK gate as project CLAUDE.md — avoids rewriting
|
|
1353
|
+
# the machine-wide policy from stale source when git pull failed.
|
|
1354
|
+
if [ "${GIT_PULL_OK:-1}" = "0" ]; then
|
|
1355
|
+
note "global CLAUDE.md injection skipped — git pull failed, stale source (F2/M-21)"
|
|
1356
|
+
elif [ -f "$INSTALL_DIR/scripts/inject-global-claude-md.js" ]; then
|
|
1357
|
+
node --no-deprecation "$INSTALL_DIR/scripts/inject-global-claude-md.js" 2>&1 | sed 's/^/ /' || warn "global CLAUDE.md injector failed"
|
|
1358
|
+
fi
|
|
1359
|
+
# Hook registration in ~/.claude/settings.json (SessionStart / PreToolUse / Stop).
|
|
1360
|
+
# Bin path passed to the injector is $INSTALL_DIR/scripts — hooks register pointing
|
|
1361
|
+
# to the source clone's committed scripts/hooks/ directory (NOT gitignored, kept
|
|
1362
|
+
# current by git pull). Using the shared INSTALL_DIR path is correct because
|
|
1363
|
+
# ~/.claude/settings.json is a per-user global file, not per-project.
|
|
1364
|
+
# NOT $INSTALL_DIR/.claws-bin — that directory IS gitignored and missing on fresh clones.
|
|
1365
|
+
# CLAWS_NO_GLOBAL_HOOKS=1 skips this step entirely — useful for testing
|
|
1366
|
+
# an isolated install without touching the user's global Claude Code config.
|
|
1367
|
+
if [ -f "$INSTALL_DIR/scripts/inject-settings-hooks.js" ]; then
|
|
1368
|
+
if [ "${CLAWS_NO_GLOBAL_HOOKS:-0}" != "1" ]; then
|
|
1369
|
+
echo "Updating Claws hooks..."
|
|
1370
|
+
# M-18: use --update (atomic remove+add in one read-modify-write) instead of
|
|
1371
|
+
# two-pass --remove + add, which has a kill-window with zero Claws hooks.
|
|
1372
|
+
node --no-deprecation "$INSTALL_DIR/scripts/inject-settings-hooks.js" "$INSTALL_DIR/scripts" --update 2>&1 | sed 's/^/ /' || warn "settings hooks update failed"
|
|
1373
|
+
else
|
|
1374
|
+
echo " CLAWS_NO_GLOBAL_HOOKS=1 — skipping ~/.claude/settings.json registration"
|
|
1375
|
+
fi
|
|
1376
|
+
fi
|
|
1377
|
+
fi
|
|
1378
|
+
|
|
1379
|
+
ok "$LABEL: $cmd_count commands, rules, skills"
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
if [ "$PROJECT_INSTALL" = "1" ]; then
|
|
1383
|
+
install_capabilities_into "$PROJECT_ROOT" "project"
|
|
1384
|
+
else
|
|
1385
|
+
warn "skipped project-local capabilities (no safe project dir)"
|
|
1386
|
+
fi
|
|
1387
|
+
|
|
1388
|
+
if [ "${CLAWS_GLOBAL_CONFIG:-0}" = "1" ]; then
|
|
1389
|
+
install_capabilities_into "$HOME" "global (~/.claude)"
|
|
1390
|
+
fi
|
|
1391
|
+
|
|
1392
|
+
# ─── Step 6b: Dev-discipline hooks ──────────────────────────────────────────
|
|
1393
|
+
# Copies scripts/dev-hooks/*.js into <project>/.claws-bin/dev-hooks/ and
|
|
1394
|
+
# registers them in <project>/.claude/settings.json via inject-dev-hooks.js.
|
|
1395
|
+
# Idempotent: safe to re-run on every update.
|
|
1396
|
+
# Fix #2 (v0.7.12): dev-hooks are contributor diagnostics — only install when:
|
|
1397
|
+
# a) CLAWS_INSTALL_DEV_HOOKS=1 is set explicitly, OR
|
|
1398
|
+
# b) the install target IS the Claws source tree (contributor dev environment).
|
|
1399
|
+
# Default user installs skip dev-hooks entirely to avoid SessionStart hook spam.
|
|
1400
|
+
_claws_is_dev_tree=0
|
|
1401
|
+
if [ -f "$PROJECT_ROOT/scripts/install.sh" ] && [ -d "$PROJECT_ROOT/extension/src" ]; then
|
|
1402
|
+
_claws_is_dev_tree=1
|
|
1403
|
+
fi
|
|
1404
|
+
if { [ "${CLAWS_INSTALL_DEV_HOOKS:-0}" = "1" ] || [ "$_claws_is_dev_tree" = "1" ]; } && [ "$PROJECT_INSTALL" = "1" ] && [ -d "$INSTALL_DIR/scripts/dev-hooks" ]; then
|
|
1405
|
+
step "Installing dev-discipline hooks"
|
|
1406
|
+
DEV_HOOKS_DST="$PROJECT_ROOT/.claws-bin/dev-hooks"
|
|
1407
|
+
mkdir -p "$DEV_HOOKS_DST"
|
|
1408
|
+
_dh_count=0
|
|
1409
|
+
for _dh in "$INSTALL_DIR/scripts/dev-hooks"/*.js; do
|
|
1410
|
+
[ -f "$_dh" ] || continue
|
|
1411
|
+
cp "$_dh" "$DEV_HOOKS_DST/" && _dh_count=$((_dh_count+1))
|
|
1412
|
+
done
|
|
1413
|
+
ok "copied $_dh_count dev-hook scripts → $DEV_HOOKS_DST"
|
|
1414
|
+
|
|
1415
|
+
if [ -f "$INSTALL_DIR/scripts/inject-dev-hooks.js" ]; then
|
|
1416
|
+
node --no-deprecation "$INSTALL_DIR/scripts/inject-dev-hooks.js" "$PROJECT_ROOT" 2>&1 | sed 's/^/ /' \
|
|
1417
|
+
|| warn "inject-dev-hooks.js failed — dev hooks not registered"
|
|
1418
|
+
else
|
|
1419
|
+
warn "inject-dev-hooks.js not found — dev hooks not registered"
|
|
1420
|
+
fi
|
|
1421
|
+
unset _dh _dh_count
|
|
1422
|
+
fi
|
|
1423
|
+
unset _claws_is_dev_tree
|
|
1424
|
+
|
|
1425
|
+
# ─── Step 7: Shell hook ────────────────────────────────────────────────────
|
|
1426
|
+
step "Injecting shell hook"
|
|
1427
|
+
HOOK_SOURCE="source \"$INSTALL_DIR/scripts/shell-hook.sh\""
|
|
1428
|
+
HOOK_MARKER="# CLAWS terminal hook"
|
|
1429
|
+
|
|
1430
|
+
inject_hook() {
|
|
1431
|
+
local rcfile="$1"
|
|
1432
|
+
touch "$rcfile" 2>/dev/null || true
|
|
1433
|
+
|
|
1434
|
+
# Detect prior state BEFORE rewriting so we can report accurately.
|
|
1435
|
+
local had_stale=0
|
|
1436
|
+
local had_marker=0
|
|
1437
|
+
if grep -q "CLAWS terminal hook" "$rcfile" 2>/dev/null; then
|
|
1438
|
+
had_marker=1
|
|
1439
|
+
if ! grep -Fq "$HOOK_SOURCE" "$rcfile" 2>/dev/null; then
|
|
1440
|
+
had_stale=1
|
|
1441
|
+
fi
|
|
1442
|
+
fi
|
|
1443
|
+
# Detect orphaned source lines (marker missing, source survived from a
|
|
1444
|
+
# prior install whose sed delete failed on BSD sed). Audit 4 finding G.
|
|
1445
|
+
if grep -Eq '^[[:space:]]*source[[:space:]].*/shell-hook\.sh' "$rcfile" 2>/dev/null \
|
|
1446
|
+
&& ! grep -Fq "$HOOK_SOURCE" "$rcfile" 2>/dev/null; then
|
|
1447
|
+
had_stale=1
|
|
1448
|
+
fi
|
|
1449
|
+
|
|
1450
|
+
# M-01: create a timestamped backup of the dotfile BEFORE any modification.
|
|
1451
|
+
# Allows the user to restore if something goes wrong. Only created when the
|
|
1452
|
+
# file already has content — no backup for a freshly touch'd empty file.
|
|
1453
|
+
# Keep only the 3 most recent backups to prevent accumulation on repeated runs.
|
|
1454
|
+
local tmp="$rcfile.claws-tmp.$$"
|
|
1455
|
+
if [ -s "$rcfile" ]; then
|
|
1456
|
+
local _bak_ts
|
|
1457
|
+
_bak_ts=$(date -u +%Y%m%dT%H%M%SZ 2>/dev/null || date +%Y%m%dT%H%M%SZ)
|
|
1458
|
+
cp "$rcfile" "${rcfile}.claws-bak.${_bak_ts}" 2>/dev/null || true
|
|
1459
|
+
# Prune older backups — keep the 3 newest, remove the rest.
|
|
1460
|
+
ls -1t "${rcfile}.claws-bak."* 2>/dev/null | tail -n +4 | xargs rm -f 2>/dev/null || true
|
|
1461
|
+
fi
|
|
1462
|
+
|
|
1463
|
+
# Portable cleanup via awk (works on BSD awk and GNU awk identically).
|
|
1464
|
+
# Replaces the GNU-only `sed '/pat/,+1d'` form, which silently failed on
|
|
1465
|
+
# macOS ≤ Monterey and left orphaned `source ".../shell-hook.sh"` lines.
|
|
1466
|
+
#
|
|
1467
|
+
# M-01: strips ONLY lines inside a Claws-marked block:
|
|
1468
|
+
# 1. Strip every `# CLAWS terminal hook` marker line.
|
|
1469
|
+
# 2. Strip the immediately following line IF it is the Claws source line.
|
|
1470
|
+
# The previous generic `/source .../shell-hook\.sh/` regex is removed because
|
|
1471
|
+
# it matched non-Claws tools (oh-my-zsh, asdf, custom dotfiles) causing data loss.
|
|
1472
|
+
#
|
|
1473
|
+
# F4: orphaned-marker edge case — if a user manually deleted the source line but
|
|
1474
|
+
# left the marker, the old `skip { skip=0; next }` would silently strip whatever
|
|
1475
|
+
# user content happened to follow the marker. Fix: only skip the following line if
|
|
1476
|
+
# it matches the Claws source pattern; otherwise keep it (skip=0; print).
|
|
1477
|
+
#
|
|
1478
|
+
# M-17: always promote awk output when awk succeeds, even if output is empty.
|
|
1479
|
+
# When the file contains ONLY the Claws block, awk produces no output (empty tmp).
|
|
1480
|
+
# The old `[ -s "$tmp" -o ! -s "$rcfile" ]` guard prevented promotion in that case
|
|
1481
|
+
# (rcfile had content → `! -s` false; tmp empty → `-s` false → guard fails), so the
|
|
1482
|
+
# original was left intact and a new block was appended on the next install, creating
|
|
1483
|
+
# duplicate hooks. Fix: mv unconditionally when awk exits 0.
|
|
1484
|
+
if awk '
|
|
1485
|
+
/# CLAWS terminal hook/ { skip = 1; next }
|
|
1486
|
+
skip && /source.*shell-hook\.sh/ { skip = 0; next }
|
|
1487
|
+
skip { skip = 0; print }
|
|
1488
|
+
{ print }
|
|
1489
|
+
' "$rcfile" > "$tmp" 2>/dev/null; then
|
|
1490
|
+
mv "$tmp" "$rcfile" 2>/dev/null || rm -f "$tmp"
|
|
1491
|
+
else
|
|
1492
|
+
rm -f "$tmp" 2>/dev/null || true
|
|
1493
|
+
fi
|
|
1494
|
+
|
|
1495
|
+
if printf "\n%s\n%s\n" "$HOOK_MARKER" "$HOOK_SOURCE" >> "$rcfile" 2>/dev/null; then
|
|
1496
|
+
if [ "$had_stale" = "1" ]; then
|
|
1497
|
+
ok "refreshed in $(basename "$rcfile") (removed stale path)"
|
|
1498
|
+
elif [ "$had_marker" = "1" ]; then
|
|
1499
|
+
ok "refreshed in $(basename "$rcfile")"
|
|
1500
|
+
else
|
|
1501
|
+
ok "added to $(basename "$rcfile")"
|
|
1502
|
+
fi
|
|
1503
|
+
else
|
|
1504
|
+
warn "could not write to $rcfile"
|
|
1505
|
+
fi
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
[ -f "$INSTALL_DIR/scripts/shell-hook.sh" ] \
|
|
1509
|
+
|| die "shell-hook.sh missing from $INSTALL_DIR/scripts/ — clone may be incomplete."
|
|
1510
|
+
inject_hook "$HOME/.zshrc"
|
|
1511
|
+
# Use `zsh -n` for .zshrc (zsh-only syntax like `setopt`/`autoload -Uz` parses
|
|
1512
|
+
# fine in zsh but `bash -n` reports false-positive errors). Fall back to `bash -n`
|
|
1513
|
+
# only if zsh is not installed. Audit 1 finding H-2.
|
|
1514
|
+
if command -v zsh &>/dev/null; then
|
|
1515
|
+
zsh -n "$HOME/.zshrc" 2>/dev/null || warn "~/.zshrc has a syntax error after hook injection — check manually"
|
|
1516
|
+
else
|
|
1517
|
+
bash -n "$HOME/.zshrc" 2>/dev/null || warn "~/.zshrc has a syntax error after hook injection — check manually"
|
|
1518
|
+
fi
|
|
1519
|
+
inject_hook "$HOME/.bashrc"
|
|
1520
|
+
bash -n "$HOME/.bashrc" 2>/dev/null || warn "~/.bashrc has a syntax error after hook injection — check manually"
|
|
1521
|
+
if [ "$PLATFORM" = "Darwin" ]; then
|
|
1522
|
+
inject_hook "$HOME/.bash_profile"
|
|
1523
|
+
bash -n "$HOME/.bash_profile" 2>/dev/null || warn "~/.bash_profile has a syntax error after hook injection — check manually"
|
|
1524
|
+
fi
|
|
1525
|
+
|
|
1526
|
+
if [ -d "$HOME/.config/fish" ]; then
|
|
1527
|
+
FISH_CONF="$HOME/.config/fish/conf.d/claws.fish"
|
|
1528
|
+
mkdir -p "$HOME/.config/fish/conf.d" 2>/dev/null
|
|
1529
|
+
# Write a minimal conf.d loader that sets CLAWS_DIR and sources the
|
|
1530
|
+
# standalone shell-hook.fish (no bass dependency required).
|
|
1531
|
+
{
|
|
1532
|
+
echo "# CLAWS terminal hook (auto-generated — do not edit)"
|
|
1533
|
+
echo "set -gx CLAWS_DIR '$INSTALL_DIR'"
|
|
1534
|
+
echo "set -gx CLAWS_SOCKET '.claws/claws.sock'"
|
|
1535
|
+
echo "if test -f '$INSTALL_DIR/scripts/shell-hook.fish'"
|
|
1536
|
+
echo " source '$INSTALL_DIR/scripts/shell-hook.fish'"
|
|
1537
|
+
echo "end"
|
|
1538
|
+
} > "$FISH_CONF" && ok "wrote fish conf (native fish, no bass required)" || warn "could not write fish config"
|
|
1539
|
+
fi
|
|
1540
|
+
|
|
1541
|
+
# ── Nushell hook ─────────────────────────────────────────────────────────────
|
|
1542
|
+
# Nushell sources env.nu on startup. We append a CLAWS_DIR assignment if absent.
|
|
1543
|
+
_NU_ENV="$HOME/.config/nushell/env.nu"
|
|
1544
|
+
_NU_CONFIG="$HOME/.config/nushell/config.nu"
|
|
1545
|
+
if [ -f "$_NU_ENV" ] || [ -f "$_NU_CONFIG" ]; then
|
|
1546
|
+
_NU_TARGET="${_NU_ENV:-$_NU_CONFIG}"
|
|
1547
|
+
if ! grep -q "CLAWS_DIR" "$_NU_TARGET" 2>/dev/null; then
|
|
1548
|
+
{
|
|
1549
|
+
printf '\n# CLAWS terminal hook\n'
|
|
1550
|
+
printf '$env.CLAWS_DIR = "%s"\n' "$INSTALL_DIR"
|
|
1551
|
+
printf '$env.CLAWS_SOCKET = ".claws/claws.sock"\n'
|
|
1552
|
+
} >> "$_NU_TARGET" && ok "wrote nushell env ($( basename "$_NU_TARGET" ))" \
|
|
1553
|
+
|| warn "could not write nushell config"
|
|
1554
|
+
else
|
|
1555
|
+
ok "nushell env already has CLAWS_DIR — skipped"
|
|
1556
|
+
fi
|
|
1557
|
+
fi
|
|
1558
|
+
unset _NU_ENV _NU_CONFIG _NU_TARGET
|
|
1559
|
+
|
|
1560
|
+
# ─── Step 8: Verify ────────────────────────────────────────────────────────
|
|
1561
|
+
step "Verifying"
|
|
1562
|
+
|
|
1563
|
+
CHECKS_PASS=0
|
|
1564
|
+
CHECKS_FAIL=0
|
|
1565
|
+
_ok() { ok "$*"; CHECKS_PASS=$((CHECKS_PASS+1)); }
|
|
1566
|
+
_miss() { bad "$*"; CHECKS_FAIL=$((CHECKS_FAIL+1)); }
|
|
1567
|
+
|
|
1568
|
+
if [ "${#INSTALLED_EDITORS[@]}" -gt 0 ]; then
|
|
1569
|
+
_ok "Extension installed in: ${INSTALLED_EDITORS[*]}"
|
|
1570
|
+
else
|
|
1571
|
+
_miss "Extension not installed in any editor — run /claws-fix"
|
|
1572
|
+
fi
|
|
1573
|
+
[ -f "$INSTALL_DIR/extension/dist/extension.js" ] && _ok "Extension bundle built" || warn "Extension bundle missing — fallback to legacy JS active"
|
|
1574
|
+
[ -f "$MCP_PATH" ] && _ok "MCP server exists at $MCP_PATH" || _miss "$MCP_PATH missing"
|
|
1575
|
+
if command -v node &>/dev/null; then
|
|
1576
|
+
_ok "Node.js available ($(node --version)) — $(node -e 'process.stdout.write(process.execPath)' 2>/dev/null)"
|
|
1577
|
+
info "Note: GUI-launched VS Code may resolve a different Node.js PATH than this shell"
|
|
1578
|
+
else
|
|
1579
|
+
_miss "node not found"
|
|
1580
|
+
fi
|
|
1581
|
+
|
|
1582
|
+
if [ "$PROJECT_INSTALL" = "1" ]; then
|
|
1583
|
+
if [ -f "$PROJECT_ROOT/.mcp.json" ] && node -e "JSON.parse(require('fs').readFileSync('$PROJECT_ROOT/.mcp.json','utf8'))" 2>/dev/null; then
|
|
1584
|
+
_ok "Project .mcp.json (present and valid JSON)"
|
|
1585
|
+
elif [ -f "$PROJECT_ROOT/.mcp.json" ]; then
|
|
1586
|
+
_miss "Project .mcp.json exists but is invalid JSON — MCP server will fail to load"
|
|
1587
|
+
else
|
|
1588
|
+
_miss "project .mcp.json missing"
|
|
1589
|
+
fi
|
|
1590
|
+
[ -f "$PROJECT_ROOT/.claws-bin/mcp_server.js" ] && _ok "Project .claws-bin/mcp_server.js" || _miss "project mcp_server.js copy missing"
|
|
1591
|
+
if [ "${CLAWS_SKIP_EXTENSION_COPY:-0}" != "1" ]; then
|
|
1592
|
+
[ -f "$PROJECT_ROOT/.claws-bin/extension/dist/extension.js" ] \
|
|
1593
|
+
&& _ok "Project .claws-bin/extension/ (visible copy)" \
|
|
1594
|
+
|| warn "project .claws-bin/extension/ not copied"
|
|
1595
|
+
fi
|
|
1596
|
+
[ -f "$PROJECT_ROOT/.claws-bin/README.md" ] && _ok "Project .claws-bin/README.md" || warn "project .claws-bin/README.md missing"
|
|
1597
|
+
[ -d "$PROJECT_ROOT/.claws-bin/hooks" ] && _ok "Project .claws-bin/hooks/ (lifecycle hooks)" || _miss "project .claws-bin/hooks/ missing"
|
|
1598
|
+
# BUG-28: verify MCP spawn-class PreToolUse hooks registered (Monitor arm gate)
|
|
1599
|
+
if [ -f "$HOME/.claude/settings.json" ]; then
|
|
1600
|
+
if CLAWS_SETTINGS_CHECK="$HOME/.claude/settings.json" node --no-deprecation -e "
|
|
1601
|
+
const s = JSON.parse(require('fs').readFileSync(process.env.CLAWS_SETTINGS_CHECK, 'utf8'));
|
|
1602
|
+
const h = (s.hooks && s.hooks.PreToolUse) || [];
|
|
1603
|
+
process.exit(h.some(e => e.matcher && e.matcher.includes('mcp__claws__claws_worker')) ? 0 : 1);
|
|
1604
|
+
" 2>/dev/null; then
|
|
1605
|
+
_ok "MCP spawn-class PreToolUse hooks registered in ~/.claude/settings.json"
|
|
1606
|
+
else
|
|
1607
|
+
_miss "MCP spawn-class PreToolUse hooks missing — re-run inject-settings-hooks.js to register Monitor gate"
|
|
1608
|
+
fi
|
|
1609
|
+
fi
|
|
1610
|
+
if [ -f "$HOME/.claude/settings.json" ]; then
|
|
1611
|
+
if CLAWS_SETTINGS_CHECK="$HOME/.claude/settings.json" node --no-deprecation -e "
|
|
1612
|
+
const s = JSON.parse(require('fs').readFileSync(process.env.CLAWS_SETTINGS_CHECK, 'utf8'));
|
|
1613
|
+
const h = (s.hooks && s.hooks.PostToolUse) || [];
|
|
1614
|
+
process.exit(h.some(e => e.matcher && e.matcher.includes('mcp__claws__claws_worker')) ? 0 : 1);
|
|
1615
|
+
" 2>/dev/null; then
|
|
1616
|
+
_ok "PostToolUse spawn-class hooks registered in ~/.claude/settings.json"
|
|
1617
|
+
else
|
|
1618
|
+
_miss "PostToolUse spawn-class hooks missing — re-run inject-settings-hooks.js to register Wave C monitor gate"
|
|
1619
|
+
fi
|
|
1620
|
+
fi
|
|
1621
|
+
[ -f "$PROJECT_ROOT/.vscode/extensions.json" ] && grep -q "neunaha.claws" "$PROJECT_ROOT/.vscode/extensions.json" 2>/dev/null && _ok "Project .vscode/extensions.json recommends claws" || warn "project .vscode/extensions.json missing claws recommendation"
|
|
1622
|
+
[ -d "$PROJECT_ROOT/.claude/commands" ] && _ok "Project .claude/commands" || _miss "project commands missing"
|
|
1623
|
+
[ -d "$PROJECT_ROOT/.claude/skills" ] && _ok "Project .claude/skills" || _miss "project skills missing"
|
|
1624
|
+
[ -d "$PROJECT_ROOT/.claude/rules" ] && _ok "Project .claude/rules" || _miss "project rules missing"
|
|
1625
|
+
[ -f "$PROJECT_ROOT/CLAUDE.md" ] && _ok "Project CLAUDE.md" || warn "project CLAUDE.md not created"
|
|
1626
|
+
fi
|
|
1627
|
+
|
|
1628
|
+
# Test MCP server handshake (portable — no dependency on GNU timeout)
|
|
1629
|
+
VERIFY_MCP="$MCP_PATH"
|
|
1630
|
+
[ "$PROJECT_INSTALL" = "1" ] && [ -f "$PROJECT_ROOT/.claws-bin/mcp_server.js" ] && VERIFY_MCP="$PROJECT_ROOT/.claws-bin/mcp_server.js"
|
|
1631
|
+
if command -v node &>/dev/null && [ -f "$VERIFY_MCP" ]; then
|
|
1632
|
+
if MCP_TEST=$(node --no-deprecation -e '
|
|
1633
|
+
const { spawn } = require("child_process");
|
|
1634
|
+
const mcp = spawn("node", [process.argv[1]], { stdio: ["pipe", "pipe", "ignore"] });
|
|
1635
|
+
const req = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "claws-install", version: "1" } } });
|
|
1636
|
+
let buf = "";
|
|
1637
|
+
const done = (code, out) => { try { mcp.kill(); } catch {} ; process.stdout.write(out); process.exit(code); };
|
|
1638
|
+
const timer = setTimeout(() => done(1, "TIMEOUT"), 5000);
|
|
1639
|
+
mcp.stdout.on("data", d => { buf += d.toString("utf8"); if (buf.includes("claws")) { clearTimeout(timer); done(0, buf.slice(0, 200)); } });
|
|
1640
|
+
mcp.on("error", e => { clearTimeout(timer); done(1, "SPAWN_ERROR: " + e.message); });
|
|
1641
|
+
mcp.stdin.write(req + "\n");
|
|
1642
|
+
' "$VERIFY_MCP" 2>&1) && echo "$MCP_TEST" | grep -q "claws"; then
|
|
1643
|
+
_ok "MCP server starts and responds (initialize OK)"
|
|
1644
|
+
else
|
|
1645
|
+
_miss "MCP server failed initialize — run: node $VERIFY_MCP"
|
|
1646
|
+
info "$MCP_TEST"
|
|
1647
|
+
fi
|
|
1648
|
+
fi
|
|
1649
|
+
|
|
1650
|
+
echo ""
|
|
1651
|
+
if [ "$CHECKS_FAIL" -eq 0 ]; then
|
|
1652
|
+
ok "$CHECKS_PASS checks passed"
|
|
1653
|
+
else
|
|
1654
|
+
warn "$CHECKS_PASS passed, $CHECKS_FAIL issue(s) — see above"
|
|
1655
|
+
fi
|
|
1656
|
+
|
|
1657
|
+
# Verify shell hook is active in user's rc files
|
|
1658
|
+
_hook_verified=0
|
|
1659
|
+
for _rc in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile"; do
|
|
1660
|
+
if [ -f "$_rc" ] && grep -q "CLAWS terminal hook" "$_rc" 2>/dev/null; then
|
|
1661
|
+
_hook_verified=1
|
|
1662
|
+
break
|
|
1663
|
+
fi
|
|
1664
|
+
done
|
|
1665
|
+
if [ "$_hook_verified" = "0" ]; then
|
|
1666
|
+
warn "Shell hook not detected in any rc file — run: source $INSTALL_DIR/scripts/shell-hook.sh"
|
|
1667
|
+
fi
|
|
1668
|
+
|
|
1669
|
+
# ─── End-of-install banner ─────────────────────────────────────────────────
|
|
1670
|
+
cat <<BANNER
|
|
1671
|
+
|
|
1672
|
+
${C_BOLD}██████╗██╗ █████╗ ██╗ ██╗███████╗
|
|
1673
|
+
██╔════╝██║ ██╔══██╗██║ ██║██╔════╝
|
|
1674
|
+
██║ ██║ ███████║██║ █╗ ██║███████╗
|
|
1675
|
+
██║ ██║ ██╔══██║██║███╗██║╚════██║
|
|
1676
|
+
╚██████╗███████╗██║ ██║╚███╔███╔╝███████║
|
|
1677
|
+
╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚══════╝${C_RESET}
|
|
1678
|
+
|
|
1679
|
+
${C_BOLD}Terminal Control Bridge${C_RESET} v$EXT_VERSION — installed.
|
|
1680
|
+
|
|
1681
|
+
BANNER
|
|
1682
|
+
if [ "$PROJECT_INSTALL" = "1" ]; then
|
|
1683
|
+
printf ' Project: %s\n' "$PROJECT_ROOT"
|
|
1684
|
+
printf ' MCP server: %s\n' "$PROJECT_ROOT/.claws-bin/mcp_server.js"
|
|
1685
|
+
printf ' Registered: %s\n' "$PROJECT_ROOT/.mcp.json"
|
|
1686
|
+
else
|
|
1687
|
+
printf ' Project: ${C_YELLOW}(none — re-run from your project root)${C_RESET}\n'
|
|
1688
|
+
printf ' MCP server: %s\n' "$MCP_PATH"
|
|
1689
|
+
fi
|
|
1690
|
+
if [ "${#INSTALLED_EDITORS[@]}" -gt 0 ]; then
|
|
1691
|
+
printf ' Extension: installed in %s\n' "${INSTALLED_EDITORS[*]}"
|
|
1692
|
+
printf ' (method: %s)\n' "$VSIX_INSTALL_METHOD"
|
|
1693
|
+
printf ' (loaded from %s)\n' "$HOME/.vscode/extensions/neunaha.claws-$EXT_VERSION"
|
|
1694
|
+
if [ "$PROJECT_INSTALL" = "1" ] && [ "${CLAWS_SKIP_EXTENSION_COPY:-0}" != "1" ]; then
|
|
1695
|
+
printf ' (visible copy in %s/.claws-bin/extension/)\n' "$PROJECT_ROOT"
|
|
1696
|
+
fi
|
|
1697
|
+
else
|
|
1698
|
+
printf ' Extension: ${C_YELLOW}NOT INSTALLED — run /claws-fix${C_RESET}\n'
|
|
1699
|
+
fi
|
|
1700
|
+
printf ' Install log: %s\n' "$CLAWS_LOG"
|
|
1701
|
+
cat <<NEXT
|
|
1702
|
+
|
|
1703
|
+
${C_BOLD}── One step left to activate ──${C_RESET}
|
|
1704
|
+
${C_BOLD}Reload VS Code:${C_RESET} Cmd+Shift+P → "Developer: Reload Window"
|
|
1705
|
+
|
|
1706
|
+
That's it. The extension activates on reload; the MCP tools come online
|
|
1707
|
+
the next time you start a Claude Code session in this project (new
|
|
1708
|
+
sessions auto-pick-up .mcp.json — no manual restart required if Claude
|
|
1709
|
+
Code isn't already running here).
|
|
1710
|
+
|
|
1711
|
+
${C_BOLD}── If something is off ──${C_RESET}
|
|
1712
|
+
MCP tools not appearing? /claws-fix
|
|
1713
|
+
Want to report an issue? /claws-report
|
|
1714
|
+
Update later: /claws-update
|
|
1715
|
+
|
|
1716
|
+
${C_DIM}Optional: export CLAWS_STRICT=1 to hard-block long-running Bash via PreToolUse hook${C_RESET}
|
|
1717
|
+
${C_DIM}Worker binary: defaults to 'claude'. Override via:${C_RESET}
|
|
1718
|
+
/claws-bin <name> (slash command — recommended)
|
|
1719
|
+
echo name > .claws/claude-bin (file override, gitignored)
|
|
1720
|
+
export CLAWS_CLAUDE_BIN=<name> (shell env var)
|
|
1721
|
+
|
|
1722
|
+
Docs: https://github.com/neunaha/claws
|
|
1723
|
+
Website: https://neunaha.github.io/claws/
|
|
1724
|
+
|
|
1725
|
+
NEXT
|
|
1726
|
+
|
|
1727
|
+
# Source shell hook last so its output doesn't push the banner off-screen.
|
|
1728
|
+
# shellcheck disable=SC1090
|
|
1729
|
+
info "Open a new terminal to activate the shell hook."
|