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,343 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
7
|
+
const { dryRunLog, getDefaultShellRcFile } = require('./platform.js');
|
|
8
|
+
|
|
9
|
+
// Canonical marker — matches install.sh's HOOK_MARKER exactly.
|
|
10
|
+
const MARKER = '# CLAWS terminal hook';
|
|
11
|
+
|
|
12
|
+
// Legacy format from old Node installer. Stripped as a one-time migration during inject.
|
|
13
|
+
const LEGACY_BEGIN = '# >>> claws-code shell hook >>>';
|
|
14
|
+
const LEGACY_END = '# <<< claws-code shell hook <<<';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns the list of standard shell rc files to inject into.
|
|
18
|
+
* macOS: ~/.zshrc, ~/.bashrc, ~/.bash_profile
|
|
19
|
+
* Linux: ~/.zshrc, ~/.bashrc
|
|
20
|
+
* win32: [] (no-op on Windows)
|
|
21
|
+
* Matches install.sh's multi-file injection (lines 1419–1433).
|
|
22
|
+
* @param {object} [opts]
|
|
23
|
+
* @returns {string[]}
|
|
24
|
+
*/
|
|
25
|
+
function _getStandardRcFiles(opts = {}) {
|
|
26
|
+
const platform = opts.platform !== undefined ? opts.platform : process.platform;
|
|
27
|
+
const home = opts.home !== undefined ? opts.home : os.homedir();
|
|
28
|
+
|
|
29
|
+
if (platform === 'win32') return [];
|
|
30
|
+
|
|
31
|
+
const files = [
|
|
32
|
+
path.join(home, '.zshrc'),
|
|
33
|
+
path.join(home, '.bashrc'),
|
|
34
|
+
];
|
|
35
|
+
if (platform === 'darwin') {
|
|
36
|
+
files.push(path.join(home, '.bash_profile'));
|
|
37
|
+
}
|
|
38
|
+
return files;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Inject (or replace) the claws shell hook block in all standard rc files.
|
|
43
|
+
* On macOS: ~/.zshrc, ~/.bashrc, ~/.bash_profile.
|
|
44
|
+
* On Linux: ~/.zshrc, ~/.bashrc.
|
|
45
|
+
* Also handles fish (conf.d/claws.fish) and nushell (env.nu) when present.
|
|
46
|
+
* Idempotent per file.
|
|
47
|
+
* @param {string} installDir - path to the claws repo (contains scripts/shell-hook.sh)
|
|
48
|
+
* @param {boolean} [dryRun]
|
|
49
|
+
*/
|
|
50
|
+
function injectShellHook(installDir, dryRun = false) {
|
|
51
|
+
if (process.platform === 'win32') {
|
|
52
|
+
_injectPowershellHook(installDir, dryRun);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const rcFiles = _getStandardRcFiles();
|
|
56
|
+
for (const rcFile of rcFiles) {
|
|
57
|
+
_injectIntoFile(rcFile, installDir, dryRun);
|
|
58
|
+
}
|
|
59
|
+
_injectFishHook(installDir, dryRun);
|
|
60
|
+
_injectNushellHook(installDir, dryRun);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Write (or overwrite) ~/.config/fish/conf.d/claws.fish when the fish
|
|
65
|
+
* config directory exists. Fish sources conf.d/ automatically on startup.
|
|
66
|
+
* Matches install.sh lines 1435-1448.
|
|
67
|
+
* @param {string} installDir
|
|
68
|
+
* @param {boolean} [dryRun]
|
|
69
|
+
*/
|
|
70
|
+
function _injectFishHook(installDir, dryRun) {
|
|
71
|
+
const home = os.homedir();
|
|
72
|
+
if (!fs.existsSync(path.join(home, '.config', 'fish'))) return;
|
|
73
|
+
|
|
74
|
+
const confD = path.join(home, '.config', 'fish', 'conf.d');
|
|
75
|
+
const fishFile = path.join(confD, 'claws.fish');
|
|
76
|
+
const hookFish = path.join(installDir, 'scripts', 'shell-hook.fish');
|
|
77
|
+
|
|
78
|
+
const content = [
|
|
79
|
+
'# CLAWS terminal hook (auto-generated — do not edit)',
|
|
80
|
+
`set -gx CLAWS_DIR '${installDir}'`,
|
|
81
|
+
`set -gx CLAWS_SOCKET '.claws/claws.sock'`,
|
|
82
|
+
`if test -f '${hookFish}'`,
|
|
83
|
+
` source '${hookFish}'`,
|
|
84
|
+
'end',
|
|
85
|
+
'',
|
|
86
|
+
].join('\n');
|
|
87
|
+
|
|
88
|
+
if (dryRun) {
|
|
89
|
+
dryRunLog(`write fish hook to ${fishFile}`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fs.mkdirSync(confD, { recursive: true });
|
|
94
|
+
const tmp = fishFile + '.claws-tmp.' + process.pid;
|
|
95
|
+
fs.writeFileSync(tmp, content, 'utf8');
|
|
96
|
+
fs.renameSync(tmp, fishFile);
|
|
97
|
+
process.stdout.write(` \x1b[32m✓\x1b[0m fish hook written to ${fishFile}\n`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Append the CLAWS_DIR assignment to nushell env.nu (or config.nu) when
|
|
102
|
+
* either file exists. Idempotent: skips if CLAWS_DIR already present.
|
|
103
|
+
* Matches install.sh lines 1450-1466.
|
|
104
|
+
* @param {string} installDir
|
|
105
|
+
* @param {boolean} [dryRun]
|
|
106
|
+
*/
|
|
107
|
+
function _injectNushellHook(installDir, dryRun) {
|
|
108
|
+
const home = os.homedir();
|
|
109
|
+
const nuEnv = path.join(home, '.config', 'nushell', 'env.nu');
|
|
110
|
+
const nuConfig = path.join(home, '.config', 'nushell', 'config.nu');
|
|
111
|
+
|
|
112
|
+
let target = null;
|
|
113
|
+
if (fs.existsSync(nuEnv)) target = nuEnv;
|
|
114
|
+
else if (fs.existsSync(nuConfig)) target = nuConfig;
|
|
115
|
+
if (!target) return;
|
|
116
|
+
|
|
117
|
+
if (dryRun) {
|
|
118
|
+
dryRunLog(`append CLAWS_DIR to nushell ${path.basename(target)}`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const existing = fs.readFileSync(target, 'utf8');
|
|
123
|
+
if (existing.includes('CLAWS_DIR')) {
|
|
124
|
+
process.stdout.write(` \x1b[2m${path.basename(target)} already has CLAWS_DIR — skipped\x1b[0m\n`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const append = `\n${MARKER}\n$env.CLAWS_DIR = "${installDir}"\n$env.CLAWS_SOCKET = ".claws/claws.sock"\n`;
|
|
129
|
+
fs.appendFileSync(target, append, 'utf8');
|
|
130
|
+
process.stdout.write(` \x1b[32m✓\x1b[0m nushell env written to ${target}\n`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Remove the claws shell hook block from rcFile.
|
|
135
|
+
* Handles both canonical install.sh format and legacy >>>...<<< format.
|
|
136
|
+
* @param {string} rcFile
|
|
137
|
+
* @param {boolean} [dryRun]
|
|
138
|
+
*/
|
|
139
|
+
function removeShellHook(rcFile, dryRun = false) {
|
|
140
|
+
if (!fs.existsSync(rcFile)) return;
|
|
141
|
+
|
|
142
|
+
if (dryRun) {
|
|
143
|
+
dryRunLog(`remove shell hook from ${rcFile}`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const original = fs.readFileSync(rcFile, 'utf8');
|
|
148
|
+
const cleaned = _removePriorBlock(original);
|
|
149
|
+
if (cleaned === original) return;
|
|
150
|
+
|
|
151
|
+
const tmp = rcFile + '.claws-tmp.' + process.pid;
|
|
152
|
+
fs.writeFileSync(tmp, cleaned, 'utf8');
|
|
153
|
+
fs.renameSync(tmp, rcFile);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Inject the Claws shell hook into the PowerShell profile on win32.
|
|
158
|
+
* Copies shell-hook.ps1 to a stable location ($HOME/.claude/claws/) so that
|
|
159
|
+
* $PROFILE never references the install-time temp dir (which is deleted post-install).
|
|
160
|
+
* Uses the same `# CLAWS terminal hook` marker as the bash installer.
|
|
161
|
+
* @param {string} installDir - path to the claws repo (contains scripts/shell-hook.ps1)
|
|
162
|
+
* @param {boolean} [dryRun]
|
|
163
|
+
* @param {object} [opts] - internal overrides for testing
|
|
164
|
+
* @param {string} [opts.home] - override os.homedir()
|
|
165
|
+
* @param {Function}[opts.execFn] - override execSync for powershell profile lookup
|
|
166
|
+
*/
|
|
167
|
+
function _injectPowershellHook(installDir, dryRun, opts = {}) {
|
|
168
|
+
const home = opts.home !== undefined ? opts.home : os.homedir();
|
|
169
|
+
const profilePath = getDefaultShellRcFile({ platform: 'win32', home, execFn: opts.execFn });
|
|
170
|
+
const hookPs1 = path.join(installDir, 'scripts', 'shell-hook.ps1');
|
|
171
|
+
|
|
172
|
+
// Stable location: $HOME/.claude/claws/shell-hook.ps1 — one level above the lifecycle
|
|
173
|
+
// hooks dir (~/.claude/claws/hooks/). $PROFILE sources this stable copy so it survives
|
|
174
|
+
// the install temp-dir deletion (root cause of W7-4B).
|
|
175
|
+
const stableDir = path.join(home, '.claude', 'claws');
|
|
176
|
+
const stableHookPs1 = path.join(stableDir, 'shell-hook.ps1');
|
|
177
|
+
|
|
178
|
+
// PowerShell dot-source syntax: . "absolute\path\to\shell-hook.ps1"
|
|
179
|
+
const block = `\n${MARKER}\n. "${stableHookPs1}"\n`;
|
|
180
|
+
|
|
181
|
+
if (dryRun) {
|
|
182
|
+
dryRunLog(`copy shell-hook.ps1 to ${stableHookPs1}`);
|
|
183
|
+
dryRunLog(`inject PS shell hook into ${profilePath}`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Copy hook to stable dir before writing $PROFILE entry.
|
|
188
|
+
fs.mkdirSync(stableDir, { recursive: true });
|
|
189
|
+
if (fs.existsSync(hookPs1)) {
|
|
190
|
+
fs.copyFileSync(hookPs1, stableHookPs1);
|
|
191
|
+
process.stdout.write(` \x1b[32m✓\x1b[0m shell-hook.ps1 copied to ${stableHookPs1}\n`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fs.mkdirSync(path.dirname(profilePath), { recursive: true });
|
|
195
|
+
if (!fs.existsSync(profilePath)) {
|
|
196
|
+
fs.writeFileSync(profilePath, '', 'utf8');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Backup before modification — mirrors _injectIntoFile's backup pattern.
|
|
200
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
201
|
+
const backup = profilePath + '.claws-bak.' + ts;
|
|
202
|
+
try {
|
|
203
|
+
fs.copyFileSync(profilePath, backup);
|
|
204
|
+
} catch (e) {
|
|
205
|
+
process.stderr.write(` ! Warning: could not backup ${profilePath}: ${e.message}\n`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const original = fs.readFileSync(profilePath, 'utf8');
|
|
209
|
+
const cleaned = _removePriorBlock(original);
|
|
210
|
+
const updated = cleaned + block;
|
|
211
|
+
|
|
212
|
+
const tmp = profilePath + '.claws-tmp.' + process.pid;
|
|
213
|
+
fs.writeFileSync(tmp, updated, 'utf8');
|
|
214
|
+
fs.renameSync(tmp, profilePath);
|
|
215
|
+
process.stdout.write(` \x1b[32m✓\x1b[0m PS profile updated: ${profilePath}\n`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Return the shell binary to use for syntax-checking rcFile, or null if
|
|
220
|
+
* no check is applicable. Mirrors install.sh lines 1420-1432 (use zsh for
|
|
221
|
+
* .zshrc to avoid false positives with zsh-only syntax like setopt/autoload).
|
|
222
|
+
* @param {string} rcFile
|
|
223
|
+
* @returns {string|null}
|
|
224
|
+
*/
|
|
225
|
+
function _getValidatorShell(rcFile) {
|
|
226
|
+
const base = path.basename(rcFile);
|
|
227
|
+
if (base === '.zshrc') return 'zsh';
|
|
228
|
+
if (base === '.bashrc' || base === '.bash_profile') return 'bash';
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function _injectIntoFile(rcFile, installDir, dryRun) {
|
|
233
|
+
const hookSh = path.join(installDir, 'scripts', 'shell-hook.sh');
|
|
234
|
+
// Canonical format matches install.sh: marker line + source line (no closing marker).
|
|
235
|
+
const block = `\n${MARKER}\nsource "${hookSh}"\n`;
|
|
236
|
+
|
|
237
|
+
if (dryRun) {
|
|
238
|
+
dryRunLog(`backup ${rcFile}`);
|
|
239
|
+
dryRunLog(`inject shell hook into ${rcFile}`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!fs.existsSync(rcFile)) {
|
|
244
|
+
fs.mkdirSync(path.dirname(rcFile), { recursive: true });
|
|
245
|
+
fs.writeFileSync(rcFile, '', 'utf8');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Backup before modification — matches install.sh lines 1360-1369.
|
|
249
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
250
|
+
const backup = rcFile + '.claws-bak.' + ts;
|
|
251
|
+
try {
|
|
252
|
+
fs.copyFileSync(rcFile, backup);
|
|
253
|
+
} catch (e) {
|
|
254
|
+
process.stderr.write(` ! Warning: could not backup ${rcFile}: ${e.message}\n`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const original = fs.readFileSync(rcFile, 'utf8');
|
|
258
|
+
const cleaned = _removePriorBlock(original);
|
|
259
|
+
const updated = cleaned + block;
|
|
260
|
+
|
|
261
|
+
const tmp = rcFile + '.claws-tmp.' + process.pid;
|
|
262
|
+
fs.writeFileSync(tmp, updated, 'utf8');
|
|
263
|
+
fs.renameSync(tmp, rcFile);
|
|
264
|
+
|
|
265
|
+
// Syntax validation — matches install.sh lines 1423-1432.
|
|
266
|
+
const shellBin = _getValidatorShell(rcFile);
|
|
267
|
+
if (shellBin) {
|
|
268
|
+
const check = spawnSync(shellBin, ['-n', rcFile], { encoding: 'utf8', stdio: 'pipe' });
|
|
269
|
+
if (check.status !== 0) {
|
|
270
|
+
process.stderr.write(
|
|
271
|
+
` ! Warning: ${path.basename(rcFile)} failed ${shellBin} -n check — backup at ${backup}\n`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Remove any prior Claws shell hook block from content.
|
|
279
|
+
* Handles both formats:
|
|
280
|
+
* - Canonical (install.sh): "# CLAWS terminal hook" marker + following source line
|
|
281
|
+
* - Legacy (old Node installer): "# >>> claws-code shell hook >>>" ... "# <<< ... <<<"
|
|
282
|
+
* Mirrors the awk cleanup pattern in install.sh's inject_hook():
|
|
283
|
+
* /# CLAWS terminal hook/ { skip = 1; next }
|
|
284
|
+
* skip && /source.*shell-hook\.sh/ { skip = 0; next }
|
|
285
|
+
* skip { skip = 0; print }
|
|
286
|
+
* { print }
|
|
287
|
+
* @param {string} content
|
|
288
|
+
* @returns {string}
|
|
289
|
+
*/
|
|
290
|
+
function _removePriorBlock(content) {
|
|
291
|
+
// Step 1: one-time migration — strip legacy >>>...<<< block if present.
|
|
292
|
+
content = _removeLegacyBlock(content);
|
|
293
|
+
|
|
294
|
+
// Step 2: strip canonical format — marker line + following source line.
|
|
295
|
+
const lines = content.split('\n');
|
|
296
|
+
const out = [];
|
|
297
|
+
let skip = false;
|
|
298
|
+
for (const line of lines) {
|
|
299
|
+
if (/# CLAWS terminal hook/.test(line)) {
|
|
300
|
+
skip = true;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (skip && /shell-hook\.(sh|ps1)/.test(line)) {
|
|
304
|
+
skip = false;
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (skip) {
|
|
308
|
+
// Marker found but next line is not a source line — keep it, stop skipping.
|
|
309
|
+
skip = false;
|
|
310
|
+
out.push(line);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
out.push(line);
|
|
314
|
+
}
|
|
315
|
+
return out.join('\n').replace(/\n+$/, '');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Strip the legacy Node-installer >>>...<<< block from content.
|
|
320
|
+
* @param {string} content
|
|
321
|
+
* @returns {string}
|
|
322
|
+
*/
|
|
323
|
+
function _removeLegacyBlock(content) {
|
|
324
|
+
const beginRe = /^# >>> claws-code shell hook >>>$/m;
|
|
325
|
+
const endRe = /^# <<< claws-code shell hook <<<$/m;
|
|
326
|
+
|
|
327
|
+
const beginIdx = content.search(beginRe);
|
|
328
|
+
if (beginIdx === -1) return content;
|
|
329
|
+
|
|
330
|
+
const afterBegin = content.slice(beginIdx);
|
|
331
|
+
const endMatch = afterBegin.match(endRe);
|
|
332
|
+
if (!endMatch) return content;
|
|
333
|
+
|
|
334
|
+
const endIdx = beginIdx + afterBegin.indexOf(endMatch[0]) + endMatch[0].length;
|
|
335
|
+
return content.slice(0, beginIdx).replace(/\n+$/, '') + content.slice(endIdx);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
module.exports = {
|
|
339
|
+
injectShellHook, removeShellHook,
|
|
340
|
+
_getStandardRcFiles, _getValidatorShell,
|
|
341
|
+
_injectFishHook, _injectNushellHook, _injectPowershellHook,
|
|
342
|
+
_injectIntoFile, _removePriorBlock, _removeLegacyBlock,
|
|
343
|
+
};
|
package/lib/uninstall.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
const { findCodeCli, getDefaultShellRcFile, dryRunLog } = require('./platform.js');
|
|
9
|
+
const { sweepCommands, sweepSkills } = require('./capabilities.js');
|
|
10
|
+
const { removeShellHook } = require('./shell-hook.js');
|
|
11
|
+
const { removeMcpEntry } = require('./mcp-setup.js');
|
|
12
|
+
|
|
13
|
+
const HOME = os.homedir();
|
|
14
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
15
|
+
|
|
16
|
+
function _step(label) { process.stdout.write(`\n\x1b[1m${label}\x1b[0m\n`); }
|
|
17
|
+
function _ok(msg) { process.stdout.write(` \x1b[32m✓\x1b[0m ${msg}\n`); }
|
|
18
|
+
function _warn(msg) { process.stdout.write(` \x1b[33m!\x1b[0m ${msg}\n`); }
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Reverse of install: remove all Claws artifacts from the current project and ~/.claude/.
|
|
22
|
+
* @param {object} [opts]
|
|
23
|
+
* @param {boolean} [opts.dryRun]
|
|
24
|
+
*/
|
|
25
|
+
function run(opts = {}) {
|
|
26
|
+
const { dryRun = false } = opts;
|
|
27
|
+
const projectRoot = process.cwd();
|
|
28
|
+
const claudeDir = path.join(HOME, '.claude');
|
|
29
|
+
|
|
30
|
+
process.stdout.write('\nUninstalling Claws...\n');
|
|
31
|
+
|
|
32
|
+
// 1. VS Code extension
|
|
33
|
+
_step('Uninstall VS Code extension');
|
|
34
|
+
_uninstallExtension(dryRun);
|
|
35
|
+
|
|
36
|
+
// 2. .claws-bin/
|
|
37
|
+
_step('Remove .claws-bin/');
|
|
38
|
+
const clawsBin = path.join(projectRoot, '.claws-bin');
|
|
39
|
+
if (dryRun) {
|
|
40
|
+
dryRunLog(`rm -rf ${clawsBin}`);
|
|
41
|
+
} else if (fs.existsSync(clawsBin)) {
|
|
42
|
+
fs.rmSync(clawsBin, { recursive: true });
|
|
43
|
+
_ok('.claws-bin/ removed');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 3. .mcp.json claws entry
|
|
47
|
+
_step('Remove claws entry from .mcp.json');
|
|
48
|
+
removeMcpEntry(projectRoot, dryRun);
|
|
49
|
+
if (!dryRun) _ok('.mcp.json updated');
|
|
50
|
+
|
|
51
|
+
// 4. CLAUDE.md CLAWS:BEGIN block (project)
|
|
52
|
+
_step('Remove CLAWS:BEGIN block from CLAUDE.md');
|
|
53
|
+
_removeClawsBlock(
|
|
54
|
+
path.join(projectRoot, 'CLAUDE.md'),
|
|
55
|
+
/<!-- CLAWS:BEGIN(?:[^>]*)-->/,
|
|
56
|
+
/<!-- CLAWS:END(?:[^>]*)-->/,
|
|
57
|
+
dryRun
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// 5. ~/.claude/CLAUDE.md CLAWS-GLOBAL:BEGIN block
|
|
61
|
+
_step('Remove CLAWS-GLOBAL:BEGIN block from ~/.claude/CLAUDE.md');
|
|
62
|
+
_removeClawsBlock(
|
|
63
|
+
path.join(HOME, '.claude', 'CLAUDE.md'),
|
|
64
|
+
/<!-- CLAWS-GLOBAL:BEGIN(?:[^>]*)-->/,
|
|
65
|
+
/<!-- CLAWS-GLOBAL:END(?:[^>]*)-->/,
|
|
66
|
+
dryRun
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// 6. Hooks from settings.json
|
|
70
|
+
_step('Remove hooks from ~/.claude/settings.json');
|
|
71
|
+
_removeHooks(dryRun);
|
|
72
|
+
|
|
73
|
+
// 7. claws-default-behavior.md rule
|
|
74
|
+
_step('Remove claws-default-behavior.md');
|
|
75
|
+
const rulePath = path.join(claudeDir, 'rules', 'claws-default-behavior.md');
|
|
76
|
+
if (dryRun) {
|
|
77
|
+
dryRunLog(`rm ${rulePath}`);
|
|
78
|
+
} else if (fs.existsSync(rulePath)) {
|
|
79
|
+
fs.rmSync(rulePath);
|
|
80
|
+
_ok('claws-default-behavior.md removed');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 8. Commands + skills (global)
|
|
84
|
+
_step('Remove commands and skills from ~/.claude/');
|
|
85
|
+
sweepCommands(path.join(claudeDir, 'commands'), dryRun);
|
|
86
|
+
sweepSkills(path.join(claudeDir, 'skills'), dryRun);
|
|
87
|
+
if (!dryRun) _ok('Commands and skills removed');
|
|
88
|
+
|
|
89
|
+
// 9. Shell hook
|
|
90
|
+
_step('Remove shell hook');
|
|
91
|
+
const rcFile = getDefaultShellRcFile();
|
|
92
|
+
removeShellHook(rcFile, dryRun);
|
|
93
|
+
if (!dryRun) _ok(`Shell hook removed from ${rcFile}`);
|
|
94
|
+
|
|
95
|
+
process.stdout.write('\n \x1b[32m✓ Claws uninstalled\x1b[0m\n\n');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function _uninstallExtension(dryRun) {
|
|
99
|
+
const codeCli = findCodeCli();
|
|
100
|
+
if (!codeCli) {
|
|
101
|
+
_warn('VS Code CLI not found — extension may still be installed; remove manually');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (dryRun) { dryRunLog(`${codeCli} --uninstall-extension neunaha.claws`); return; }
|
|
106
|
+
|
|
107
|
+
const r = spawnSync(codeCli, ['--uninstall-extension', 'neunaha.claws'], {
|
|
108
|
+
stdio: 'inherit', encoding: 'utf8',
|
|
109
|
+
});
|
|
110
|
+
if (r.status === 0) { _ok('VS Code extension uninstalled'); return; }
|
|
111
|
+
|
|
112
|
+
// CLI failed — remove extension directory directly
|
|
113
|
+
const extDirs = [
|
|
114
|
+
path.join(HOME, '.vscode', 'extensions'),
|
|
115
|
+
path.join(HOME, '.cursor', 'extensions'),
|
|
116
|
+
];
|
|
117
|
+
let removed = false;
|
|
118
|
+
for (const dir of extDirs) {
|
|
119
|
+
if (!fs.existsSync(dir)) continue;
|
|
120
|
+
for (const entry of fs.readdirSync(dir).filter(f => f.startsWith('neunaha.claws-'))) {
|
|
121
|
+
fs.rmSync(path.join(dir, entry), { recursive: true });
|
|
122
|
+
removed = true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (removed) { _ok('Extension directory removed'); } else { _warn('Extension not found in extensions dirs'); }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function _removeClawsBlock(filePath, beginRe, endRe, dryRun) {
|
|
129
|
+
if (!fs.existsSync(filePath)) return;
|
|
130
|
+
|
|
131
|
+
if (dryRun) { dryRunLog(`remove claws block from ${filePath}`); return; }
|
|
132
|
+
|
|
133
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
134
|
+
const beginMatch = content.match(beginRe);
|
|
135
|
+
if (!beginMatch) return;
|
|
136
|
+
|
|
137
|
+
const beginIdx = content.indexOf(beginMatch[0]);
|
|
138
|
+
const endMatch = content.match(endRe);
|
|
139
|
+
if (!endMatch) return;
|
|
140
|
+
|
|
141
|
+
const endIdx = content.lastIndexOf(endMatch[0]) + endMatch[0].length;
|
|
142
|
+
content = (content.slice(0, beginIdx) + content.slice(endIdx)).replace(/\n{3,}/g, '\n\n');
|
|
143
|
+
|
|
144
|
+
const tmp = filePath + '.claws-tmp.' + process.pid;
|
|
145
|
+
fs.writeFileSync(tmp, content, 'utf8');
|
|
146
|
+
fs.renameSync(tmp, filePath);
|
|
147
|
+
_ok(`CLAWS block removed from ${path.basename(filePath)}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function _removeHooks(dryRun) {
|
|
151
|
+
const script = path.join(REPO_ROOT, 'scripts', 'inject-settings-hooks.js');
|
|
152
|
+
const hooksDir = path.join(REPO_ROOT, 'scripts');
|
|
153
|
+
const extraArgs = dryRun ? [hooksDir, '--dry-run', '--remove'] : [hooksDir, '--remove'];
|
|
154
|
+
|
|
155
|
+
if (!fs.existsSync(script)) { _warn('inject-settings-hooks.js not found — hooks not removed'); return; }
|
|
156
|
+
|
|
157
|
+
spawnSync(process.execPath, [script, ...extraArgs], {
|
|
158
|
+
cwd: REPO_ROOT, stdio: 'inherit', encoding: 'utf8',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = { run };
|
package/lib/verify.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { spawnSync, spawn } = require('child_process');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Post-install verification. Returns array of failure strings (empty = OK).
|
|
10
|
+
* All checks use project-local paths under projectRoot/.claude/ — matching
|
|
11
|
+
* where lib/install.js (W7-6) places skills and rules.
|
|
12
|
+
* @param {string} projectRoot
|
|
13
|
+
* @returns {string[]}
|
|
14
|
+
*/
|
|
15
|
+
function verify(projectRoot) {
|
|
16
|
+
const failures = [];
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(path.join(projectRoot, '.claws-bin', 'mcp_server.js'))) {
|
|
19
|
+
failures.push('.claws-bin/mcp_server.js missing');
|
|
20
|
+
}
|
|
21
|
+
if (!fs.existsSync(path.join(projectRoot, '.mcp.json'))) {
|
|
22
|
+
failures.push('.mcp.json missing');
|
|
23
|
+
}
|
|
24
|
+
if (!fs.existsSync(path.join(projectRoot, '.claude', 'commands', 'claws.md'))) {
|
|
25
|
+
failures.push('claws commands missing from .claude/commands/');
|
|
26
|
+
}
|
|
27
|
+
if (!fs.existsSync(path.join(projectRoot, '.claude', 'skills', 'claws-prompt-templates'))) {
|
|
28
|
+
failures.push('claws skills missing from .claude/skills/');
|
|
29
|
+
}
|
|
30
|
+
if (!fs.existsSync(path.join(projectRoot, '.claude', 'rules', 'claws-default-behavior.md'))) {
|
|
31
|
+
failures.push('claws rule missing from .claude/rules/');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// W7h-28: Live MCP server handshake test (matches install.sh:1540-1557).
|
|
35
|
+
// Spawns mcp_server.js, sends an initialize JSON-RPC request, expects
|
|
36
|
+
// a response containing "claws" within 3s.
|
|
37
|
+
const mcpServer = path.join(projectRoot, '.claws-bin', 'mcp_server.js');
|
|
38
|
+
if (fs.existsSync(mcpServer)) {
|
|
39
|
+
const handshakeResult = _mcpHandshake(mcpServer);
|
|
40
|
+
if (!handshakeResult) {
|
|
41
|
+
failures.push(`MCP server failed to respond — run: node ${mcpServer}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// W7h-29: Hook registration check (matches install.sh:1508-1528).
|
|
46
|
+
// Parse ~/.claude/settings.json and assert PreToolUse + PostToolUse hooks
|
|
47
|
+
// are registered with Claws _source.
|
|
48
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
49
|
+
if (fs.existsSync(settingsPath)) {
|
|
50
|
+
const hookFailures = _checkHooksRegistered(settingsPath);
|
|
51
|
+
for (const f of hookFailures) failures.push(f);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return failures;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Spawn mcp_server.js, send initialize JSON-RPC, expect response with "claws" in 3s.
|
|
59
|
+
* Returns true if handshake succeeded, false otherwise.
|
|
60
|
+
* @param {string} mcpServerPath
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
function _mcpHandshake(mcpServerPath) {
|
|
64
|
+
try {
|
|
65
|
+
const req = JSON.stringify({
|
|
66
|
+
jsonrpc: '2.0', id: 1, method: 'initialize',
|
|
67
|
+
params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'claws-verify', version: '1' } },
|
|
68
|
+
}) + '\n';
|
|
69
|
+
|
|
70
|
+
// Run the handshake in a synchronous subprocess via a self-contained node -e script.
|
|
71
|
+
const script = `
|
|
72
|
+
const {spawn} = require('child_process');
|
|
73
|
+
const mcp = spawn('node', [process.argv[1]], {stdio:['pipe','pipe','ignore']});
|
|
74
|
+
let buf = '';
|
|
75
|
+
const done = (ok) => { try{mcp.kill()}catch{} process.exit(ok ? 0 : 1); };
|
|
76
|
+
const timer = setTimeout(() => done(false), 3000);
|
|
77
|
+
mcp.stdout.on('data', d => {
|
|
78
|
+
buf += d.toString();
|
|
79
|
+
if (buf.includes('claws')) { clearTimeout(timer); done(true); }
|
|
80
|
+
});
|
|
81
|
+
mcp.on('error', () => { clearTimeout(timer); done(false); });
|
|
82
|
+
mcp.stdin.write(${JSON.stringify(req)});
|
|
83
|
+
`;
|
|
84
|
+
const r = spawnSync(process.execPath, ['-e', script, mcpServerPath],
|
|
85
|
+
{ encoding: 'utf8', timeout: 5000 });
|
|
86
|
+
return r.status === 0;
|
|
87
|
+
} catch (_) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Parse ~/.claude/settings.json and check that PreToolUse + PostToolUse
|
|
94
|
+
* spawn-class hooks are registered (W7h-29). Matches install.sh:1508-1528.
|
|
95
|
+
* @param {string} settingsPath
|
|
96
|
+
* @returns {string[]} array of failure strings
|
|
97
|
+
*/
|
|
98
|
+
function _checkHooksRegistered(settingsPath) {
|
|
99
|
+
const failures = [];
|
|
100
|
+
let settings;
|
|
101
|
+
try {
|
|
102
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
103
|
+
} catch (_) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const hooks = settings.hooks || {};
|
|
108
|
+
const preHooks = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
|
|
109
|
+
const postHooks = Array.isArray(hooks.PostToolUse) ? hooks.PostToolUse : [];
|
|
110
|
+
|
|
111
|
+
const hasClawsPre = preHooks.some(h => h.matcher && h.matcher.includes('mcp__claws__claws_worker'));
|
|
112
|
+
const hasClawsPost = postHooks.some(h => h.matcher && h.matcher.includes('mcp__claws__claws_worker'));
|
|
113
|
+
|
|
114
|
+
if (!hasClawsPre) {
|
|
115
|
+
failures.push('MCP spawn-class PreToolUse hooks missing — re-run: node scripts/inject-settings-hooks.js');
|
|
116
|
+
}
|
|
117
|
+
if (!hasClawsPost) {
|
|
118
|
+
failures.push('PostToolUse spawn-class hooks missing — re-run: node scripts/inject-settings-hooks.js');
|
|
119
|
+
}
|
|
120
|
+
return failures;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Print a human-readable status dashboard for the current project.
|
|
125
|
+
* Sets process.exitCode = 1 when any check fails.
|
|
126
|
+
* All path checks are project-local (cwd-relative), matching install.sh behavior.
|
|
127
|
+
*/
|
|
128
|
+
function status() {
|
|
129
|
+
const cwd = process.cwd();
|
|
130
|
+
process.stdout.write('\nClaws installation status\n\n');
|
|
131
|
+
|
|
132
|
+
const checks = [
|
|
133
|
+
['Node.js ≥ 18', _nodeOk()],
|
|
134
|
+
['git in PATH', _gitOk()],
|
|
135
|
+
['.claws-bin/ present', fs.existsSync(path.join(cwd, '.claws-bin'))],
|
|
136
|
+
['.mcp.json present', fs.existsSync(path.join(cwd, '.mcp.json'))],
|
|
137
|
+
['mcp_server.js in .claws-bin', fs.existsSync(path.join(cwd, '.claws-bin', 'mcp_server.js'))],
|
|
138
|
+
['commands present', fs.existsSync(path.join(cwd, '.claude', 'commands', 'claws.md'))],
|
|
139
|
+
['skills present', fs.existsSync(path.join(cwd, '.claude', 'skills', 'claws-prompt-templates'))],
|
|
140
|
+
['behavior rule present', fs.existsSync(path.join(cwd, '.claude', 'rules', 'claws-default-behavior.md'))],
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
let passing = 0;
|
|
144
|
+
for (const [label, pass] of checks) {
|
|
145
|
+
const icon = pass ? '✓' : '✗';
|
|
146
|
+
const color = pass ? '\x1b[32m' : '\x1b[31m';
|
|
147
|
+
process.stdout.write(` ${color}${icon}\x1b[0m ${label}\n`);
|
|
148
|
+
if (pass) passing++;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
process.stdout.write(`\n ${passing}/${checks.length} checks passing\n\n`);
|
|
152
|
+
if (passing < checks.length) {
|
|
153
|
+
process.stdout.write(' Run: claws-code install\n\n');
|
|
154
|
+
process.exitCode = 1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function _nodeOk() {
|
|
159
|
+
return Number(process.versions.node.split('.')[0]) >= 18;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function _gitOk() {
|
|
163
|
+
return spawnSync('git', ['--version'], { stdio: 'pipe' }).status === 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = { verify, status };
|