yingclaw 2.3.0 → 2.3.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 +20 -0
- package/bin/cli.js +154 -1
- package/lib/opencode.js +162 -0
- package/lib/panel.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,23 @@ 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
|
+
|
|
53
70
|
## 支持的厂商
|
|
54
71
|
|
|
55
72
|
| 厂商 | 主模型 | 快速模型 |
|
|
@@ -71,6 +88,9 @@ claw # 交互菜单(无参数时自动进入)
|
|
|
71
88
|
claw config # 配置 API 连接
|
|
72
89
|
claw code # 接入 Claude Code 终端
|
|
73
90
|
claw desktop # 接入 Claude 桌面应用
|
|
91
|
+
claw opencode # 使用当前厂商接入 opencode
|
|
92
|
+
claw opencode-custom # 使用自定义 Anthropic 接口接入 opencode
|
|
93
|
+
claw opencode-start # 启动 opencode
|
|
74
94
|
claw switch # 快速切换厂商或模型
|
|
75
95
|
claw status # 查看当前配置,验证 Key 是否有效
|
|
76
96
|
claw update # 检查并升级到最新版本
|
package/bin/cli.js
CHANGED
|
@@ -24,6 +24,13 @@ const { buildMenuStatusLines, buildStatusView } = require('../lib/panel');
|
|
|
24
24
|
const { buildClaudeInstallCommand } = require('../lib/install');
|
|
25
25
|
const { clearClaudeDesktopConfig, isDesktopConfigured, openClaudeDesktop, writeClaudeDesktopConfig } = require('../lib/desktop');
|
|
26
26
|
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');
|
|
27
34
|
|
|
28
35
|
const program = new Command();
|
|
29
36
|
|
|
@@ -91,6 +98,76 @@ function getSavedConfigHint() {
|
|
|
91
98
|
return '⚠ API Key 以明文存储在 ~/.clawai.json';
|
|
92
99
|
}
|
|
93
100
|
|
|
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
|
+
|
|
94
171
|
async function offerDesktopSync(chalk, ora, config) {
|
|
95
172
|
if (!isDesktopConfigured()) return;
|
|
96
173
|
const syncDesktop = await confirm({ message: 'Claude 桌面应用已配置,是否同步新模型?', default: true });
|
|
@@ -233,6 +310,7 @@ async function showStatus() {
|
|
|
233
310
|
apiStatus: valid,
|
|
234
311
|
claudeInstalled: isClaudeInstalled(),
|
|
235
312
|
env: process.env,
|
|
313
|
+
opencode: isOpenCodeConfigured(),
|
|
236
314
|
});
|
|
237
315
|
|
|
238
316
|
const lines = view.lines.map(({ label, value }) => {
|
|
@@ -500,6 +578,43 @@ program
|
|
|
500
578
|
));
|
|
501
579
|
});
|
|
502
580
|
|
|
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
|
+
|
|
503
618
|
program
|
|
504
619
|
.command('code-reset')
|
|
505
620
|
.description('恢复 Claude Code 终端默认配置')
|
|
@@ -942,6 +1057,7 @@ async function renderStatusBar(apiStatus) {
|
|
|
942
1057
|
apiStatus,
|
|
943
1058
|
claudeInstalled,
|
|
944
1059
|
env: process.env,
|
|
1060
|
+
opencode: isOpenCodeConfigured(),
|
|
945
1061
|
});
|
|
946
1062
|
const statusLines = buildMenuStatusLines(view, { apiStatus, claudeInstalled, platform: process.platform });
|
|
947
1063
|
cfgPart = statusLines.map((line, index) => {
|
|
@@ -1004,6 +1120,19 @@ async function maybeCheckApi(config, forceRecheck) {
|
|
|
1004
1120
|
|
|
1005
1121
|
const ADVANCED_DISABLED_HINT = '需先配置 API 连接';
|
|
1006
1122
|
|
|
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
|
+
|
|
1007
1136
|
async function runAdvancedMenu(chalk, hasConfig) {
|
|
1008
1137
|
const action = await select({ loop: false,
|
|
1009
1138
|
message: chalk.cyan('高级选项'),
|
|
@@ -1067,6 +1196,8 @@ async function runMenu() {
|
|
|
1067
1196
|
{ name: '🔄 切换厂商或模型', value: 'switch', disabled: disabledHint },
|
|
1068
1197
|
{ name: '💻 接入 Claude Code 终端', value: 'code', disabled: disabledHint },
|
|
1069
1198
|
{ name: '🖥 接入 Claude 桌面应用', value: 'desktop', disabled: disabledHint },
|
|
1199
|
+
{ name: '⌘ opencode ›', value: 'opencode-menu' },
|
|
1200
|
+
{ name: '▶ 启动 opencode', value: 'opencode-start' },
|
|
1070
1201
|
{ name: '📊 查看当前配置', value: 'status', disabled: !config && '需先配置 API 连接' },
|
|
1071
1202
|
{ name: '🛠 高级 ›', value: 'advanced' },
|
|
1072
1203
|
{ name: '退出', value: 'exit' },
|
|
@@ -1081,6 +1212,11 @@ async function runMenu() {
|
|
|
1081
1212
|
if (adv === '__BACK__') continue;
|
|
1082
1213
|
resolvedAction = adv;
|
|
1083
1214
|
}
|
|
1215
|
+
if (action === 'opencode-menu') {
|
|
1216
|
+
const opencodeAction = await runOpenCodeMenu(chalk, !!config && !configProblem);
|
|
1217
|
+
if (opencodeAction === '__BACK__') continue;
|
|
1218
|
+
resolvedAction = opencodeAction;
|
|
1219
|
+
}
|
|
1084
1220
|
|
|
1085
1221
|
if (resolvedAction === 'recheck') {
|
|
1086
1222
|
lastCheckResult = undefined;
|
|
@@ -1106,10 +1242,27 @@ async function runMenu() {
|
|
|
1106
1242
|
continue;
|
|
1107
1243
|
}
|
|
1108
1244
|
|
|
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
|
+
|
|
1109
1260
|
const cmdMap = {
|
|
1110
1261
|
install: 'install-claude',
|
|
1111
1262
|
config: 'config',
|
|
1112
1263
|
code: 'code',
|
|
1264
|
+
opencode: 'opencode',
|
|
1265
|
+
'opencode-custom': 'opencode-custom',
|
|
1113
1266
|
'code-reset': 'code-reset',
|
|
1114
1267
|
switch: 'switch',
|
|
1115
1268
|
desktop: 'desktop',
|
|
@@ -1128,7 +1281,7 @@ async function runMenu() {
|
|
|
1128
1281
|
});
|
|
1129
1282
|
|
|
1130
1283
|
// 改 config 的命令需要刷新缓存
|
|
1131
|
-
if (['config', 'switch', 'reset', 'code-reset'].includes(resolvedAction)) {
|
|
1284
|
+
if (['config', 'switch', 'reset', 'code-reset', 'opencode', 'opencode-custom'].includes(resolvedAction)) {
|
|
1132
1285
|
lastCheckResult = undefined;
|
|
1133
1286
|
lastCheckedHash = null;
|
|
1134
1287
|
}
|
package/lib/opencode.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
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
|
+
};
|
package/lib/panel.js
CHANGED
|
@@ -21,6 +21,7 @@ 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 || {};
|
|
24
25
|
const warnings = [];
|
|
25
26
|
|
|
26
27
|
if (config.provider === 'deepseek' && (
|
|
@@ -46,6 +47,8 @@ function buildStatusView(config, options = {}) {
|
|
|
46
47
|
{ label: 'Claude Code', value: claudeInstalled ? '已安装' : '未检测到' },
|
|
47
48
|
{ label: '当前终端', value: envActive ? '已生效' : '未生效' },
|
|
48
49
|
{ label: 'Base URL', value: config.baseUrl },
|
|
50
|
+
{ label: 'opencode', value: opencode.configured ? '已接入' : '未接入' },
|
|
51
|
+
{ label: '系统提示词', value: opencode.promptConfigured ? '已配置' : '未配置' },
|
|
49
52
|
],
|
|
50
53
|
};
|
|
51
54
|
}
|