yingclaw 2.4.5 → 2.5.1
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/README.md +1 -1
- package/bin/cli.js +35 -127
- package/lib/config.js +4 -67
- package/lib/install.js +86 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,7 +72,6 @@ claw config # 配置 API 连接
|
|
|
72
72
|
claw code # 接入 Claude Code 终端
|
|
73
73
|
claw desktop # 接入 Claude 桌面应用
|
|
74
74
|
claw switch # 快速切换厂商或模型
|
|
75
|
-
claw prompt # 配置系统提示词(按模型独立保存)
|
|
76
75
|
claw status # 查看当前配置,验证 Key 是否有效
|
|
77
76
|
claw update # 检查并升级到最新版本
|
|
78
77
|
|
|
@@ -82,6 +81,7 @@ claw reset # 清除所有 yingclaw 配置
|
|
|
82
81
|
|
|
83
82
|
claw install-claude # 安装 Claude Code
|
|
84
83
|
claw setup # 兼容旧命令:config + code
|
|
84
|
+
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
## 平台支持
|
package/bin/cli.js
CHANGED
|
@@ -5,8 +5,6 @@ const { select, input, confirm } = require('@inquirer/prompts');
|
|
|
5
5
|
const {
|
|
6
6
|
loadConfig,
|
|
7
7
|
saveConfig,
|
|
8
|
-
getSystemPrompt,
|
|
9
|
-
setSystemPrompt,
|
|
10
8
|
writeEnvToZshrc,
|
|
11
9
|
clearClaudeCodeEnv,
|
|
12
10
|
fetchModels,
|
|
@@ -23,7 +21,7 @@ const {
|
|
|
23
21
|
const { execSync, spawn, spawnSync } = require('child_process');
|
|
24
22
|
const pkg = require('../package.json');
|
|
25
23
|
const { buildMenuStatusLines, buildStatusView } = require('../lib/panel');
|
|
26
|
-
const { buildClaudeInstallCommand } = require('../lib/install');
|
|
24
|
+
const { buildClaudeInstallCommand, checkNodeEnv, getNodeInstallGuide, getInstallFailureHints } = require('../lib/install');
|
|
27
25
|
const { clearClaudeDesktopConfig, isDesktopConfigured, openClaudeDesktop, writeClaudeDesktopConfig } = require('../lib/desktop');
|
|
28
26
|
const { runDoctorChecks, summarize, STATUS_OK, STATUS_FAIL, STATUS_WARN, STATUS_INFO } = require('../lib/doctor');
|
|
29
27
|
|
|
@@ -237,14 +235,6 @@ async function showStatus() {
|
|
|
237
235
|
env: process.env,
|
|
238
236
|
});
|
|
239
237
|
|
|
240
|
-
const systemPromptText = getSystemPrompt(config);
|
|
241
|
-
if (systemPromptText) {
|
|
242
|
-
const preview = systemPromptText.length > 60 ? systemPromptText.slice(0, 60) + '…' : systemPromptText;
|
|
243
|
-
view.lines.push({ label: '系统提示词', value: preview });
|
|
244
|
-
} else {
|
|
245
|
-
view.lines.push({ label: '系统提示词', value: '未配置' });
|
|
246
|
-
}
|
|
247
|
-
|
|
248
238
|
const lines = view.lines.map(({ label, value }) => {
|
|
249
239
|
const coloredValue = label === '厂商'
|
|
250
240
|
? chalk.white.bold(value)
|
|
@@ -364,7 +354,7 @@ async function runConfigFlow({ writeCodeEnv = false } = {}) {
|
|
|
364
354
|
cfg = customConfig || { provider: providerKey, model, fastModel, apiKey, baseUrl: provider.baseUrl, availableModels };
|
|
365
355
|
saveConfig(cfg);
|
|
366
356
|
if (writeCodeEnv) {
|
|
367
|
-
({ file } = writeEnvToZshrc(cfg.baseUrl, cfg.apiKey, cfg.model, cfg.fastModel
|
|
357
|
+
({ file } = writeEnvToZshrc(cfg.baseUrl, cfg.apiKey, cfg.model, cfg.fastModel));
|
|
368
358
|
}
|
|
369
359
|
spinner.succeed(chalk.green(writeCodeEnv ? 'API 连接已保存,Claude Code 终端已接入' : 'API 连接已保存'));
|
|
370
360
|
} catch (e) {
|
|
@@ -412,6 +402,31 @@ program
|
|
|
412
402
|
if (!yes) return;
|
|
413
403
|
} catch {}
|
|
414
404
|
|
|
405
|
+
// 检查 npm / Node.js 环境
|
|
406
|
+
const nodeEnv = checkNodeEnv();
|
|
407
|
+
if (!nodeEnv.npmOk) {
|
|
408
|
+
console.log(boxen(
|
|
409
|
+
chalk.bold.red('未检测到 npm,无法安装 Claude Code\n\n') +
|
|
410
|
+
chalk.dim('Claude Code 通过 npm 安装,需要先安装 Node.js(含 npm)。\n\n') +
|
|
411
|
+
chalk.bold('安装方式:\n') +
|
|
412
|
+
getNodeInstallGuide().map(l => chalk.cyan(' ' + l)).join('\n') +
|
|
413
|
+
'\n\n' + chalk.dim('安装完成后重新运行 claw install-claude'),
|
|
414
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'red', margin: { top: 1, bottom: 1 } }
|
|
415
|
+
));
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (!nodeEnv.nodeOk) {
|
|
419
|
+
console.log(boxen(
|
|
420
|
+
chalk.bold.yellow(`Node.js 版本过低(当前 v${nodeEnv.nodeVersion},需要 ≥18)\n\n`) +
|
|
421
|
+
chalk.dim('Claude Code 要求 Node.js ≥18,当前版本可能导致安装或运行失败。\n\n') +
|
|
422
|
+
chalk.bold('升级方式:\n') +
|
|
423
|
+
getNodeInstallGuide().map(l => chalk.cyan(' ' + l)).join('\n'),
|
|
424
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'yellow', margin: { top: 1, bottom: 1 } }
|
|
425
|
+
));
|
|
426
|
+
const proceed = await confirm({ message: '仍然尝试安装?', default: false });
|
|
427
|
+
if (!proceed) return;
|
|
428
|
+
}
|
|
429
|
+
|
|
415
430
|
const network = await select({ loop: false,
|
|
416
431
|
message: chalk.cyan('你的网络环境'),
|
|
417
432
|
choices: [
|
|
@@ -433,10 +448,9 @@ program
|
|
|
433
448
|
console.log(chalk.green('\n✔ Claude Code 安装成功!'));
|
|
434
449
|
} else {
|
|
435
450
|
console.log(chalk.red('\n✘ 安装失败'));
|
|
451
|
+
const hints = getInstallFailureHints(result, chalk);
|
|
436
452
|
console.log(boxen(
|
|
437
|
-
|
|
438
|
-
chalk.cyan('npm config set registry https://registry.npmmirror.com\n') +
|
|
439
|
-
chalk.cyan('npm install -g @anthropic-ai/claude-code'),
|
|
453
|
+
hints.join(''),
|
|
440
454
|
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'yellow', margin: { top: 1, bottom: 1 } }
|
|
441
455
|
));
|
|
442
456
|
return;
|
|
@@ -492,7 +506,7 @@ program
|
|
|
492
506
|
const spinner = ora('写入 Claude Code 终端环境变量...').start();
|
|
493
507
|
let file;
|
|
494
508
|
try {
|
|
495
|
-
({ file } = writeEnvToZshrc(config.baseUrl, config.apiKey, config.model, config.fastModel
|
|
509
|
+
({ file } = writeEnvToZshrc(config.baseUrl, config.apiKey, config.model, config.fastModel));
|
|
496
510
|
spinner.succeed(chalk.green(`Claude Code 终端已接入 → ${file}`));
|
|
497
511
|
} catch (e) {
|
|
498
512
|
spinner.fail(chalk.red(`写入失败: ${e.message}`));
|
|
@@ -643,105 +657,6 @@ program
|
|
|
643
657
|
await offerDesktopSync(chalk, ora, newConfig);
|
|
644
658
|
});
|
|
645
659
|
|
|
646
|
-
program
|
|
647
|
-
.command('prompt')
|
|
648
|
-
.description('配置系统提示词')
|
|
649
|
-
.action(async () => {
|
|
650
|
-
const chalk = (await import('chalk')).default;
|
|
651
|
-
const ora = (await import('ora')).default;
|
|
652
|
-
const boxen = (await import('boxen')).default;
|
|
653
|
-
|
|
654
|
-
console.log(await getBanner());
|
|
655
|
-
|
|
656
|
-
const config = loadConfig();
|
|
657
|
-
if (!config) {
|
|
658
|
-
console.log(chalk.red('\n未配置 API 连接,请先运行: claw config\n'));
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
const provider = PROVIDERS[config.provider];
|
|
663
|
-
const providerName = config.providerName || provider?.name || config.provider;
|
|
664
|
-
const currentPrompt = getSystemPrompt(config);
|
|
665
|
-
|
|
666
|
-
console.log(chalk.dim(` 当前模型:${providerName} · ${config.model}\n`));
|
|
667
|
-
|
|
668
|
-
if (currentPrompt) {
|
|
669
|
-
const preview = currentPrompt.length > 300 ? currentPrompt.slice(0, 300) + '\n...' : currentPrompt;
|
|
670
|
-
console.log(boxen(
|
|
671
|
-
chalk.bold('当前系统提示词\n\n') + chalk.dim(preview),
|
|
672
|
-
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'cyan', margin: { top: 1, bottom: 1 } }
|
|
673
|
-
));
|
|
674
|
-
} else {
|
|
675
|
-
console.log(chalk.dim(' 当前模型未配置系统提示词\n'));
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
const action = await select({ loop: false,
|
|
679
|
-
message: chalk.cyan('操作'),
|
|
680
|
-
choices: [
|
|
681
|
-
{ name: currentPrompt ? '✏️ 编辑系统提示词' : '✏️ 设置系统提示词', value: 'edit' },
|
|
682
|
-
...(currentPrompt ? [{ name: '🗑 清除当前模型提示词', value: 'clear' }] : []),
|
|
683
|
-
{ name: chalk.dim('↩ 返回'), value: '__BACK__' },
|
|
684
|
-
],
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
if (action === '__BACK__') return;
|
|
688
|
-
|
|
689
|
-
if (action === 'clear') {
|
|
690
|
-
const yes = await confirm({ message: '确定清除该模型的系统提示词?', default: false });
|
|
691
|
-
if (!yes) { console.log(chalk.dim('已取消')); return; }
|
|
692
|
-
const spinner = ora('清除中...').start();
|
|
693
|
-
setSystemPrompt(config, null);
|
|
694
|
-
saveConfig(config);
|
|
695
|
-
try {
|
|
696
|
-
writeEnvToZshrc(config.baseUrl, config.apiKey, config.model, config.fastModel, {});
|
|
697
|
-
} catch {}
|
|
698
|
-
spinner.succeed(chalk.green('系统提示词已清除'));
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
if (currentPrompt) {
|
|
703
|
-
console.log(chalk.dim(' 直接输入新内容覆盖,留空并回车取消'));
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const newPrompt = await input({
|
|
707
|
-
message: chalk.cyan('系统提示词'),
|
|
708
|
-
default: currentPrompt || undefined,
|
|
709
|
-
validate: () => true,
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
if (!newPrompt || !newPrompt.trim()) {
|
|
713
|
-
console.log(chalk.dim('内容为空,已取消'));
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
const spinner = ora('保存中...').start();
|
|
718
|
-
setSystemPrompt(config, newPrompt);
|
|
719
|
-
saveConfig(config);
|
|
720
|
-
|
|
721
|
-
// 无论是否已有 clawai 块,都同步写入 shell RC / PowerShell Profile
|
|
722
|
-
let rcFile;
|
|
723
|
-
try {
|
|
724
|
-
({ file: rcFile } = writeEnvToZshrc(config.baseUrl, config.apiKey, config.model, config.fastModel, { systemPrompt: getSystemPrompt(config) }));
|
|
725
|
-
} catch {}
|
|
726
|
-
|
|
727
|
-
spinner.succeed(chalk.green('系统提示词已保存'));
|
|
728
|
-
|
|
729
|
-
if (process.platform === 'win32') {
|
|
730
|
-
console.log(boxen(
|
|
731
|
-
chalk.dim('• 通过菜单"启动 Claude Code"立即生效\n') +
|
|
732
|
-
chalk.dim('• 直接运行 claude 需重新打开 PowerShell 后生效\n') +
|
|
733
|
-
chalk.dim(' (已写入 PowerShell Profile,新窗口自动加载)'),
|
|
734
|
-
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
735
|
-
));
|
|
736
|
-
} else {
|
|
737
|
-
console.log(boxen(
|
|
738
|
-
chalk.dim('• 通过菜单"启动 Claude Code"立即生效\n') +
|
|
739
|
-
chalk.dim('• 终端直接运行 claude 需先执行:') + chalk.cyan(`source ${rcFile || '~/.zshrc'}`),
|
|
740
|
-
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
741
|
-
));
|
|
742
|
-
}
|
|
743
|
-
});
|
|
744
|
-
|
|
745
660
|
program
|
|
746
661
|
.command('status')
|
|
747
662
|
.description('查看当前配置和 Key 有效性')
|
|
@@ -1174,7 +1089,6 @@ async function runMenu() {
|
|
|
1174
1089
|
{ name: '📦 安装 Claude Code', value: 'install' },
|
|
1175
1090
|
{ name: config ? '🔑 重新配置 API 连接' : '🔑 配置 API 连接', value: 'config' },
|
|
1176
1091
|
{ name: '🔄 切换厂商或模型', value: 'switch', disabled: disabledHint },
|
|
1177
|
-
{ name: getSystemPrompt(config) ? '📝 系统提示词(已配置)' : '📝 配置系统提示词', value: 'prompt', disabled: !config && '需先配置 API 连接' },
|
|
1178
1092
|
{ name: '💻 接入 Claude Code 终端', value: 'code', disabled: disabledHint },
|
|
1179
1093
|
{ name: '🖥 接入 Claude 桌面应用', value: 'desktop', disabled: disabledHint },
|
|
1180
1094
|
{ name: '📊 查看当前配置', value: 'status', disabled: !config && '需先配置 API 连接' },
|
|
@@ -1201,17 +1115,12 @@ async function runMenu() {
|
|
|
1201
1115
|
if (resolvedAction === 'launch') {
|
|
1202
1116
|
const cfg = loadConfig();
|
|
1203
1117
|
if (!cfg || getConfigValidationMessage(cfg)) continue;
|
|
1204
|
-
const systemPromptText = getSystemPrompt(cfg);
|
|
1205
|
-
const launchArgs = systemPromptText ? ['--append-system-prompt', systemPromptText] : [];
|
|
1206
|
-
if (systemPromptText) {
|
|
1207
|
-
const preview = systemPromptText.length > 40 ? systemPromptText.slice(0, 40) + '…' : systemPromptText;
|
|
1208
|
-
console.log(chalk.dim(` 系统提示词已启用:${preview}`));
|
|
1209
|
-
}
|
|
1210
1118
|
await new Promise((resolve) => {
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
:
|
|
1119
|
+
const child = spawn('claude', [], {
|
|
1120
|
+
stdio: 'inherit',
|
|
1121
|
+
env: { ...process.env, ...buildClaudeEnv(cfg) },
|
|
1122
|
+
shell: process.platform === 'win32',
|
|
1123
|
+
});
|
|
1215
1124
|
child.on('error', () => {
|
|
1216
1125
|
console.log(chalk.yellow('\nClaude Code 未找到,请先选择"安装 Claude Code"'));
|
|
1217
1126
|
resolve();
|
|
@@ -1227,7 +1136,6 @@ async function runMenu() {
|
|
|
1227
1136
|
code: 'code',
|
|
1228
1137
|
'code-reset': 'code-reset',
|
|
1229
1138
|
switch: 'switch',
|
|
1230
|
-
prompt: 'prompt',
|
|
1231
1139
|
desktop: 'desktop',
|
|
1232
1140
|
'desktop-reset': 'desktop-reset',
|
|
1233
1141
|
status: 'status',
|
package/lib/config.js
CHANGED
|
@@ -128,26 +128,6 @@ function normalizeAnthropicBaseUrl(baseUrl) {
|
|
|
128
128
|
return url.toString().replace(/\/+$/, '');
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
function systemPromptKey(config) {
|
|
132
|
-
return `${config.provider}::${config.model}`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function getSystemPrompt(config) {
|
|
136
|
-
if (!config?.systemPrompts) return null;
|
|
137
|
-
return config.systemPrompts[systemPromptKey(config)] || null;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function setSystemPrompt(config, prompt) {
|
|
141
|
-
const key = systemPromptKey(config);
|
|
142
|
-
if (!config.systemPrompts) config.systemPrompts = {};
|
|
143
|
-
if (prompt && prompt.trim()) {
|
|
144
|
-
config.systemPrompts[key] = prompt.trim();
|
|
145
|
-
} else {
|
|
146
|
-
delete config.systemPrompts[key];
|
|
147
|
-
if (Object.keys(config.systemPrompts).length === 0) delete config.systemPrompts;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
131
|
function buildModelUrlCandidates(baseUrl) {
|
|
152
132
|
let url;
|
|
153
133
|
try { url = new URL(normalizeAnthropicBaseUrl(baseUrl)); } catch { return []; }
|
|
@@ -344,10 +324,10 @@ function shellQuote(value) {
|
|
|
344
324
|
}
|
|
345
325
|
|
|
346
326
|
// 构造完整的 clawai 环境变量块
|
|
347
|
-
function buildEnvBlock(baseUrl, apiKey, model, fastModel
|
|
327
|
+
function buildEnvBlock(baseUrl, apiKey, model, fastModel) {
|
|
348
328
|
const provider = providerKeyFromBaseUrl(baseUrl);
|
|
349
329
|
const env = buildClaudeEnv({ provider, baseUrl, apiKey, model, fastModel });
|
|
350
|
-
|
|
330
|
+
return [
|
|
351
331
|
'',
|
|
352
332
|
'# clawai-start',
|
|
353
333
|
`export ANTHROPIC_BASE_URL=${shellQuote(env.ANTHROPIC_BASE_URL)}`,
|
|
@@ -359,47 +339,9 @@ function buildEnvBlock(baseUrl, apiKey, model, fastModel, systemPrompt) {
|
|
|
359
339
|
`export ANTHROPIC_DEFAULT_HAIKU_MODEL=${shellQuote(env.ANTHROPIC_DEFAULT_HAIKU_MODEL)}`,
|
|
360
340
|
`export CLAUDE_CODE_SUBAGENT_MODEL=${shellQuote(env.CLAUDE_CODE_SUBAGENT_MODEL)}`,
|
|
361
341
|
`export CLAUDE_CODE_EFFORT_LEVEL=${shellQuote(env.CLAUDE_CODE_EFFORT_LEVEL)}`,
|
|
362
|
-
];
|
|
363
|
-
|
|
364
|
-
if (systemPrompt && systemPrompt.trim()) {
|
|
365
|
-
lines.push(`_claw_system_prompt=${shellQuote(systemPrompt.trim())}`);
|
|
366
|
-
lines.push(`claude() { command claude --append-system-prompt "$_claw_system_prompt" "$@"; }`);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
lines.push('# clawai-end', '');
|
|
370
|
-
return lines.join('\n');
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function buildPsBlock(systemPrompt) {
|
|
374
|
-
const escaped = systemPrompt.replace(/'/g, "''");
|
|
375
|
-
return [
|
|
376
|
-
'',
|
|
377
|
-
'# clawai-start',
|
|
378
|
-
`$_claw_system_prompt = '${escaped}'`,
|
|
379
|
-
`function claude { $claudePath = (Get-Command -Name claude.cmd -ErrorAction SilentlyContinue)?.Source; if (-not $claudePath) { $claudePath = 'claude' }; & $claudePath --append-system-prompt $_claw_system_prompt @args }`,
|
|
380
342
|
'# clawai-end',
|
|
381
343
|
'',
|
|
382
|
-
].join('\
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function writePowerShellProfile(systemPrompt, options = {}) {
|
|
386
|
-
const homeDir = options.homeDir || os.homedir();
|
|
387
|
-
const profilePaths = [
|
|
388
|
-
path.join(homeDir, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1'),
|
|
389
|
-
path.join(homeDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'),
|
|
390
|
-
];
|
|
391
|
-
const written = [];
|
|
392
|
-
for (const profilePath of profilePaths) {
|
|
393
|
-
try {
|
|
394
|
-
fs.mkdirSync(path.dirname(profilePath), { recursive: true });
|
|
395
|
-
const current = fs.existsSync(profilePath) ? fs.readFileSync(profilePath, 'utf8') : '';
|
|
396
|
-
const cleaned = current.replace(/\r?\n?# clawai-start[\s\S]*?# clawai-end\r?\n?/g, '');
|
|
397
|
-
const next = (systemPrompt && systemPrompt.trim()) ? cleaned + buildPsBlock(systemPrompt) : cleaned;
|
|
398
|
-
fs.writeFileSync(profilePath, next, 'utf8');
|
|
399
|
-
written.push(profilePath);
|
|
400
|
-
} catch {}
|
|
401
|
-
}
|
|
402
|
-
return written;
|
|
344
|
+
].join('\n');
|
|
403
345
|
}
|
|
404
346
|
|
|
405
347
|
// 写入或更新 shell 配置文件中的环境变量块
|
|
@@ -409,7 +351,6 @@ function writeEnvToZshrc(baseUrl, apiKey, model, fastModel, options = {}) {
|
|
|
409
351
|
const provider = providerKeyFromBaseUrl(baseUrl);
|
|
410
352
|
const env = buildClaudeEnv({ provider, baseUrl, apiKey, model, fastModel });
|
|
411
353
|
runWindowsEnvCommands(buildWindowsSetEnvCommands(env), options.runner || spawnSync);
|
|
412
|
-
writePowerShellProfile(options.systemPrompt, options);
|
|
413
354
|
return { result: 'updated', file: WINDOWS_ENV_LABEL };
|
|
414
355
|
}
|
|
415
356
|
|
|
@@ -423,7 +364,7 @@ function writeEnvToZshrc(baseUrl, apiKey, model, fastModel, options = {}) {
|
|
|
423
364
|
rcFile = path.join(os.homedir(), '.zshrc');
|
|
424
365
|
}
|
|
425
366
|
|
|
426
|
-
const block = buildEnvBlock(baseUrl, apiKey, model, fastModel
|
|
367
|
+
const block = buildEnvBlock(baseUrl, apiKey, model, fastModel);
|
|
427
368
|
const current = fs.existsSync(rcFile) ? fs.readFileSync(rcFile, 'utf8') : '';
|
|
428
369
|
|
|
429
370
|
// 兼容旧版(# clawai 单行块)和新版(# clawai-start...# clawai-end 多行块)
|
|
@@ -483,11 +424,7 @@ function resetConfig(options = {}) {
|
|
|
483
424
|
module.exports = {
|
|
484
425
|
loadConfig,
|
|
485
426
|
saveConfig,
|
|
486
|
-
getSystemPrompt,
|
|
487
|
-
setSystemPrompt,
|
|
488
427
|
writeEnvToZshrc,
|
|
489
|
-
writePowerShellProfile,
|
|
490
|
-
buildPsBlock,
|
|
491
428
|
buildWindowsSetEnvCommands,
|
|
492
429
|
buildWindowsClearEnvCommands,
|
|
493
430
|
clearClaudeCodeEnv,
|
package/lib/install.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
|
|
1
3
|
function buildClaudeInstallCommand(network) {
|
|
2
4
|
const args = ['install', '-g', '@anthropic-ai/claude-code'];
|
|
3
5
|
if (network === 'cn') {
|
|
@@ -6,4 +8,87 @@ function buildClaudeInstallCommand(network) {
|
|
|
6
8
|
return { command: 'npm', args };
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
function checkNodeEnv() {
|
|
12
|
+
const nodeVersion = process.versions.node;
|
|
13
|
+
const nodeMajor = parseInt(nodeVersion.split('.')[0], 10);
|
|
14
|
+
|
|
15
|
+
let npmVersion = null;
|
|
16
|
+
try {
|
|
17
|
+
// Windows 上 npm 是 .cmd 文件,必须加 shell: true 才能找到
|
|
18
|
+
npmVersion = execSync('npm --version', {
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
21
|
+
shell: process.platform === 'win32',
|
|
22
|
+
}).trim();
|
|
23
|
+
} catch {}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
nodeVersion,
|
|
27
|
+
nodeMajor,
|
|
28
|
+
nodeOk: nodeMajor >= 18,
|
|
29
|
+
npmVersion,
|
|
30
|
+
npmOk: !!npmVersion,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getNodeInstallGuide() {
|
|
35
|
+
const platform = process.platform;
|
|
36
|
+
if (platform === 'darwin') {
|
|
37
|
+
return [
|
|
38
|
+
'Homebrew: brew install node',
|
|
39
|
+
'官方安装包: https://nodejs.org (选 LTS 版本)',
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
if (platform === 'win32') {
|
|
43
|
+
return [
|
|
44
|
+
'官方安装包: https://nodejs.org (选 LTS 版本)',
|
|
45
|
+
'winget: winget install OpenJS.NodeJS.LTS',
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
return [
|
|
49
|
+
'Ubuntu/Debian: sudo apt install nodejs npm',
|
|
50
|
+
'CentOS/RHEL: sudo yum install nodejs npm',
|
|
51
|
+
'nvm(推荐): https://github.com/nvm-sh/nvm',
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 根据 spawnSync 结果判断失败原因,返回对应的提示内容(chalk 字符串数组)。
|
|
57
|
+
* @param {object} result spawnSync 返回值
|
|
58
|
+
* @param {Function} chalk chalk 实例
|
|
59
|
+
* @returns {string[]}
|
|
60
|
+
*/
|
|
61
|
+
function getInstallFailureHints(result, chalk) {
|
|
62
|
+
// spawnSync 找不到命令(npm 在执行时消失或环境问题)
|
|
63
|
+
if (result.status === null && result.error?.code === 'ENOENT') {
|
|
64
|
+
return [
|
|
65
|
+
chalk.bold.red('npm 命令未找到\n\n'),
|
|
66
|
+
chalk.dim('安装 Node.js 后重试:\n'),
|
|
67
|
+
...getNodeInstallGuide().map(l => chalk.cyan(' ' + l)),
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 尝试从 stderr 判断权限错误(stdio inherit 时 stderr 为 null,兜底靠文字提示)
|
|
72
|
+
const stderr = result.stderr ? result.stderr.toString() : '';
|
|
73
|
+
if (stderr.includes('EACCES') || stderr.includes('permission denied') || stderr.includes('Access is denied')) {
|
|
74
|
+
const fixes = process.platform === 'win32'
|
|
75
|
+
? ['以管理员身份运行 PowerShell / CMD,然后重试']
|
|
76
|
+
: [
|
|
77
|
+
'sudo npm install -g @anthropic-ai/claude-code',
|
|
78
|
+
'或修复 npm 全局目录权限:https://docs.npmjs.com/resolving-eacces-permissions-errors',
|
|
79
|
+
];
|
|
80
|
+
return [
|
|
81
|
+
chalk.bold.red('权限不足(EACCES)\n\n'),
|
|
82
|
+
...fixes.map(l => chalk.cyan(' ' + l)),
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 通用失败:显示手动安装命令
|
|
87
|
+
return [
|
|
88
|
+
chalk.bold('手动安装:\n\n'),
|
|
89
|
+
chalk.cyan('npm config set registry https://registry.npmmirror.com\n'),
|
|
90
|
+
chalk.cyan('npm install -g @anthropic-ai/claude-code'),
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { buildClaudeInstallCommand, checkNodeEnv, getNodeInstallGuide, getInstallFailureHints };
|