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
package/lib/mcp-setup.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { dryRunLog } = require('./platform.js');
|
|
6
|
+
|
|
7
|
+
// Strip JSONC extensions (line/block comments, trailing commas) from text before
|
|
8
|
+
// JSON.parse. Handles .mcp.json files with VS Code JSONC comments.
|
|
9
|
+
// Matches install.sh json-safe.mjs approach (simple regex version).
|
|
10
|
+
function _stripJsonc(text) {
|
|
11
|
+
return text
|
|
12
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
13
|
+
.replace(/\/\/[^\n]*/g, '')
|
|
14
|
+
.replace(/,(\s*[}\]])/g, '$1');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Idempotently write (or update) the claws entry in <projectRoot>/.mcp.json.
|
|
19
|
+
* Preserves all other mcpServers entries. Atomic write (tmp + rename).
|
|
20
|
+
* Uses absolute path for args so the MCP server resolves correctly regardless
|
|
21
|
+
* of the cwd from which Claude Code is launched (matches install.sh behavior).
|
|
22
|
+
* @param {string} projectRoot
|
|
23
|
+
* @param {boolean} [dryRun]
|
|
24
|
+
*/
|
|
25
|
+
function writeMcpJson(projectRoot, dryRun = false) {
|
|
26
|
+
const mcpPath = path.join(projectRoot, '.mcp.json');
|
|
27
|
+
|
|
28
|
+
if (dryRun) {
|
|
29
|
+
dryRunLog(`merge claws entry into ${mcpPath}`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let config = {};
|
|
34
|
+
if (fs.existsSync(mcpPath)) {
|
|
35
|
+
try {
|
|
36
|
+
config = JSON.parse(_stripJsonc(fs.readFileSync(mcpPath, 'utf8')));
|
|
37
|
+
} catch {
|
|
38
|
+
process.stderr.write(` ! .mcp.json is malformed — preserving original, skipping merge\n`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
44
|
+
config.mcpServers.claws = {
|
|
45
|
+
command: 'node',
|
|
46
|
+
args: [path.join(projectRoot, '.claws-bin', 'mcp_server.js')],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const tmp = mcpPath + '.claws-tmp.' + process.pid;
|
|
50
|
+
fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
51
|
+
fs.renameSync(tmp, mcpPath);
|
|
52
|
+
|
|
53
|
+
// W7h-27: re-read and parse after rename to catch any FS-level corruption.
|
|
54
|
+
// Matches install.sh:1090-1093.
|
|
55
|
+
try {
|
|
56
|
+
JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
57
|
+
} catch (e) {
|
|
58
|
+
process.stderr.write(` ! .mcp.json post-write validation failed: ${e.message}\n`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Remove the claws entry from <projectRoot>/.mcp.json (uninstall path).
|
|
64
|
+
* @param {string} projectRoot
|
|
65
|
+
* @param {boolean} [dryRun]
|
|
66
|
+
*/
|
|
67
|
+
function removeMcpEntry(projectRoot, dryRun = false) {
|
|
68
|
+
const mcpPath = path.join(projectRoot, '.mcp.json');
|
|
69
|
+
if (!fs.existsSync(mcpPath)) return;
|
|
70
|
+
|
|
71
|
+
if (dryRun) {
|
|
72
|
+
dryRunLog(`remove claws entry from ${mcpPath}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let config;
|
|
77
|
+
try {
|
|
78
|
+
config = JSON.parse(_stripJsonc(fs.readFileSync(mcpPath, 'utf8')));
|
|
79
|
+
} catch {
|
|
80
|
+
process.stderr.write(` ! .mcp.json is malformed — skipping\n`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (config.mcpServers && config.mcpServers.claws) {
|
|
85
|
+
delete config.mcpServers.claws;
|
|
86
|
+
const tmp = mcpPath + '.claws-tmp.' + process.pid;
|
|
87
|
+
fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
88
|
+
fs.renameSync(tmp, mcpPath);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { writeMcpJson, removeMcpEntry };
|
package/lib/platform.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { spawnSync, execSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Find all installed editor CLIs (VS Code, Code Insiders, Cursor, Windsurf).
|
|
10
|
+
* Returns an array of { label, cliPath } for each editor found on the system.
|
|
11
|
+
* Mirrors install.sh's `for label in code code-insiders cursor windsurf` loop.
|
|
12
|
+
*
|
|
13
|
+
* Search order per editor:
|
|
14
|
+
* 1. CLAWS_VSCODE_CLI env override (code label only)
|
|
15
|
+
* 2. PATH lookup via which/where
|
|
16
|
+
* 3. Platform-specific known bundle/package locations
|
|
17
|
+
*
|
|
18
|
+
* @param {object} [_opts] - Internal overrides for testing only
|
|
19
|
+
* @returns {{ label: string, cliPath: string }[]}
|
|
20
|
+
*/
|
|
21
|
+
function findAllEditorClis(_opts = {}) {
|
|
22
|
+
const platform = _opts.platform !== undefined ? _opts.platform : process.platform;
|
|
23
|
+
const env = _opts.env !== undefined ? _opts.env : process.env;
|
|
24
|
+
const existsFn = _opts.existsFn !== undefined ? _opts.existsFn : fs.existsSync;
|
|
25
|
+
const spawnFn = _opts.spawnFn !== undefined ? _opts.spawnFn : spawnSync;
|
|
26
|
+
|
|
27
|
+
const localApp = env.LOCALAPPDATA || '';
|
|
28
|
+
const programFiles = env.ProgramFiles || 'C:\\Program Files';
|
|
29
|
+
const whichCmd = platform === 'win32' ? 'where' : 'which';
|
|
30
|
+
|
|
31
|
+
const EDITORS = [
|
|
32
|
+
{
|
|
33
|
+
label: 'code',
|
|
34
|
+
macBundle: '/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code',
|
|
35
|
+
linux: ['/usr/bin/code', '/snap/bin/code'],
|
|
36
|
+
win: [
|
|
37
|
+
path.join(localApp, 'Programs', 'Microsoft VS Code', 'bin', 'Code.cmd'),
|
|
38
|
+
path.join(programFiles, 'Microsoft VS Code', 'bin', 'Code.cmd'),
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: 'code-insiders',
|
|
43
|
+
macBundle: '/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders',
|
|
44
|
+
linux: ['/usr/bin/code-insiders', '/snap/bin/code-insiders'],
|
|
45
|
+
win: [
|
|
46
|
+
path.join(localApp, 'Programs', 'Microsoft VS Code Insiders', 'bin', 'Code - Insiders.cmd'),
|
|
47
|
+
path.join(programFiles, 'Microsoft VS Code Insiders', 'bin', 'Code - Insiders.cmd'),
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
label: 'cursor',
|
|
52
|
+
macBundle: '/Applications/Cursor.app/Contents/Resources/app/bin/cursor',
|
|
53
|
+
linux: ['/usr/bin/cursor', '/snap/bin/cursor', '/usr/local/bin/cursor'],
|
|
54
|
+
win: [
|
|
55
|
+
path.join(localApp, 'Programs', 'cursor', 'cursor.cmd'),
|
|
56
|
+
path.join(localApp, 'cursor', 'resources', 'app', 'bin', 'cursor.cmd'),
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
label: 'windsurf',
|
|
61
|
+
macBundle: '/Applications/Windsurf.app/Contents/Resources/app/bin/windsurf',
|
|
62
|
+
linux: ['/usr/bin/windsurf', '/snap/bin/windsurf', '/usr/local/bin/windsurf'],
|
|
63
|
+
win: [
|
|
64
|
+
path.join(localApp, 'Programs', 'Windsurf', 'windsurf.cmd'),
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const results = [];
|
|
70
|
+
for (const editor of EDITORS) {
|
|
71
|
+
let found = null;
|
|
72
|
+
|
|
73
|
+
// 1. CLAWS_VSCODE_CLI env override (code label only — backward compat)
|
|
74
|
+
if (editor.label === 'code') {
|
|
75
|
+
const override = env.CLAWS_VSCODE_CLI;
|
|
76
|
+
if (override && existsFn(override)) found = override;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 2. PATH lookup
|
|
80
|
+
if (!found) {
|
|
81
|
+
const r = spawnFn(whichCmd, [editor.label], { encoding: 'utf8', stdio: 'pipe' });
|
|
82
|
+
if (r.status === 0 && r.stdout) {
|
|
83
|
+
const first = r.stdout.trim().split('\n')[0];
|
|
84
|
+
if (first) found = first;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 3. Platform-specific known bundle/package locations
|
|
89
|
+
if (!found) {
|
|
90
|
+
const candidates = platform === 'darwin'
|
|
91
|
+
? (editor.macBundle ? [editor.macBundle] : [])
|
|
92
|
+
: platform === 'win32'
|
|
93
|
+
? editor.win
|
|
94
|
+
: editor.linux;
|
|
95
|
+
for (const c of candidates) {
|
|
96
|
+
if (existsFn(c)) { found = c; break; }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (found) results.push({ label: editor.label, cliPath: found });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return results;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Find the VS Code CLI executable path.
|
|
108
|
+
* Kept for backward compatibility — returns the first match from findAllEditorClis().
|
|
109
|
+
*
|
|
110
|
+
* @param {object} [_opts] - Internal overrides for testing only
|
|
111
|
+
* @returns {string|null}
|
|
112
|
+
*/
|
|
113
|
+
function findCodeCli(_opts = {}) {
|
|
114
|
+
const env = _opts.env !== undefined ? _opts.env : process.env;
|
|
115
|
+
const existsFn = _opts.existsFn !== undefined ? _opts.existsFn : fs.existsSync;
|
|
116
|
+
|
|
117
|
+
// Env override takes precedence (retained for backward compat)
|
|
118
|
+
const override = env.CLAWS_VSCODE_CLI;
|
|
119
|
+
if (override && existsFn(override)) return override;
|
|
120
|
+
|
|
121
|
+
const found = findAllEditorClis(_opts);
|
|
122
|
+
return found.length > 0 ? found[0].cliPath : null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Returns true if homeDir contains "OneDrive".
|
|
127
|
+
* @param {string} homeDir
|
|
128
|
+
* @returns {boolean}
|
|
129
|
+
*/
|
|
130
|
+
function detectOneDrivePath(homeDir) {
|
|
131
|
+
return homeDir.includes('OneDrive');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Returns a warning string when homeDir is suspiciously long (>100 chars) or
|
|
136
|
+
* OneDrive-rooted; returns null otherwise.
|
|
137
|
+
* @param {string} homeDir
|
|
138
|
+
* @returns {string|null}
|
|
139
|
+
*/
|
|
140
|
+
function longPathPreflight(homeDir) {
|
|
141
|
+
if (homeDir.length > 100) {
|
|
142
|
+
return `Warning: home directory path is very long (${homeDir.length} chars). Node on Windows may hit the 260-character path limit.`;
|
|
143
|
+
}
|
|
144
|
+
if (detectOneDrivePath(homeDir)) {
|
|
145
|
+
return `Warning: home directory appears to be OneDrive-synced (${homeDir}). Use fs.realpathSync() on all install paths to resolve symlinks.`;
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Returns a PowerShell one-liner to add a Windows Defender exclusion for
|
|
152
|
+
* installPath; returns null on non-Windows.
|
|
153
|
+
* @param {string} installPath
|
|
154
|
+
* @param {object} [_opts] - Internal overrides for testing only
|
|
155
|
+
* @returns {string|null}
|
|
156
|
+
*/
|
|
157
|
+
function defenderExclusionCommand(installPath, _opts = {}) {
|
|
158
|
+
const platform = _opts.platform !== undefined ? _opts.platform : process.platform;
|
|
159
|
+
if (platform !== 'win32') return null;
|
|
160
|
+
return `Add-MpPreference -ExclusionPath "${installPath}"`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Returns the default shell rc-file path for the current user.
|
|
165
|
+
*
|
|
166
|
+
* Platform mapping:
|
|
167
|
+
* zsh → ~/.zshrc
|
|
168
|
+
* bash → ~/.bashrc
|
|
169
|
+
* fish → ~/.config/fish/conf.d/claws.fish (conf.d is auto-sourced by fish)
|
|
170
|
+
* win32 PowerShell → $PROFILE (resolved via powershell -c $PROFILE)
|
|
171
|
+
*
|
|
172
|
+
* @param {object} [_opts] - Internal overrides for testing only
|
|
173
|
+
* @returns {string}
|
|
174
|
+
*/
|
|
175
|
+
function getDefaultShellRcFile(_opts = {}) {
|
|
176
|
+
const platform = _opts.platform !== undefined ? _opts.platform : process.platform;
|
|
177
|
+
const env = _opts.env !== undefined ? _opts.env : process.env;
|
|
178
|
+
const home = _opts.home !== undefined ? _opts.home : os.homedir();
|
|
179
|
+
const execFn = _opts.execFn !== undefined ? _opts.execFn : execSync;
|
|
180
|
+
|
|
181
|
+
if (platform === 'win32') {
|
|
182
|
+
try {
|
|
183
|
+
const profile = execFn('powershell -c $PROFILE', { encoding: 'utf8' }).trim();
|
|
184
|
+
if (profile) return profile;
|
|
185
|
+
} catch (_) {
|
|
186
|
+
// powershell not available — use canonical default
|
|
187
|
+
}
|
|
188
|
+
return path.join(home, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const shell = env.SHELL || '';
|
|
192
|
+
if (shell.includes('zsh')) return path.join(home, '.zshrc');
|
|
193
|
+
if (shell.includes('fish')) return path.join(home, '.config', 'fish', 'conf.d', 'claws.fish');
|
|
194
|
+
if (shell.includes('bash')) return path.join(home, '.bashrc');
|
|
195
|
+
|
|
196
|
+
// No $SHELL set — fall back by platform
|
|
197
|
+
if (platform === 'darwin') return path.join(home, '.zshrc');
|
|
198
|
+
return path.join(home, '.bashrc');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Dry-run logger — writes a [dry-run] prefixed line to stdout.
|
|
203
|
+
* Used throughout the installer when --dry-run is active.
|
|
204
|
+
* @param {string} msg
|
|
205
|
+
*/
|
|
206
|
+
function dryRunLog(msg) {
|
|
207
|
+
process.stdout.write(`[dry-run] ${msg}\n`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Detect the node-pty prebuild key for the current platform and architecture.
|
|
212
|
+
* Maps os.platform() + os.arch() to the directory name used by bundle-native.mjs:
|
|
213
|
+
* darwin-x64 | darwin-arm64 | linux-x64 | linux-arm64
|
|
214
|
+
* Mirrors the case block in scripts/install.sh (CLAWS_PLATFORM_ARCH).
|
|
215
|
+
* @param {object} [_opts]
|
|
216
|
+
* @param {string} [_opts.platform] - os.platform() override for testing
|
|
217
|
+
* @param {string} [_opts.arch] - os.arch() override for testing
|
|
218
|
+
* @returns {string}
|
|
219
|
+
*/
|
|
220
|
+
function detectPlatformArch(_opts = {}) {
|
|
221
|
+
const platform = _opts.platform !== undefined ? _opts.platform : os.platform();
|
|
222
|
+
const arch = _opts.arch !== undefined ? _opts.arch : os.arch();
|
|
223
|
+
const normArch = arch === 'arm64' ? 'arm64' : 'x64';
|
|
224
|
+
switch (platform) {
|
|
225
|
+
case 'darwin': return `darwin-${normArch}`;
|
|
226
|
+
case 'linux': return `linux-${normArch}`;
|
|
227
|
+
default: return 'linux-x64';
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
module.exports = {
|
|
232
|
+
findCodeCli,
|
|
233
|
+
findAllEditorClis,
|
|
234
|
+
detectOneDrivePath,
|
|
235
|
+
longPathPreflight,
|
|
236
|
+
defenderExclusionCommand,
|
|
237
|
+
getDefaultShellRcFile,
|
|
238
|
+
dryRunLog,
|
|
239
|
+
detectPlatformArch,
|
|
240
|
+
};
|
package/lib/preflight.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Run preflight checks. Returns { failures, warnings }.
|
|
9
|
+
* failures → hard errors that abort the install.
|
|
10
|
+
* warnings → WARN-not-block advisories printed before continuing.
|
|
11
|
+
*
|
|
12
|
+
* VS Code CLI presence is intentionally NOT checked here — a missing editor CLI
|
|
13
|
+
* is a soft warning emitted by _installExtension itself (phase 7), not a
|
|
14
|
+
* hard failure. install.sh behaves the same way (warn + continue).
|
|
15
|
+
*
|
|
16
|
+
* @param {object} [opts]
|
|
17
|
+
* @returns {{ failures: string[], warnings: string[] }}
|
|
18
|
+
*/
|
|
19
|
+
function run(opts = {}) {
|
|
20
|
+
const failures = [];
|
|
21
|
+
const warnings = [];
|
|
22
|
+
|
|
23
|
+
// ── Hard failures ──────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
// Node >= 18
|
|
26
|
+
const major = Number(process.versions.node.split('.')[0]);
|
|
27
|
+
if (major < 18) {
|
|
28
|
+
failures.push(`Node.js ≥ 18 required (found ${process.version})`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// git in PATH (any version — version check is a soft warning below)
|
|
32
|
+
const git = spawnSync('git', ['--version'], { encoding: 'utf8', stdio: 'pipe' });
|
|
33
|
+
if (git.status !== 0) {
|
|
34
|
+
failures.push('git not found — install git: https://git-scm.com');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// HOME writable
|
|
38
|
+
const home = os.homedir();
|
|
39
|
+
try {
|
|
40
|
+
fs.accessSync(home, fs.constants.W_OK);
|
|
41
|
+
} catch {
|
|
42
|
+
failures.push(`Home directory not writable: ${home}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Soft warnings (WARN-not-block) ──────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
// git >= 2
|
|
48
|
+
if (git.status === 0) {
|
|
49
|
+
const m = (git.stdout || '').match(/git version (\d+)/);
|
|
50
|
+
if (m && Number(m[1]) < 2) {
|
|
51
|
+
warnings.push(`git ≥ 2 recommended (found ${(git.stdout || '').trim()}) — some git operations may fail`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// npm >= 7
|
|
56
|
+
const npm = spawnSync('npm', ['--version'], { encoding: 'utf8', stdio: 'pipe' });
|
|
57
|
+
if (npm.status === 0) {
|
|
58
|
+
const npmMajor = Number((npm.stdout || '').trim().split('.')[0]);
|
|
59
|
+
if (npmMajor < 7) {
|
|
60
|
+
warnings.push(`npm ≥ 7 recommended (found ${(npm.stdout || '').trim()}) — some install steps may fail`);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
warnings.push('npm not found in PATH — extension build may fail');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// C++ toolchain (needed by node-gyp / @electron/rebuild)
|
|
67
|
+
_checkCppToolchain(warnings);
|
|
68
|
+
|
|
69
|
+
// python3 (node-gyp fallback); on Windows Python 3 ships as `python`, try that as fallback
|
|
70
|
+
const py = spawnSync('python3', ['--version'], { encoding: 'utf8', stdio: 'pipe' });
|
|
71
|
+
if (py.status !== 0) {
|
|
72
|
+
const pyFallback = spawnSync('python', ['--version'], { encoding: 'utf8', stdio: 'pipe' });
|
|
73
|
+
const pyFallbackOut = (pyFallback.stdout || '') + (pyFallback.stderr || '');
|
|
74
|
+
if (pyFallback.status !== 0 || !pyFallbackOut.includes('Python 3')) {
|
|
75
|
+
warnings.push('python3 not found — node-gyp builds may fail if native modules need recompiling');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Disk space >= 512 MB free in HOME
|
|
80
|
+
_checkDiskSpace(home, warnings);
|
|
81
|
+
|
|
82
|
+
return { failures, warnings };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check for a C++ build toolchain. Emits a warning (not a failure) if missing.
|
|
87
|
+
* darwin: Xcode Command Line Tools (clang)
|
|
88
|
+
* linux: g++ or make
|
|
89
|
+
* win32: cl.exe or msbuild.exe (install.ps1 already handles this for Windows)
|
|
90
|
+
* @param {string[]} warnings
|
|
91
|
+
*/
|
|
92
|
+
function _checkCppToolchain(warnings) {
|
|
93
|
+
const platform = process.platform;
|
|
94
|
+
|
|
95
|
+
if (platform === 'win32') {
|
|
96
|
+
const cl = spawnSync('cl.exe', [], { encoding: 'utf8', stdio: 'pipe' });
|
|
97
|
+
const msbuild = spawnSync('msbuild.exe', [], { encoding: 'utf8', stdio: 'pipe' });
|
|
98
|
+
if (cl.status === null && msbuild.status === null) {
|
|
99
|
+
warnings.push(
|
|
100
|
+
'C++ build tools not found (cl.exe / msbuild.exe) — run: winget install Microsoft.VisualStudio.BuildTools'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (platform === 'darwin') {
|
|
107
|
+
const clang = spawnSync('clang', ['--version'], { encoding: 'utf8', stdio: 'pipe' });
|
|
108
|
+
if (clang.status !== 0) {
|
|
109
|
+
warnings.push(
|
|
110
|
+
'Xcode Command Line Tools not found — run: xcode-select --install'
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// linux
|
|
117
|
+
const gpp = spawnSync('g++', ['--version'], { encoding: 'utf8', stdio: 'pipe' });
|
|
118
|
+
const make = spawnSync('make', ['--version'], { encoding: 'utf8', stdio: 'pipe' });
|
|
119
|
+
if (gpp.status !== 0 || make.status !== 0) {
|
|
120
|
+
warnings.push(
|
|
121
|
+
'C++ build tools not found (g++ / make) — run: sudo apt-get install build-essential (or distro equivalent)'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check free disk space in the given directory. Warns if < 512 MB.
|
|
128
|
+
* Uses `df -k` on POSIX; skips silently on win32.
|
|
129
|
+
* @param {string} dir
|
|
130
|
+
* @param {string[]} warnings
|
|
131
|
+
*/
|
|
132
|
+
function _checkDiskSpace(dir, warnings) {
|
|
133
|
+
if (process.platform === 'win32') return;
|
|
134
|
+
|
|
135
|
+
const df = spawnSync('df', ['-k', dir], { encoding: 'utf8', stdio: 'pipe' });
|
|
136
|
+
if (df.status !== 0) return;
|
|
137
|
+
|
|
138
|
+
const lines = (df.stdout || '').trim().split('\n');
|
|
139
|
+
if (lines.length < 2) return;
|
|
140
|
+
|
|
141
|
+
// df -k output: Filesystem 1K-blocks Used Available Capacity Mounted
|
|
142
|
+
// column index 3 is "Available" in blocks of 1024 bytes.
|
|
143
|
+
const cols = lines[lines.length - 1].trim().split(/\s+/);
|
|
144
|
+
const freeKb = Number(cols[3]);
|
|
145
|
+
if (!isNaN(freeKb) && freeKb < 512 * 1024) {
|
|
146
|
+
warnings.push(
|
|
147
|
+
`Low disk space: ${Math.round(freeKb / 1024)} MB free in ${dir} (512 MB recommended)`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = { run };
|