openclawsetup 2.4.6 → 2.4.8
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/bin/cli.mjs +99 -4
- package/install.sh +20 -0
- package/package.json +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { execSync, spawnSync } from 'child_process';
|
|
14
14
|
import { existsSync, accessSync, constants as fsConstants, rmSync, readFileSync } from 'fs';
|
|
15
|
+
import http from 'http';
|
|
15
16
|
import { homedir, platform } from 'os';
|
|
16
17
|
import { join } from 'path';
|
|
17
18
|
import { createInterface } from 'readline';
|
|
@@ -187,6 +188,23 @@ function needsSudo() {
|
|
|
187
188
|
return true;
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
function fixNpmCacheOwnership() {
|
|
192
|
+
// sudo npm install 会把 ~/.npm 的文件 owner 改成 root
|
|
193
|
+
// 导致后续普通用户的 npx 无法写缓存(EACCES)
|
|
194
|
+
if (platform() === 'win32') return;
|
|
195
|
+
const npmCacheDir = join(homedir(), '.npm');
|
|
196
|
+
if (!existsSync(npmCacheDir)) return;
|
|
197
|
+
try {
|
|
198
|
+
accessSync(npmCacheDir, fsConstants.W_OK);
|
|
199
|
+
} catch {
|
|
200
|
+
const user = process.env.USER || process.env.LOGNAME;
|
|
201
|
+
if (user) {
|
|
202
|
+
log.hint('修复 npm 缓存目录权限...');
|
|
203
|
+
safeExec(`sudo chown -R ${user} "${npmCacheDir}"`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
190
208
|
function detectExistingInstall() {
|
|
191
209
|
const home = homedir();
|
|
192
210
|
const openclawDir = join(home, '.openclaw');
|
|
@@ -436,6 +454,7 @@ async function installOpenClaw() {
|
|
|
436
454
|
process.exit(1);
|
|
437
455
|
}
|
|
438
456
|
log.success(`OpenClaw 已安装: ${check.output}`);
|
|
457
|
+
fixNpmCacheOwnership();
|
|
439
458
|
return 'openclaw';
|
|
440
459
|
}
|
|
441
460
|
|
|
@@ -460,6 +479,7 @@ async function installOpenClaw() {
|
|
|
460
479
|
shell: true,
|
|
461
480
|
});
|
|
462
481
|
if (sudoResult.status === 0) {
|
|
482
|
+
fixNpmCacheOwnership();
|
|
463
483
|
log.success('OpenClaw CLI 安装完成(sudo)');
|
|
464
484
|
return 'openclaw';
|
|
465
485
|
}
|
|
@@ -1217,6 +1237,48 @@ async function runHealthCheck(cliName, autoFix = false) {
|
|
|
1217
1237
|
|
|
1218
1238
|
// ============ 交互式菜单 ============
|
|
1219
1239
|
|
|
1240
|
+
function testModelChat(port, token, model) {
|
|
1241
|
+
return new Promise((resolve) => {
|
|
1242
|
+
const postData = JSON.stringify({ model, input: '请用一句话回复你的模型名称' });
|
|
1243
|
+
const req = http.request({
|
|
1244
|
+
hostname: '127.0.0.1',
|
|
1245
|
+
port,
|
|
1246
|
+
path: '/v1/responses',
|
|
1247
|
+
method: 'POST',
|
|
1248
|
+
timeout: 30000,
|
|
1249
|
+
headers: {
|
|
1250
|
+
'Content-Type': 'application/json',
|
|
1251
|
+
'Authorization': `Bearer ${token}`,
|
|
1252
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
1253
|
+
},
|
|
1254
|
+
}, (res) => {
|
|
1255
|
+
let data = '';
|
|
1256
|
+
res.on('data', chunk => data += chunk);
|
|
1257
|
+
res.on('end', () => {
|
|
1258
|
+
try {
|
|
1259
|
+
const json = JSON.parse(data);
|
|
1260
|
+
const text = json.output?.[0]?.content?.[0]?.text;
|
|
1261
|
+
if (text) {
|
|
1262
|
+
resolve({ success: true, message: text });
|
|
1263
|
+
} else if (json.error) {
|
|
1264
|
+
resolve({ success: false, error: json.error.message || JSON.stringify(json.error) });
|
|
1265
|
+
} else {
|
|
1266
|
+
resolve({ success: false, error: `HTTP ${res.statusCode}: ${data.substring(0, 200)}` });
|
|
1267
|
+
}
|
|
1268
|
+
} catch {
|
|
1269
|
+
resolve({ success: false, error: `HTTP ${res.statusCode}: ${data.substring(0, 200)}` });
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
});
|
|
1273
|
+
req.on('timeout', () => { req.destroy(); resolve({ success: false, error: '请求超时 (30s)' }); });
|
|
1274
|
+
req.on('error', (e) => {
|
|
1275
|
+
resolve({ success: false, error: e.code === 'ECONNREFUSED' ? 'Gateway 未响应' : e.message });
|
|
1276
|
+
});
|
|
1277
|
+
req.write(postData);
|
|
1278
|
+
req.end();
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1220
1282
|
async function showStatusInfo(cliName) {
|
|
1221
1283
|
const config = getConfigInfo();
|
|
1222
1284
|
const port = config.port || 18789;
|
|
@@ -1252,12 +1314,45 @@ async function showStatusInfo(cliName) {
|
|
|
1252
1314
|
}
|
|
1253
1315
|
|
|
1254
1316
|
// 模型配置
|
|
1317
|
+
let primaryModel = '';
|
|
1255
1318
|
if (config.raw) {
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1319
|
+
try {
|
|
1320
|
+
const json = JSON.parse(config.raw);
|
|
1321
|
+
const hasProviders = json.models?.providers && Object.keys(json.models.providers).length > 0;
|
|
1322
|
+
primaryModel = json.agents?.defaults?.model?.primary || '';
|
|
1323
|
+
if (hasProviders) {
|
|
1324
|
+
console.log(colors.green(' ✓ 已配置 AI 模型'));
|
|
1325
|
+
if (primaryModel) {
|
|
1326
|
+
console.log(colors.gray(` 主模型: ${primaryModel}`));
|
|
1327
|
+
}
|
|
1328
|
+
} else {
|
|
1329
|
+
console.log(colors.yellow(' ⚠ 未配置模型,请先选择「配置模型」'));
|
|
1330
|
+
}
|
|
1331
|
+
} catch {
|
|
1332
|
+
const hasProviders = config.raw.includes('"providers"');
|
|
1333
|
+
if (hasProviders) {
|
|
1334
|
+
console.log(colors.green(' ✓ 已配置 AI 模型'));
|
|
1335
|
+
} else {
|
|
1336
|
+
console.log(colors.yellow(' ⚠ 未配置模型,请先选择「配置模型」'));
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// 模型对话测试
|
|
1342
|
+
if (config.token && config.token !== '<未配置>' && primaryModel) {
|
|
1343
|
+
console.log(colors.gray(' … 正在测试模型对话...'));
|
|
1344
|
+
const testResult = await testModelChat(port, config.token, primaryModel);
|
|
1345
|
+
if (testResult.success) {
|
|
1346
|
+
console.log(colors.green(' ✓ 模型对话正常'));
|
|
1347
|
+
if (testResult.message) {
|
|
1348
|
+
const reply = testResult.message.length > 60
|
|
1349
|
+
? testResult.message.substring(0, 60) + '...'
|
|
1350
|
+
: testResult.message;
|
|
1351
|
+
console.log(colors.gray(` 回复: ${reply}`));
|
|
1352
|
+
}
|
|
1259
1353
|
} else {
|
|
1260
|
-
console.log(colors.
|
|
1354
|
+
console.log(colors.red(` ✗ 模型对话失败: ${testResult.error}`));
|
|
1355
|
+
console.log(colors.yellow(' → 请选择「配置模型」检查 API Key 和节点配置'));
|
|
1261
1356
|
}
|
|
1262
1357
|
}
|
|
1263
1358
|
|
package/install.sh
CHANGED
|
@@ -258,6 +258,7 @@ install_node() {
|
|
|
258
258
|
# 验证安装
|
|
259
259
|
if command -v node &> /dev/null; then
|
|
260
260
|
log_success "Node.js 安装完成: $(node -v)"
|
|
261
|
+
fix_npm_cache_ownership
|
|
261
262
|
else
|
|
262
263
|
log_error "Node.js 安装失败"
|
|
263
264
|
echo ""
|
|
@@ -401,6 +402,22 @@ show_installed_info() {
|
|
|
401
402
|
echo " 飞书: npx openclaw-chat-cn@latest feishu"
|
|
402
403
|
}
|
|
403
404
|
|
|
405
|
+
# 修复 npm 缓存目录权限(sudo 安装后 ~/.npm 可能被 root 占用)
|
|
406
|
+
fix_npm_cache_ownership() {
|
|
407
|
+
local npm_cache="$HOME/.npm"
|
|
408
|
+
if [ -d "$npm_cache" ]; then
|
|
409
|
+
local current_user
|
|
410
|
+
current_user=$(whoami 2>/dev/null || echo "")
|
|
411
|
+
if [ -n "$current_user" ] && [ "$current_user" != "root" ]; then
|
|
412
|
+
# 检查是否有 root 拥有的文件
|
|
413
|
+
if find "$npm_cache" -maxdepth 1 -user root 2>/dev/null | grep -q .; then
|
|
414
|
+
log_info "修复 npm 缓存目录权限..."
|
|
415
|
+
sudo chown -R "$current_user" "$npm_cache" 2>/dev/null || true
|
|
416
|
+
fi
|
|
417
|
+
fi
|
|
418
|
+
fi
|
|
419
|
+
}
|
|
420
|
+
|
|
404
421
|
# 显示菜单并获取选择
|
|
405
422
|
show_menu() {
|
|
406
423
|
local choice=""
|
|
@@ -541,6 +558,9 @@ install_openclaw() {
|
|
|
541
558
|
log_info "开始安装 OpenClaw..."
|
|
542
559
|
echo ""
|
|
543
560
|
|
|
561
|
+
# 修复 npm 缓存权限(防止之前 sudo 导致的 EACCES)
|
|
562
|
+
fix_npm_cache_ownership
|
|
563
|
+
|
|
544
564
|
# 清除 npm 缓存确保获取最新版本
|
|
545
565
|
log_info "清除 npm 缓存..."
|
|
546
566
|
npm cache clean --force 2>/dev/null || true
|