feat: Node.js path scanning + manual path input + git HTTPS auto-fix (v0.4.2)

- Add scan_node_paths: auto-scan C/D/E/F/G drives for Node.js installations
- Add check_node_at_path: verify Node.js at user-specified directory
- Add save_custom_node_path: persist custom path to ~/.openclaw/clawpanel.json
- enhanced_path() now loads saved custom path and applies to all commands
- Windows enhanced_path: scan Program Files, LOCALAPPDATA, APPDATA, common drives
- Auto git config HTTPS-instead-of-SSH before npm install (fixes exit 128)
- Setup page: auto-scan button + manual path input when Node.js not detected
- Error diagnosis: add EPERM, MODULE_NOT_FOUND, SSH publickey patterns
- README: expanded troubleshooting section
This commit is contained in:
晴天
2026-03-05 22:30:19 +08:00
parent b1b95e5a11
commit 6ca4267970
14 changed files with 382 additions and 42 deletions

View File

@@ -10,6 +10,15 @@
export function diagnoseInstallError(errStr) {
const s = errStr.toLowerCase()
// git SSH 权限问题(有 git 但没配 SSH Key
if (s.includes('permission denied (publickey)') || s.includes('ssh://git@github')) {
return {
title: '安装失败 — Git SSH 权限',
hint: '依赖包用了 SSH 协议拉取代码,但你没配 GitHub SSH Key。运行以下命令改用 HTTPS',
command: 'git config --global url."https://github.com/".insteadOf ssh://git@github.com/',
}
}
// git 未安装exit 128 + access rights
if (s.includes('code 128') || s.includes('exit 128') || s.includes('access rights')) {
return {
@@ -19,6 +28,24 @@ export function diagnoseInstallError(errStr) {
}
}
// EPERM文件被占用/权限问题)
if (s.includes('eperm') || s.includes('operation not permitted')) {
return {
title: '安装失败 — 文件被占用',
hint: '有文件被锁定无法写入。先关闭所有 ClawPanel 和 Node.js 进程,然后在管理员终端手动安装:',
command: 'npm install -g @qingchencloud/openclaw-zh --registry https://registry.npmmirror.com',
}
}
// MODULE_NOT_FOUND安装不完整
if (s.includes('module_not_found') || s.includes('cannot find module')) {
return {
title: '安装不完整',
hint: '上次安装可能中断了。先清理残留再重装:',
command: 'npm cache clean --force && npm install -g @qingchencloud/openclaw-zh --registry https://registry.npmmirror.com',
}
}
// ENOENT文件找不到
if (s.includes('enoent') || s.includes('-4058') || s.includes('code -4058')) {
return {

View File

@@ -287,6 +287,9 @@ export const api = {
// 安装/部署
checkInstallation: () => cachedInvoke('check_installation', {}, 60000),
checkNode: () => cachedInvoke('check_node', {}, 60000),
checkNodeAtPath: (nodeDir) => invoke('check_node_at_path', { node_dir: nodeDir }),
scanNodePaths: () => invoke('scan_node_paths'),
saveCustomNodePath: (nodeDir) => invoke('save_custom_node_path', { node_dir: nodeDir }),
getDeployConfig: () => cachedInvoke('get_deploy_config'),
patchModelVision: () => invoke('patch_model_vision'),
checkPanelUpdate: () => invoke('check_panel_update'),

View File

@@ -86,11 +86,24 @@ function renderSteps(page, { node, cliOk, config }) {
</p>
<a class="btn btn-primary btn-sm" href="https://nodejs.org/" target="_blank" rel="noopener">下载 Node.js</a>
<span class="form-hint" style="margin-left:8px">安装后点击「重新检测」</span>
${isMacPlatform() ? `
<div style="margin-top:var(--space-sm);padding:8px 12px;background:var(--bg-tertiary);border-radius:var(--radius-sm);font-size:var(--font-size-xs);color:var(--text-secondary);line-height:1.6">
<strong>已经装了但检测不到?</strong> macOS 上从 Finder 启动可能找不到 Node.js。试试关掉 ClawPanel 后从终端启动:<br>
<code style="background:var(--bg-secondary);padding:2px 6px;border-radius:3px;user-select:all">open /Applications/ClawPanel.app</code>
</div>` : ''}`
<strong>已经装了但检测不到?</strong>
${isMacPlatform()
? `macOS 上从 Finder 启动可能找不到 Node.js。试试关掉 ClawPanel 后从终端启动:<br>
<code style="background:var(--bg-secondary);padding:2px 6px;border-radius:3px;user-select:all">open /Applications/ClawPanel.app</code>`
: `安装 Node.js 后需要<strong>重启 ClawPanel</strong>,新的环境变量才能生效。`
}
<div style="margin-top:8px;display:flex;gap:6px;align-items:center;flex-wrap:wrap">
<button class="btn btn-secondary btn-sm" id="btn-scan-node" style="font-size:11px;padding:3px 10px">🔍 自动扫描</button>
<span style="color:var(--text-tertiary)">或手动指定路径:</span>
</div>
<div style="margin-top:6px;display:flex;gap:6px">
<input id="input-node-path" type="text" placeholder="${isMacPlatform() ? '/usr/local/bin' : 'F:\\\\AI\\\\Node'}"
style="flex:1;padding:4px 8px;border:1px solid var(--border-primary);border-radius:var(--radius-sm);background:var(--bg-secondary);color:var(--text-primary);font-size:11px;font-family:monospace">
<button class="btn btn-primary btn-sm" id="btn-check-path" style="font-size:11px;padding:3px 10px">检测</button>
</div>
<div id="scan-result" style="margin-top:6px;display:none"></div>
</div>`
}
</div>
`
@@ -174,6 +187,66 @@ function bindEvents(page, nodeOk) {
window.location.hash = '/dashboard'
})
// 自动扫描 Node.js
page.querySelector('#btn-scan-node')?.addEventListener('click', async () => {
const btn = page.querySelector('#btn-scan-node')
const resultEl = page.querySelector('#scan-result')
btn.disabled = true
btn.textContent = '扫描中...'
resultEl.style.display = 'block'
resultEl.innerHTML = '<span style="color:var(--text-tertiary)">正在扫描常见安装路径...</span>'
try {
const results = await api.scanNodePaths()
if (results.length === 0) {
resultEl.innerHTML = '<span style="color:var(--warning)">未找到 Node.js 安装,请手动指定路径或下载安装。</span>'
} else {
resultEl.innerHTML = results.map(r =>
`<div style="display:flex;align-items:center;gap:6px;margin-top:4px">
<span style="color:var(--success)">✓</span>
<code style="flex:1;background:var(--bg-secondary);padding:2px 6px;border-radius:3px;font-size:11px">${r.path}</code>
<span style="font-size:11px;color:var(--text-tertiary)">${r.version}</span>
<button class="btn btn-primary btn-sm btn-use-path" data-path="${r.path}" style="font-size:10px;padding:2px 8px">使用</button>
</div>`
).join('')
resultEl.querySelectorAll('.btn-use-path').forEach(b => {
b.addEventListener('click', async () => {
await api.saveCustomNodePath(b.dataset.path)
toast('Node.js 路径已保存,正在重新检测...', 'success')
setTimeout(() => window.location.reload(), 500)
})
})
}
} catch (e) {
resultEl.innerHTML = `<span style="color:var(--danger)">扫描失败: ${e}</span>`
} finally {
btn.disabled = false
btn.textContent = '🔍 自动扫描'
}
})
// 手动指定路径检测
page.querySelector('#btn-check-path')?.addEventListener('click', async () => {
const input = page.querySelector('#input-node-path')
const resultEl = page.querySelector('#scan-result')
const dir = input?.value?.trim()
if (!dir) { toast('请输入 Node.js 安装目录', 'warning'); return }
resultEl.style.display = 'block'
resultEl.innerHTML = '<span style="color:var(--text-tertiary)">检测中...</span>'
try {
const result = await api.checkNodeAtPath(dir)
if (result.installed) {
await api.saveCustomNodePath(dir)
resultEl.innerHTML = `<span style="color:var(--success)">✓ 找到 Node.js ${result.version},路径已保存</span>`
toast('Node.js 路径已保存,正在重新检测...', 'success')
setTimeout(() => window.location.reload(), 500)
} else {
resultEl.innerHTML = `<span style="color:var(--warning)">该目录下未找到 node 可执行文件,请确认路径正确。</span>`
}
} catch (e) {
resultEl.innerHTML = `<span style="color:var(--danger)">检测失败: ${e}</span>`
}
})
// 一键安装
const installBtn = page.querySelector('#btn-install')
if (!installBtn || !nodeOk) return