yingclaw 2.3.1 → 2.4.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/README.md +1 -20
- package/bin/cli.js +86 -158
- package/lib/config.js +34 -6
- package/lib/panel.js +0 -3
- package/package.json +1 -1
- package/lib/opencode.js +0 -162
package/README.md
CHANGED
|
@@ -50,23 +50,6 @@ claw desktop
|
|
|
50
50
|
```
|
|
51
51
|
将配置写入 Claude Desktop 第三方推理本地配置。macOS 会自动重启 Claude Desktop;Windows 需手动重新打开。
|
|
52
52
|
|
|
53
|
-
接入 opencode:
|
|
54
|
-
```bash
|
|
55
|
-
claw opencode
|
|
56
|
-
```
|
|
57
|
-
使用当前厂商接入 opencode。DeepSeek 等已知厂商会优先走 opencode 内置 provider;真正的 Anthropic 兼容自定义接口可使用:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
claw opencode-custom
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
两种方式都会写入 `~/.config/opencode/opencode.json` 和系统提示词文件,复用当前 API 连接。
|
|
64
|
-
|
|
65
|
-
启动 opencode:
|
|
66
|
-
```bash
|
|
67
|
-
claw opencode-start
|
|
68
|
-
```
|
|
69
|
-
|
|
70
53
|
## 支持的厂商
|
|
71
54
|
|
|
72
55
|
| 厂商 | 主模型 | 快速模型 |
|
|
@@ -88,10 +71,8 @@ claw # 交互菜单(无参数时自动进入)
|
|
|
88
71
|
claw config # 配置 API 连接
|
|
89
72
|
claw code # 接入 Claude Code 终端
|
|
90
73
|
claw desktop # 接入 Claude 桌面应用
|
|
91
|
-
claw opencode # 使用当前厂商接入 opencode
|
|
92
|
-
claw opencode-custom # 使用自定义 Anthropic 接口接入 opencode
|
|
93
|
-
claw opencode-start # 启动 opencode
|
|
94
74
|
claw switch # 快速切换厂商或模型
|
|
75
|
+
claw prompt # 配置系统提示词(按模型独立保存)
|
|
95
76
|
claw status # 查看当前配置,验证 Key 是否有效
|
|
96
77
|
claw update # 检查并升级到最新版本
|
|
97
78
|
|
package/bin/cli.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { Command } = require('commander');
|
|
4
|
-
const { select, input, confirm } = require('@inquirer/prompts');
|
|
4
|
+
const { select, input, confirm, editor } = require('@inquirer/prompts');
|
|
5
5
|
const {
|
|
6
6
|
loadConfig,
|
|
7
7
|
saveConfig,
|
|
8
|
+
getSystemPrompt,
|
|
9
|
+
setSystemPrompt,
|
|
8
10
|
writeEnvToZshrc,
|
|
9
11
|
clearClaudeCodeEnv,
|
|
10
12
|
fetchModels,
|
|
@@ -24,13 +26,6 @@ const { buildMenuStatusLines, buildStatusView } = require('../lib/panel');
|
|
|
24
26
|
const { buildClaudeInstallCommand } = require('../lib/install');
|
|
25
27
|
const { clearClaudeDesktopConfig, isDesktopConfigured, openClaudeDesktop, writeClaudeDesktopConfig } = require('../lib/desktop');
|
|
26
28
|
const { runDoctorChecks, summarize, STATUS_OK, STATUS_FAIL, STATUS_WARN, STATUS_INFO } = require('../lib/doctor');
|
|
27
|
-
const {
|
|
28
|
-
buildOpenCodeLaunchCommand,
|
|
29
|
-
getOpenCodePaths,
|
|
30
|
-
isOpenCodeConfigured,
|
|
31
|
-
isOpenCodeInstalled,
|
|
32
|
-
writeOpenCodeConfig,
|
|
33
|
-
} = require('../lib/opencode');
|
|
34
29
|
|
|
35
30
|
const program = new Command();
|
|
36
31
|
|
|
@@ -98,76 +93,6 @@ function getSavedConfigHint() {
|
|
|
98
93
|
return '⚠ API Key 以明文存储在 ~/.clawai.json';
|
|
99
94
|
}
|
|
100
95
|
|
|
101
|
-
async function promptOpenCodeSystemPrompt(chalk) {
|
|
102
|
-
const paths = getOpenCodePaths();
|
|
103
|
-
let existingPrompt = '';
|
|
104
|
-
try {
|
|
105
|
-
existingPrompt = require('fs').readFileSync(paths.promptFile, 'utf8').trim();
|
|
106
|
-
} catch {}
|
|
107
|
-
|
|
108
|
-
if (existingPrompt) {
|
|
109
|
-
const action = await select({ loop: false,
|
|
110
|
-
message: chalk.cyan('opencode 系统提示词'),
|
|
111
|
-
choices: [
|
|
112
|
-
{ name: '沿用当前系统提示词', value: 'keep' },
|
|
113
|
-
{ name: '重新输入系统提示词', value: 'replace' },
|
|
114
|
-
],
|
|
115
|
-
});
|
|
116
|
-
if (action === 'keep') return existingPrompt;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return input({
|
|
120
|
-
message: chalk.cyan('输入 opencode 系统提示词'),
|
|
121
|
-
default: existingPrompt || '始终使用中文回答。修改代码前先说明计划。优先保持改动最小。',
|
|
122
|
-
validate: (v) => v.trim().length > 0 ? true : '系统提示词不能为空',
|
|
123
|
-
}).then(v => v.trim());
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function runOpenCodeConfigFlow({ mode = 'auto' } = {}) {
|
|
127
|
-
const chalk = (await import('chalk')).default;
|
|
128
|
-
const ora = (await import('ora')).default;
|
|
129
|
-
const boxen = (await import('boxen')).default;
|
|
130
|
-
|
|
131
|
-
console.log(await getBanner());
|
|
132
|
-
|
|
133
|
-
const config = loadConfig();
|
|
134
|
-
if (!config) {
|
|
135
|
-
console.log(chalk.red('\n未配置 API 连接,请先运行: claw config\n'));
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
const configProblem = getConfigValidationMessage(config);
|
|
139
|
-
if (configProblem) {
|
|
140
|
-
console.log(chalk.red(`\n配置无效:${configProblem}`));
|
|
141
|
-
console.log(chalk.dim('请运行 claw config 重新配置。\n'));
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const systemPrompt = await promptOpenCodeSystemPrompt(chalk);
|
|
146
|
-
const spinner = ora(mode === 'custom' ? '写入 opencode 自定义接口配置...' : '写入 opencode 当前厂商配置...').start();
|
|
147
|
-
let result;
|
|
148
|
-
try {
|
|
149
|
-
result = writeOpenCodeConfig(config, systemPrompt, { mode });
|
|
150
|
-
spinner.succeed(chalk.green(mode === 'custom' ? 'opencode 自定义接口已接入' : 'opencode 已接入当前厂商'));
|
|
151
|
-
} catch (e) {
|
|
152
|
-
spinner.fail(chalk.red(`写入失败: ${e.message}`));
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const providerMode = mode === 'custom'
|
|
157
|
-
? '自定义 Anthropic 接口'
|
|
158
|
-
: (config.provider === 'custom' ? '自定义接口' : 'opencode 内置厂商');
|
|
159
|
-
|
|
160
|
-
console.log(boxen(
|
|
161
|
-
chalk.bold('opencode 配置完成\n\n') +
|
|
162
|
-
chalk.dim('接入方式 ') + chalk.cyan(providerMode) + '\n' +
|
|
163
|
-
chalk.dim('配置文件 ') + chalk.cyan(result.configFile) + '\n' +
|
|
164
|
-
chalk.dim('提示词 ') + chalk.cyan(result.promptFile) + '\n' +
|
|
165
|
-
chalk.dim('模型 ') + chalk.yellow(config.model) + '\n\n' +
|
|
166
|
-
chalk.white('启动时选择“启动 opencode”,或直接运行 ') + chalk.cyan.bold('opencode'),
|
|
167
|
-
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
168
|
-
));
|
|
169
|
-
}
|
|
170
|
-
|
|
171
96
|
async function offerDesktopSync(chalk, ora, config) {
|
|
172
97
|
if (!isDesktopConfigured()) return;
|
|
173
98
|
const syncDesktop = await confirm({ message: 'Claude 桌面应用已配置,是否同步新模型?', default: true });
|
|
@@ -310,7 +235,6 @@ async function showStatus() {
|
|
|
310
235
|
apiStatus: valid,
|
|
311
236
|
claudeInstalled: isClaudeInstalled(),
|
|
312
237
|
env: process.env,
|
|
313
|
-
opencode: isOpenCodeConfigured(),
|
|
314
238
|
});
|
|
315
239
|
|
|
316
240
|
const lines = view.lines.map(({ label, value }) => {
|
|
@@ -432,7 +356,7 @@ async function runConfigFlow({ writeCodeEnv = false } = {}) {
|
|
|
432
356
|
cfg = customConfig || { provider: providerKey, model, fastModel, apiKey, baseUrl: provider.baseUrl, availableModels };
|
|
433
357
|
saveConfig(cfg);
|
|
434
358
|
if (writeCodeEnv) {
|
|
435
|
-
({ file } = writeEnvToZshrc(cfg.baseUrl, cfg.apiKey, cfg.model, cfg.fastModel));
|
|
359
|
+
({ file } = writeEnvToZshrc(cfg.baseUrl, cfg.apiKey, cfg.model, cfg.fastModel, { systemPrompt: getSystemPrompt(cfg) }));
|
|
436
360
|
}
|
|
437
361
|
spinner.succeed(chalk.green(writeCodeEnv ? 'API 连接已保存,Claude Code 终端已接入' : 'API 连接已保存'));
|
|
438
362
|
} catch (e) {
|
|
@@ -560,7 +484,7 @@ program
|
|
|
560
484
|
const spinner = ora('写入 Claude Code 终端环境变量...').start();
|
|
561
485
|
let file;
|
|
562
486
|
try {
|
|
563
|
-
({ file } = writeEnvToZshrc(config.baseUrl, config.apiKey, config.model, config.fastModel));
|
|
487
|
+
({ file } = writeEnvToZshrc(config.baseUrl, config.apiKey, config.model, config.fastModel, { systemPrompt: getSystemPrompt(config) }));
|
|
564
488
|
spinner.succeed(chalk.green(`Claude Code 终端已接入 → ${file}`));
|
|
565
489
|
} catch (e) {
|
|
566
490
|
spinner.fail(chalk.red(`写入失败: ${e.message}`));
|
|
@@ -578,43 +502,6 @@ program
|
|
|
578
502
|
));
|
|
579
503
|
});
|
|
580
504
|
|
|
581
|
-
program
|
|
582
|
-
.command('opencode')
|
|
583
|
-
.description('使用当前厂商接入 opencode API 和系统提示词')
|
|
584
|
-
.action(() => runOpenCodeConfigFlow({ mode: 'auto' }));
|
|
585
|
-
|
|
586
|
-
program
|
|
587
|
-
.command('opencode-custom')
|
|
588
|
-
.description('使用自定义 Anthropic 接口接入 opencode')
|
|
589
|
-
.action(() => runOpenCodeConfigFlow({ mode: 'custom' }));
|
|
590
|
-
|
|
591
|
-
program
|
|
592
|
-
.command('opencode-start [project]')
|
|
593
|
-
.description('启动 opencode')
|
|
594
|
-
.action(async (project) => {
|
|
595
|
-
const chalk = (await import('chalk')).default;
|
|
596
|
-
|
|
597
|
-
if (!isOpenCodeInstalled()) {
|
|
598
|
-
console.log(chalk.yellow('\nopencode 未安装,请先运行:'));
|
|
599
|
-
console.log(chalk.cyan('brew install anomalyco/tap/opencode'));
|
|
600
|
-
console.log(chalk.dim('或运行:npm i -g opencode-ai@latest\n'));
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
const launch = buildOpenCodeLaunchCommand(project || process.cwd());
|
|
605
|
-
await new Promise((resolve) => {
|
|
606
|
-
const child = spawn(launch.command, launch.args, {
|
|
607
|
-
stdio: 'inherit',
|
|
608
|
-
shell: process.platform === 'win32',
|
|
609
|
-
});
|
|
610
|
-
child.on('exit', resolve);
|
|
611
|
-
child.on('error', () => {
|
|
612
|
-
console.log(chalk.yellow('\nopencode 启动失败,请确认 opencode 命令可用'));
|
|
613
|
-
resolve();
|
|
614
|
-
});
|
|
615
|
-
});
|
|
616
|
-
});
|
|
617
|
-
|
|
618
505
|
program
|
|
619
506
|
.command('code-reset')
|
|
620
507
|
.description('恢复 Claude Code 终端默认配置')
|
|
@@ -748,6 +635,82 @@ program
|
|
|
748
635
|
await offerDesktopSync(chalk, ora, newConfig);
|
|
749
636
|
});
|
|
750
637
|
|
|
638
|
+
program
|
|
639
|
+
.command('prompt')
|
|
640
|
+
.description('配置系统提示词')
|
|
641
|
+
.action(async () => {
|
|
642
|
+
const chalk = (await import('chalk')).default;
|
|
643
|
+
const ora = (await import('ora')).default;
|
|
644
|
+
const boxen = (await import('boxen')).default;
|
|
645
|
+
|
|
646
|
+
console.log(await getBanner());
|
|
647
|
+
|
|
648
|
+
const config = loadConfig();
|
|
649
|
+
if (!config) {
|
|
650
|
+
console.log(chalk.red('\n未配置 API 连接,请先运行: claw config\n'));
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const provider = PROVIDERS[config.provider];
|
|
655
|
+
const providerName = config.providerName || provider?.name || config.provider;
|
|
656
|
+
const currentPrompt = getSystemPrompt(config);
|
|
657
|
+
|
|
658
|
+
console.log(chalk.dim(` 当前模型:${providerName} · ${config.model}\n`));
|
|
659
|
+
|
|
660
|
+
if (currentPrompt) {
|
|
661
|
+
const preview = currentPrompt.length > 300 ? currentPrompt.slice(0, 300) + '\n...' : currentPrompt;
|
|
662
|
+
console.log(boxen(
|
|
663
|
+
chalk.bold('当前系统提示词\n\n') + chalk.dim(preview),
|
|
664
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'cyan', margin: { top: 1, bottom: 1 } }
|
|
665
|
+
));
|
|
666
|
+
} else {
|
|
667
|
+
console.log(chalk.dim(' 当前模型未配置系统提示词\n'));
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const action = await select({ loop: false,
|
|
671
|
+
message: chalk.cyan('操作'),
|
|
672
|
+
choices: [
|
|
673
|
+
{ name: currentPrompt ? '✏️ 编辑系统提示词' : '✏️ 设置系统提示词', value: 'edit' },
|
|
674
|
+
...(currentPrompt ? [{ name: '🗑 清除当前模型提示词', value: 'clear' }] : []),
|
|
675
|
+
{ name: chalk.dim('↩ 返回'), value: '__BACK__' },
|
|
676
|
+
],
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
if (action === '__BACK__') return;
|
|
680
|
+
|
|
681
|
+
if (action === 'clear') {
|
|
682
|
+
const yes = await confirm({ message: '确定清除该模型的系统提示词?', default: false });
|
|
683
|
+
if (!yes) { console.log(chalk.dim('已取消')); return; }
|
|
684
|
+
const spinner = ora('清除中...').start();
|
|
685
|
+
setSystemPrompt(config, null);
|
|
686
|
+
saveConfig(config);
|
|
687
|
+
spinner.succeed(chalk.green('系统提示词已清除'));
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const newPrompt = await editor({
|
|
692
|
+
message: chalk.cyan('编辑系统提示词(保存并关闭编辑器后生效)'),
|
|
693
|
+
default: currentPrompt || '',
|
|
694
|
+
waitForUseInput: false,
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
if (!newPrompt || !newPrompt.trim()) {
|
|
698
|
+
console.log(chalk.dim('内容为空,已取消'));
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const spinner = ora('保存中...').start();
|
|
703
|
+
setSystemPrompt(config, newPrompt);
|
|
704
|
+
saveConfig(config);
|
|
705
|
+
spinner.succeed(chalk.green('系统提示词已保存'));
|
|
706
|
+
|
|
707
|
+
console.log(boxen(
|
|
708
|
+
chalk.dim('• 通过菜单"启动 Claude Code"时自动应用\n') +
|
|
709
|
+
chalk.dim('• 终端直接使用 claude 命令请重新运行 ') + chalk.cyan('claw code'),
|
|
710
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
711
|
+
));
|
|
712
|
+
});
|
|
713
|
+
|
|
751
714
|
program
|
|
752
715
|
.command('status')
|
|
753
716
|
.description('查看当前配置和 Key 有效性')
|
|
@@ -1057,7 +1020,6 @@ async function renderStatusBar(apiStatus) {
|
|
|
1057
1020
|
apiStatus,
|
|
1058
1021
|
claudeInstalled,
|
|
1059
1022
|
env: process.env,
|
|
1060
|
-
opencode: isOpenCodeConfigured(),
|
|
1061
1023
|
});
|
|
1062
1024
|
const statusLines = buildMenuStatusLines(view, { apiStatus, claudeInstalled, platform: process.platform });
|
|
1063
1025
|
cfgPart = statusLines.map((line, index) => {
|
|
@@ -1120,19 +1082,6 @@ async function maybeCheckApi(config, forceRecheck) {
|
|
|
1120
1082
|
|
|
1121
1083
|
const ADVANCED_DISABLED_HINT = '需先配置 API 连接';
|
|
1122
1084
|
|
|
1123
|
-
async function runOpenCodeMenu(chalk, hasConfig) {
|
|
1124
|
-
const action = await select({ loop: false,
|
|
1125
|
-
message: chalk.cyan('opencode 接入方式'),
|
|
1126
|
-
choices: [
|
|
1127
|
-
{ name: '使用当前厂商接入 opencode', value: 'opencode', disabled: !hasConfig && ADVANCED_DISABLED_HINT },
|
|
1128
|
-
{ name: '自定义 Anthropic 接口接入 opencode', value: 'opencode-custom', disabled: !hasConfig && ADVANCED_DISABLED_HINT },
|
|
1129
|
-
{ name: '启动 opencode', value: 'opencode-start' },
|
|
1130
|
-
{ name: chalk.dim('↩ 返回主菜单'), value: '__BACK__' },
|
|
1131
|
-
],
|
|
1132
|
-
});
|
|
1133
|
-
return action;
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
1085
|
async function runAdvancedMenu(chalk, hasConfig) {
|
|
1137
1086
|
const action = await select({ loop: false,
|
|
1138
1087
|
message: chalk.cyan('高级选项'),
|
|
@@ -1194,10 +1143,9 @@ async function runMenu() {
|
|
|
1194
1143
|
{ name: '📦 安装 Claude Code', value: 'install' },
|
|
1195
1144
|
{ name: config ? '🔑 重新配置 API 连接' : '🔑 配置 API 连接', value: 'config' },
|
|
1196
1145
|
{ name: '🔄 切换厂商或模型', value: 'switch', disabled: disabledHint },
|
|
1146
|
+
{ name: getSystemPrompt(config) ? '📝 系统提示词(已配置)' : '📝 配置系统提示词', value: 'prompt', disabled: !config && '需先配置 API 连接' },
|
|
1197
1147
|
{ name: '💻 接入 Claude Code 终端', value: 'code', disabled: disabledHint },
|
|
1198
1148
|
{ name: '🖥 接入 Claude 桌面应用', value: 'desktop', disabled: disabledHint },
|
|
1199
|
-
{ name: '⌘ opencode ›', value: 'opencode-menu' },
|
|
1200
|
-
{ name: '▶ 启动 opencode', value: 'opencode-start' },
|
|
1201
1149
|
{ name: '📊 查看当前配置', value: 'status', disabled: !config && '需先配置 API 连接' },
|
|
1202
1150
|
{ name: '🛠 高级 ›', value: 'advanced' },
|
|
1203
1151
|
{ name: '退出', value: 'exit' },
|
|
@@ -1212,11 +1160,6 @@ async function runMenu() {
|
|
|
1212
1160
|
if (adv === '__BACK__') continue;
|
|
1213
1161
|
resolvedAction = adv;
|
|
1214
1162
|
}
|
|
1215
|
-
if (action === 'opencode-menu') {
|
|
1216
|
-
const opencodeAction = await runOpenCodeMenu(chalk, !!config && !configProblem);
|
|
1217
|
-
if (opencodeAction === '__BACK__') continue;
|
|
1218
|
-
resolvedAction = opencodeAction;
|
|
1219
|
-
}
|
|
1220
1163
|
|
|
1221
1164
|
if (resolvedAction === 'recheck') {
|
|
1222
1165
|
lastCheckResult = undefined;
|
|
@@ -1227,8 +1170,9 @@ async function runMenu() {
|
|
|
1227
1170
|
if (resolvedAction === 'launch') {
|
|
1228
1171
|
const cfg = loadConfig();
|
|
1229
1172
|
if (!cfg || getConfigValidationMessage(cfg)) continue;
|
|
1173
|
+
const launchArgs = getSystemPrompt(cfg) ? ['--append-system-prompt', getSystemPrompt(cfg)] : [];
|
|
1230
1174
|
await new Promise((resolve) => {
|
|
1231
|
-
const child = spawn('claude',
|
|
1175
|
+
const child = spawn('claude', launchArgs, {
|
|
1232
1176
|
stdio: 'inherit',
|
|
1233
1177
|
env: { ...process.env, ...buildClaudeEnv(cfg) },
|
|
1234
1178
|
shell: process.platform === 'win32',
|
|
@@ -1242,29 +1186,13 @@ async function runMenu() {
|
|
|
1242
1186
|
continue;
|
|
1243
1187
|
}
|
|
1244
1188
|
|
|
1245
|
-
if (resolvedAction === 'opencode-start') {
|
|
1246
|
-
if (!isOpenCodeInstalled()) {
|
|
1247
|
-
console.log(chalk.yellow('\nopencode 未安装,请先安装:'));
|
|
1248
|
-
console.log(chalk.cyan('brew install anomalyco/tap/opencode'));
|
|
1249
|
-
} else {
|
|
1250
|
-
const launch = buildOpenCodeLaunchCommand(process.cwd());
|
|
1251
|
-
await new Promise((resolve) => {
|
|
1252
|
-
const child = spawn(launch.command, launch.args, { stdio: 'inherit', shell: process.platform === 'win32' });
|
|
1253
|
-
child.on('exit', resolve);
|
|
1254
|
-
child.on('error', resolve);
|
|
1255
|
-
});
|
|
1256
|
-
}
|
|
1257
|
-
continue;
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
1189
|
const cmdMap = {
|
|
1261
1190
|
install: 'install-claude',
|
|
1262
1191
|
config: 'config',
|
|
1263
1192
|
code: 'code',
|
|
1264
|
-
opencode: 'opencode',
|
|
1265
|
-
'opencode-custom': 'opencode-custom',
|
|
1266
1193
|
'code-reset': 'code-reset',
|
|
1267
1194
|
switch: 'switch',
|
|
1195
|
+
prompt: 'prompt',
|
|
1268
1196
|
desktop: 'desktop',
|
|
1269
1197
|
'desktop-reset': 'desktop-reset',
|
|
1270
1198
|
status: 'status',
|
|
@@ -1281,7 +1209,7 @@ async function runMenu() {
|
|
|
1281
1209
|
});
|
|
1282
1210
|
|
|
1283
1211
|
// 改 config 的命令需要刷新缓存
|
|
1284
|
-
if (['config', 'switch', 'reset', 'code-reset'
|
|
1212
|
+
if (['config', 'switch', 'reset', 'code-reset'].includes(resolvedAction)) {
|
|
1285
1213
|
lastCheckResult = undefined;
|
|
1286
1214
|
lastCheckedHash = null;
|
|
1287
1215
|
}
|
package/lib/config.js
CHANGED
|
@@ -128,6 +128,26 @@ 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
|
+
|
|
131
151
|
function buildModelUrlCandidates(baseUrl) {
|
|
132
152
|
let url;
|
|
133
153
|
try { url = new URL(normalizeAnthropicBaseUrl(baseUrl)); } catch { return []; }
|
|
@@ -324,10 +344,10 @@ function shellQuote(value) {
|
|
|
324
344
|
}
|
|
325
345
|
|
|
326
346
|
// 构造完整的 clawai 环境变量块
|
|
327
|
-
function buildEnvBlock(baseUrl, apiKey, model, fastModel) {
|
|
347
|
+
function buildEnvBlock(baseUrl, apiKey, model, fastModel, systemPrompt) {
|
|
328
348
|
const provider = providerKeyFromBaseUrl(baseUrl);
|
|
329
349
|
const env = buildClaudeEnv({ provider, baseUrl, apiKey, model, fastModel });
|
|
330
|
-
|
|
350
|
+
const lines = [
|
|
331
351
|
'',
|
|
332
352
|
'# clawai-start',
|
|
333
353
|
`export ANTHROPIC_BASE_URL=${shellQuote(env.ANTHROPIC_BASE_URL)}`,
|
|
@@ -339,9 +359,15 @@ function buildEnvBlock(baseUrl, apiKey, model, fastModel) {
|
|
|
339
359
|
`export ANTHROPIC_DEFAULT_HAIKU_MODEL=${shellQuote(env.ANTHROPIC_DEFAULT_HAIKU_MODEL)}`,
|
|
340
360
|
`export CLAUDE_CODE_SUBAGENT_MODEL=${shellQuote(env.CLAUDE_CODE_SUBAGENT_MODEL)}`,
|
|
341
361
|
`export CLAUDE_CODE_EFFORT_LEVEL=${shellQuote(env.CLAUDE_CODE_EFFORT_LEVEL)}`,
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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');
|
|
345
371
|
}
|
|
346
372
|
|
|
347
373
|
// 写入或更新 shell 配置文件中的环境变量块
|
|
@@ -364,7 +390,7 @@ function writeEnvToZshrc(baseUrl, apiKey, model, fastModel, options = {}) {
|
|
|
364
390
|
rcFile = path.join(os.homedir(), '.zshrc');
|
|
365
391
|
}
|
|
366
392
|
|
|
367
|
-
const block = buildEnvBlock(baseUrl, apiKey, model, fastModel);
|
|
393
|
+
const block = buildEnvBlock(baseUrl, apiKey, model, fastModel, options.systemPrompt);
|
|
368
394
|
const current = fs.existsSync(rcFile) ? fs.readFileSync(rcFile, 'utf8') : '';
|
|
369
395
|
|
|
370
396
|
// 兼容旧版(# clawai 单行块)和新版(# clawai-start...# clawai-end 多行块)
|
|
@@ -424,6 +450,8 @@ function resetConfig(options = {}) {
|
|
|
424
450
|
module.exports = {
|
|
425
451
|
loadConfig,
|
|
426
452
|
saveConfig,
|
|
453
|
+
getSystemPrompt,
|
|
454
|
+
setSystemPrompt,
|
|
427
455
|
writeEnvToZshrc,
|
|
428
456
|
buildWindowsSetEnvCommands,
|
|
429
457
|
buildWindowsClearEnvCommands,
|
package/lib/panel.js
CHANGED
|
@@ -21,7 +21,6 @@ function buildStatusView(config, options = {}) {
|
|
|
21
21
|
const mainModel = expectedEnv.ANTHROPIC_MODEL;
|
|
22
22
|
const fastModel = expectedEnv.CLAUDE_CODE_SUBAGENT_MODEL;
|
|
23
23
|
const envActive = isEnvActive(config, env);
|
|
24
|
-
const opencode = options.opencode || {};
|
|
25
24
|
const warnings = [];
|
|
26
25
|
|
|
27
26
|
if (config.provider === 'deepseek' && (
|
|
@@ -47,8 +46,6 @@ function buildStatusView(config, options = {}) {
|
|
|
47
46
|
{ label: 'Claude Code', value: claudeInstalled ? '已安装' : '未检测到' },
|
|
48
47
|
{ label: '当前终端', value: envActive ? '已生效' : '未生效' },
|
|
49
48
|
{ label: 'Base URL', value: config.baseUrl },
|
|
50
|
-
{ label: 'opencode', value: opencode.configured ? '已接入' : '未接入' },
|
|
51
|
-
{ label: '系统提示词', value: opencode.promptConfigured ? '已配置' : '未配置' },
|
|
52
49
|
],
|
|
53
50
|
};
|
|
54
51
|
}
|
package/package.json
CHANGED
package/lib/opencode.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const os = require('os');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const { spawnSync } = require('child_process');
|
|
5
|
-
const { normalizeAnthropicBaseUrl } = require('./config');
|
|
6
|
-
|
|
7
|
-
const OPENCODE_PROVIDER_ID = 'yingclaw';
|
|
8
|
-
const OPENCODE_PROMPT_REF = '{file:~/.config/opencode/prompts/build.md}';
|
|
9
|
-
|
|
10
|
-
function getOpenCodePaths(options = {}) {
|
|
11
|
-
const homeDir = options.homeDir || os.homedir();
|
|
12
|
-
const configDir = path.join(homeDir, '.config', 'opencode');
|
|
13
|
-
return {
|
|
14
|
-
configDir,
|
|
15
|
-
configFile: path.join(configDir, 'opencode.json'),
|
|
16
|
-
promptDir: path.join(configDir, 'prompts'),
|
|
17
|
-
promptFile: path.join(configDir, 'prompts', 'build.md'),
|
|
18
|
-
authFile: path.join(homeDir, '.local', 'share', 'opencode', 'auth.json'),
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function readJsonFile(file) {
|
|
23
|
-
try {
|
|
24
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
25
|
-
} catch {
|
|
26
|
-
return {};
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function modelMap(config) {
|
|
31
|
-
const models = [config.model, config.fastModel].filter(Boolean);
|
|
32
|
-
return Object.fromEntries([...new Set(models)].map((model) => [model, { name: model }]));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function normalizeDeepSeekModel(model) {
|
|
36
|
-
if (model === 'deepseek-v4-pro[1m]') return 'deepseek-v4-pro';
|
|
37
|
-
if (model === 'deepseek-v4-flash[1m]') return 'deepseek-v4-flash';
|
|
38
|
-
return model;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function builtInOpenCodeModel(config) {
|
|
42
|
-
if (config.provider === 'deepseek' || normalizeAnthropicBaseUrl(config.baseUrl) === 'https://api.deepseek.com/anthropic') {
|
|
43
|
-
const model = normalizeDeepSeekModel(config.model);
|
|
44
|
-
// DeepSeek's Anthropic-compatible flash endpoint currently hangs for opencode title requests.
|
|
45
|
-
return { providerID: 'deepseek', model, smallModel: model };
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function buildOpenCodeConfig(config, options = {}) {
|
|
51
|
-
const existing = options.existing || {};
|
|
52
|
-
const providerName = config.providerName || config.provider || OPENCODE_PROVIDER_ID;
|
|
53
|
-
const fastModel = config.fastModel || config.model;
|
|
54
|
-
const builtIn = options.mode === 'custom' ? null : builtInOpenCodeModel(config);
|
|
55
|
-
|
|
56
|
-
if (builtIn) {
|
|
57
|
-
return {
|
|
58
|
-
...existing,
|
|
59
|
-
$schema: 'https://opencode.ai/config.json',
|
|
60
|
-
model: `${builtIn.providerID}/${builtIn.model}`,
|
|
61
|
-
small_model: `${builtIn.providerID}/${builtIn.smallModel}`,
|
|
62
|
-
agent: {
|
|
63
|
-
...(existing.agent || {}),
|
|
64
|
-
build: {
|
|
65
|
-
...(existing.agent?.build || {}),
|
|
66
|
-
prompt: OPENCODE_PROMPT_REF,
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
...existing,
|
|
74
|
-
$schema: 'https://opencode.ai/config.json',
|
|
75
|
-
model: `${OPENCODE_PROVIDER_ID}/${config.model}`,
|
|
76
|
-
small_model: `${OPENCODE_PROVIDER_ID}/${fastModel}`,
|
|
77
|
-
provider: {
|
|
78
|
-
...(existing.provider || {}),
|
|
79
|
-
[OPENCODE_PROVIDER_ID]: {
|
|
80
|
-
...(existing.provider?.[OPENCODE_PROVIDER_ID] || {}),
|
|
81
|
-
npm: '@ai-sdk/anthropic',
|
|
82
|
-
name: providerName,
|
|
83
|
-
options: {
|
|
84
|
-
...(existing.provider?.[OPENCODE_PROVIDER_ID]?.options || {}),
|
|
85
|
-
baseURL: normalizeAnthropicBaseUrl(config.baseUrl),
|
|
86
|
-
apiKey: config.apiKey,
|
|
87
|
-
},
|
|
88
|
-
models: modelMap({ ...config, fastModel }),
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
agent: {
|
|
92
|
-
...(existing.agent || {}),
|
|
93
|
-
build: {
|
|
94
|
-
...(existing.agent?.build || {}),
|
|
95
|
-
prompt: OPENCODE_PROMPT_REF,
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function writePrivateFile(file, body) {
|
|
102
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
103
|
-
fs.writeFileSync(file, body);
|
|
104
|
-
try {
|
|
105
|
-
fs.chmodSync(file, 0o600);
|
|
106
|
-
} catch {}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function writeOpenCodeConfig(config, prompt, options = {}) {
|
|
110
|
-
const paths = getOpenCodePaths(options);
|
|
111
|
-
const existing = readJsonFile(paths.configFile);
|
|
112
|
-
const next = buildOpenCodeConfig(config, { existing, mode: options.mode });
|
|
113
|
-
const builtIn = options.mode === 'custom' ? null : builtInOpenCodeModel(config);
|
|
114
|
-
const promptBody = prompt.endsWith('\n') ? prompt : `${prompt}\n`;
|
|
115
|
-
|
|
116
|
-
writePrivateFile(paths.promptFile, promptBody);
|
|
117
|
-
if (builtIn) {
|
|
118
|
-
const auth = readJsonFile(paths.authFile);
|
|
119
|
-
auth[builtIn.providerID] = { type: 'api', key: config.apiKey };
|
|
120
|
-
writePrivateFile(paths.authFile, JSON.stringify(auth, null, 2) + '\n');
|
|
121
|
-
}
|
|
122
|
-
writePrivateFile(paths.configFile, JSON.stringify(next, null, 2) + '\n');
|
|
123
|
-
|
|
124
|
-
return { configFile: paths.configFile, promptFile: paths.promptFile };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function isOpenCodeConfigured(options = {}) {
|
|
128
|
-
const paths = getOpenCodePaths(options);
|
|
129
|
-
return {
|
|
130
|
-
configured: fs.existsSync(paths.configFile),
|
|
131
|
-
promptConfigured: fs.existsSync(paths.promptFile),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function isOpenCodeInstalled(options = {}) {
|
|
136
|
-
const runner = options.runner || spawnSync;
|
|
137
|
-
try {
|
|
138
|
-
const result = runner('opencode', ['--version'], { stdio: 'ignore', shell: process.platform === 'win32' });
|
|
139
|
-
return result.status === 0;
|
|
140
|
-
} catch {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function buildOpenCodeLaunchCommand(project) {
|
|
146
|
-
return {
|
|
147
|
-
command: 'opencode',
|
|
148
|
-
args: project ? [project] : [],
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
module.exports = {
|
|
153
|
-
OPENCODE_PROMPT_REF,
|
|
154
|
-
OPENCODE_PROVIDER_ID,
|
|
155
|
-
buildOpenCodeConfig,
|
|
156
|
-
buildOpenCodeLaunchCommand,
|
|
157
|
-
builtInOpenCodeModel,
|
|
158
|
-
getOpenCodePaths,
|
|
159
|
-
isOpenCodeConfigured,
|
|
160
|
-
isOpenCodeInstalled,
|
|
161
|
-
writeOpenCodeConfig,
|
|
162
|
-
};
|