yingclaw 1.7.8 → 1.8.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 +42 -12
- package/bin/cli.js +330 -137
- package/lib/config.js +16 -9
- package/lib/desktop.js +23 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,18 +22,43 @@ claw install-claude
|
|
|
22
22
|
```
|
|
23
23
|
根据提示选择网络环境(有梯子走官方,没梯子走淘宝镜像)。
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
**第二步:配置 API 连接**
|
|
26
26
|
```bash
|
|
27
|
-
claw
|
|
27
|
+
claw config
|
|
28
|
+
```
|
|
29
|
+
选择厂商 → 输入 API Key → 选择模型。这个步骤只保存 API 连接,不会修改终端环境变量,也不会修改 Claude 桌面应用配置。
|
|
30
|
+
|
|
31
|
+
**第三步:选择接入目标**
|
|
32
|
+
|
|
33
|
+
接入 Claude Code 终端:
|
|
34
|
+
```bash
|
|
35
|
+
claw code
|
|
28
36
|
```
|
|
29
|
-
|
|
37
|
+
写入 Claude Code 所需的环境变量。配置完成后不会自动启动 Claude Code,需要时可在主菜单选择“启动 Claude Code”或直接运行 `claude`。
|
|
30
38
|
|
|
31
|
-
|
|
39
|
+
接入 Claude 桌面应用:
|
|
32
40
|
```bash
|
|
33
41
|
claw desktop
|
|
34
42
|
```
|
|
35
|
-
将当前模型配置写入 Claude Desktop 的第三方推理(Cowork on 3P
|
|
36
|
-
|
|
43
|
+
将当前模型配置写入 Claude Desktop 的第三方推理(Cowork on 3P)本地配置。macOS 会询问是否立即打开 Claude 桌面应用;Windows 需要手动打开。如果配置未生效,请完全退出 Claude Desktop 后重新打开。
|
|
44
|
+
|
|
45
|
+
恢复 Claude Code 终端默认配置:
|
|
46
|
+
```bash
|
|
47
|
+
claw code-reset
|
|
48
|
+
```
|
|
49
|
+
只清除 Claude Code 终端环境变量,不影响 API 连接和 Claude 桌面配置。
|
|
50
|
+
|
|
51
|
+
恢复 Claude 桌面应用默认配置:
|
|
52
|
+
```bash
|
|
53
|
+
claw desktop-reset
|
|
54
|
+
```
|
|
55
|
+
只清除 Claude Desktop 第三方推理配置,不影响终端里的 API Key 和模型配置。
|
|
56
|
+
|
|
57
|
+
兼容旧命令:
|
|
58
|
+
```bash
|
|
59
|
+
claw setup
|
|
60
|
+
```
|
|
61
|
+
等价于 `claw config` + `claw code`,用于一键配置 API 并接入 Claude Code 终端。
|
|
37
62
|
|
|
38
63
|
选择“自定义 Anthropic 兼容接口”时,需要输入:
|
|
39
64
|
|
|
@@ -44,7 +69,7 @@ macOS 会询问是否自动重启 Claude 桌面应用;Windows 需要手动重
|
|
|
44
69
|
|
|
45
70
|
注意:模型列表能获取不代表一定可用于 Claude Code 或 Claude 桌面应用。自定义接口还必须支持 Anthropic `/v1/messages`,否则请求会被网关拒绝。Claude 桌面应用的 Gateway Base URL 还必须使用 HTTPS。
|
|
46
71
|
|
|
47
|
-
|
|
72
|
+
**以后直接用**
|
|
48
73
|
```bash
|
|
49
74
|
claude
|
|
50
75
|
```
|
|
@@ -64,10 +89,15 @@ claude
|
|
|
64
89
|
## 其他命令
|
|
65
90
|
|
|
66
91
|
```bash
|
|
67
|
-
claw
|
|
68
|
-
claw
|
|
69
|
-
claw
|
|
70
|
-
claw
|
|
92
|
+
claw config # 配置 API 连接,不修改终端或桌面
|
|
93
|
+
claw code # 接入 Claude Code 终端
|
|
94
|
+
claw code-reset # 恢复 Claude Code 终端默认配置
|
|
95
|
+
claw switch # 快速切换模型(只更新 API 连接)
|
|
96
|
+
claw desktop # 接入 Claude 桌面应用第三方推理
|
|
97
|
+
claw desktop-reset # 恢复 Claude 桌面应用默认配置
|
|
98
|
+
claw setup # 兼容旧命令:config + code
|
|
99
|
+
claw status # 查看当前配置,验证 Key 是否有效
|
|
100
|
+
claw reset # 清除 API 连接、终端环境变量和桌面配置
|
|
71
101
|
```
|
|
72
102
|
|
|
73
103
|
## 卸载
|
|
@@ -78,7 +108,7 @@ npm uninstall -g yingclaw
|
|
|
78
108
|
|
|
79
109
|
## 原理
|
|
80
110
|
|
|
81
|
-
各厂商均原生支持 Anthropic API
|
|
111
|
+
各厂商均原生支持 Anthropic API 格式。`claw config` 只保存 API 连接;`claw code` 才会写入 Claude Code 所需的环境变量。macOS / Linux / WSL 写入 shell 配置文件,Windows 写入用户级环境变量,包括:
|
|
82
112
|
|
|
83
113
|
- `ANTHROPIC_BASE_URL`
|
|
84
114
|
- `ANTHROPIC_AUTH_TOKEN`
|
package/bin/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
loadConfig,
|
|
7
7
|
saveConfig,
|
|
8
8
|
writeEnvToZshrc,
|
|
9
|
+
clearClaudeCodeEnv,
|
|
9
10
|
fetchModels,
|
|
10
11
|
fetchModelsFromBaseUrl,
|
|
11
12
|
resetConfig,
|
|
@@ -20,7 +21,7 @@ const { execSync, spawn, spawnSync } = require('child_process');
|
|
|
20
21
|
const pkg = require('../package.json');
|
|
21
22
|
const { buildMenuStatusLines, buildStatusView } = require('../lib/panel');
|
|
22
23
|
const { buildClaudeInstallCommand } = require('../lib/install');
|
|
23
|
-
const { clearClaudeDesktopConfig,
|
|
24
|
+
const { clearClaudeDesktopConfig, openClaudeDesktop, writeClaudeDesktopConfig } = require('../lib/desktop');
|
|
24
25
|
|
|
25
26
|
const program = new Command();
|
|
26
27
|
|
|
@@ -109,8 +110,12 @@ function getStorageHint(file) {
|
|
|
109
110
|
return `⚠ API Key 以明文存储在 ${file} 和 ~/.clawai.json`;
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
function
|
|
113
|
-
return '
|
|
113
|
+
function getDesktopOpenHint() {
|
|
114
|
+
return '请打开 Claude 桌面应用;如配置未生效,请完全退出后重新打开';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getSavedConfigHint() {
|
|
118
|
+
return '⚠ API Key 以明文存储在 ~/.clawai.json';
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
async function promptModelFromChoices({ chalk, choices, message, backLabel = '↩ 返回上一步', allowManual = true }) {
|
|
@@ -201,13 +206,13 @@ async function showStatus() {
|
|
|
201
206
|
const config = loadConfig();
|
|
202
207
|
|
|
203
208
|
if (!config) {
|
|
204
|
-
console.log(chalk.red('\n未配置,请先运行: claw
|
|
209
|
+
console.log(chalk.red('\n未配置,请先运行: claw config\n'));
|
|
205
210
|
return;
|
|
206
211
|
}
|
|
207
212
|
const configProblem = getConfigValidationMessage(config);
|
|
208
213
|
if (configProblem) {
|
|
209
214
|
console.log(chalk.red(`\n配置无效:${configProblem}`));
|
|
210
|
-
console.log(chalk.dim('请运行 claw
|
|
215
|
+
console.log(chalk.dim('请运行 claw config 重新配置。\n'));
|
|
211
216
|
return;
|
|
212
217
|
}
|
|
213
218
|
|
|
@@ -257,6 +262,113 @@ async function showStatus() {
|
|
|
257
262
|
}));
|
|
258
263
|
}
|
|
259
264
|
|
|
265
|
+
async function runConfigFlow({ writeCodeEnv = false } = {}) {
|
|
266
|
+
const chalk = (await import('chalk')).default;
|
|
267
|
+
const ora = (await import('ora')).default;
|
|
268
|
+
const boxen = (await import('boxen')).default;
|
|
269
|
+
|
|
270
|
+
console.log(await getBanner());
|
|
271
|
+
|
|
272
|
+
const existing = loadConfig();
|
|
273
|
+
if (existing) {
|
|
274
|
+
const existingProvider = PROVIDERS[existing.provider];
|
|
275
|
+
console.log(boxen(
|
|
276
|
+
chalk.bold('当前 API 连接\n\n') +
|
|
277
|
+
chalk.dim('厂商 ') + chalk.white(existingProvider?.name || existing.provider) + '\n' +
|
|
278
|
+
chalk.dim('模型 ') + chalk.yellow(existing.model) + '\n' +
|
|
279
|
+
chalk.dim('Key ') + chalk.dim(existing.apiKey ? '已保存' : '缺失'),
|
|
280
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'yellow', margin: { top: 1, bottom: 1 } }
|
|
281
|
+
));
|
|
282
|
+
const overwrite = await confirm({ message: '覆盖现有 API 连接?', default: false });
|
|
283
|
+
if (!overwrite) return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let providerKey, provider, apiKey, model, customConfig;
|
|
287
|
+
let step = 'provider';
|
|
288
|
+
|
|
289
|
+
while (true) {
|
|
290
|
+
if (step === 'provider') {
|
|
291
|
+
providerKey = await select({ loop: false,
|
|
292
|
+
message: chalk.cyan('选择 AI 厂商'),
|
|
293
|
+
choices: [
|
|
294
|
+
...Object.entries(PROVIDERS).map(([value, p]) => ({ name: p.name, value })),
|
|
295
|
+
{ name: chalk.dim('↩ 返回主菜单'), value: '__BACK__' },
|
|
296
|
+
],
|
|
297
|
+
});
|
|
298
|
+
if (providerKey === '__BACK__') return;
|
|
299
|
+
provider = PROVIDERS[providerKey];
|
|
300
|
+
if (provider.custom) {
|
|
301
|
+
customConfig = await configureCustomProvider({ chalk, ora });
|
|
302
|
+
if (!customConfig) { step = 'provider'; continue; }
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
step = 'apikey';
|
|
306
|
+
} else if (step === 'apikey') {
|
|
307
|
+
const k = await input({
|
|
308
|
+
message: chalk.cyan(`${provider.name} API Key(输入 b 返回上一步)`),
|
|
309
|
+
transformer: (v) => v && v !== 'b' ? chalk.dim('•'.repeat(v.length)) : v,
|
|
310
|
+
validate: (v) => v.trim().length > 0 ? true : 'API Key 不能为空',
|
|
311
|
+
});
|
|
312
|
+
if (k.trim() === 'b') { step = 'provider'; continue; }
|
|
313
|
+
apiKey = k.trim();
|
|
314
|
+
step = 'model';
|
|
315
|
+
} else if (step === 'model') {
|
|
316
|
+
const fetchSpinner = ora('正在获取可用模型...').start();
|
|
317
|
+
const onlineModels = await fetchModels(providerKey, apiKey);
|
|
318
|
+
let modelChoices;
|
|
319
|
+
if (onlineModels && onlineModels.length > 0) {
|
|
320
|
+
fetchSpinner.succeed(chalk.green(`已获取 ${onlineModels.length} 个可用模型`));
|
|
321
|
+
modelChoices = onlineModels.map(id => ({ name: id, value: id }));
|
|
322
|
+
} else {
|
|
323
|
+
fetchSpinner.warn(chalk.yellow('无法获取在线列表,使用内置默认列表'));
|
|
324
|
+
modelChoices = provider.models;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const m = await select({ loop: false,
|
|
328
|
+
message: chalk.cyan('选择模型'),
|
|
329
|
+
choices: [
|
|
330
|
+
...modelChoices,
|
|
331
|
+
{ name: chalk.dim('↩ 返回上一步(重新输入 Key)'), value: '__BACK__' },
|
|
332
|
+
],
|
|
333
|
+
});
|
|
334
|
+
if (m === '__BACK__') { step = 'apikey'; continue; }
|
|
335
|
+
model = m;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const spinner = ora(writeCodeEnv ? '保存 API 连接并接入 Claude Code 终端...' : '保存 API 连接...').start();
|
|
341
|
+
let file;
|
|
342
|
+
let cfg;
|
|
343
|
+
try {
|
|
344
|
+
const fastModel = customConfig?.fastModel || resolveFastModel(provider, model);
|
|
345
|
+
cfg = customConfig || { provider: providerKey, model, fastModel, apiKey, baseUrl: provider.baseUrl };
|
|
346
|
+
saveConfig(cfg);
|
|
347
|
+
if (writeCodeEnv) {
|
|
348
|
+
({ file } = writeEnvToZshrc(cfg.baseUrl, cfg.apiKey, cfg.model, cfg.fastModel));
|
|
349
|
+
}
|
|
350
|
+
spinner.succeed(chalk.green(writeCodeEnv ? 'API 连接已保存,Claude Code 终端已接入' : 'API 连接已保存'));
|
|
351
|
+
} catch (e) {
|
|
352
|
+
spinner.fail(chalk.red(`写入失败: ${e.message}`));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
console.log(chalk.dim(writeCodeEnv ? getStorageHint(file) : getSavedConfigHint()));
|
|
357
|
+
|
|
358
|
+
const nextStep = writeCodeEnv
|
|
359
|
+
? chalk.white('需要启动时,在主菜单选择“启动 Claude Code”,或直接输入 ') + chalk.cyan.bold('claude') + '\n' + chalk.dim(getActivationHint(file))
|
|
360
|
+
: chalk.white('下一步可选择“接入 Claude Code 终端”或“接入 Claude 桌面应用”。');
|
|
361
|
+
|
|
362
|
+
console.log(boxen(
|
|
363
|
+
chalk.bold(writeCodeEnv ? 'Claude Code 终端配置完成!\n\n' : 'API 连接配置完成!\n\n') +
|
|
364
|
+
chalk.dim('Base URL ') + chalk.cyan(cfg.baseUrl) + '\n' +
|
|
365
|
+
chalk.dim('API Key ') + chalk.cyan('已保存') + '\n' +
|
|
366
|
+
chalk.dim('模型 ') + chalk.yellow(cfg.model) + '\n\n' +
|
|
367
|
+
nextStep,
|
|
368
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
369
|
+
));
|
|
370
|
+
}
|
|
371
|
+
|
|
260
372
|
program
|
|
261
373
|
.name('claw')
|
|
262
374
|
.description('Claude Code × 国产大模型一键接入')
|
|
@@ -318,14 +430,25 @@ program
|
|
|
318
430
|
|
|
319
431
|
console.log(boxen(
|
|
320
432
|
chalk.bold('下一步\n\n') +
|
|
321
|
-
chalk.cyan(' claw
|
|
433
|
+
chalk.cyan(' claw config') + chalk.dim(' 配置 API 连接\n') +
|
|
434
|
+
chalk.cyan(' claw code') + chalk.dim(' 接入 Claude Code 终端'),
|
|
322
435
|
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'cyan', margin: { top: 1, bottom: 1 } }
|
|
323
436
|
));
|
|
324
437
|
});
|
|
325
438
|
|
|
439
|
+
program
|
|
440
|
+
.command('config')
|
|
441
|
+
.description('配置 API 连接(不修改终端或桌面)')
|
|
442
|
+
.action(() => runConfigFlow({ writeCodeEnv: false }));
|
|
443
|
+
|
|
326
444
|
program
|
|
327
445
|
.command('setup')
|
|
328
|
-
.description('配置 API
|
|
446
|
+
.description('配置 API 并接入 Claude Code 终端(兼容旧命令)')
|
|
447
|
+
.action(() => runConfigFlow({ writeCodeEnv: true }));
|
|
448
|
+
|
|
449
|
+
program
|
|
450
|
+
.command('code')
|
|
451
|
+
.description('接入 Claude Code 终端')
|
|
329
452
|
.action(async () => {
|
|
330
453
|
const chalk = (await import('chalk')).default;
|
|
331
454
|
const ora = (await import('ora')).default;
|
|
@@ -333,112 +456,81 @@ program
|
|
|
333
456
|
|
|
334
457
|
console.log(await getBanner());
|
|
335
458
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
console.log(boxen(
|
|
341
|
-
chalk.bold('当前配置\n\n') +
|
|
342
|
-
chalk.dim('厂商 ') + chalk.white(existingProvider?.name || existing.provider) + '\n' +
|
|
343
|
-
chalk.dim('模型 ') + chalk.yellow(existing.model) + '\n' +
|
|
344
|
-
chalk.dim('Key ') + chalk.dim(existing.apiKey ? '已保存' : '缺失'),
|
|
345
|
-
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'yellow', margin: { top: 1, bottom: 1 } }
|
|
346
|
-
));
|
|
347
|
-
const overwrite = await confirm({ message: '覆盖现有配置?', default: false });
|
|
348
|
-
if (!overwrite) return;
|
|
459
|
+
const config = loadConfig();
|
|
460
|
+
if (!config) {
|
|
461
|
+
console.log(chalk.red('\n未配置 API 连接,请先运行: claw config\n'));
|
|
462
|
+
return;
|
|
349
463
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if (step === 'provider') {
|
|
356
|
-
providerKey = await select({ loop: false,
|
|
357
|
-
message: chalk.cyan('选择 AI 厂商'),
|
|
358
|
-
choices: [
|
|
359
|
-
...Object.entries(PROVIDERS).map(([value, p]) => ({ name: p.name, value })),
|
|
360
|
-
{ name: chalk.dim('↩ 返回主菜单'), value: '__BACK__' },
|
|
361
|
-
],
|
|
362
|
-
});
|
|
363
|
-
if (providerKey === '__BACK__') return;
|
|
364
|
-
provider = PROVIDERS[providerKey];
|
|
365
|
-
if (provider.custom) {
|
|
366
|
-
customConfig = await configureCustomProvider({ chalk, ora });
|
|
367
|
-
if (!customConfig) { step = 'provider'; continue; }
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
step = 'apikey';
|
|
371
|
-
} else if (step === 'apikey') {
|
|
372
|
-
const k = await input({
|
|
373
|
-
message: chalk.cyan(`${provider.name} API Key(输入 b 返回上一步)`),
|
|
374
|
-
transformer: (v) => v && v !== 'b' ? chalk.dim('•'.repeat(v.length)) : v,
|
|
375
|
-
validate: (v) => v.trim().length > 0 ? true : 'API Key 不能为空',
|
|
376
|
-
});
|
|
377
|
-
if (k.trim() === 'b') { step = 'provider'; continue; }
|
|
378
|
-
apiKey = k.trim();
|
|
379
|
-
step = 'model';
|
|
380
|
-
} else if (step === 'model') {
|
|
381
|
-
const fetchSpinner = ora('正在获取可用模型...').start();
|
|
382
|
-
const onlineModels = await fetchModels(providerKey, apiKey);
|
|
383
|
-
let modelChoices;
|
|
384
|
-
if (onlineModels && onlineModels.length > 0) {
|
|
385
|
-
fetchSpinner.succeed(chalk.green(`已获取 ${onlineModels.length} 个可用模型`));
|
|
386
|
-
modelChoices = onlineModels.map(id => ({ name: id, value: id }));
|
|
387
|
-
} else {
|
|
388
|
-
fetchSpinner.warn(chalk.yellow('无法获取在线列表,使用内置默认列表'));
|
|
389
|
-
modelChoices = provider.models;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const m = await select({ loop: false,
|
|
393
|
-
message: chalk.cyan('选择模型'),
|
|
394
|
-
choices: [
|
|
395
|
-
...modelChoices,
|
|
396
|
-
{ name: chalk.dim('↩ 返回上一步(重新输入 Key)'), value: '__BACK__' },
|
|
397
|
-
],
|
|
398
|
-
});
|
|
399
|
-
if (m === '__BACK__') { step = 'apikey'; continue; }
|
|
400
|
-
model = m;
|
|
401
|
-
break;
|
|
402
|
-
}
|
|
464
|
+
const configProblem = getConfigValidationMessage(config);
|
|
465
|
+
if (configProblem) {
|
|
466
|
+
console.log(chalk.red(`\n配置无效:${configProblem}`));
|
|
467
|
+
console.log(chalk.dim('请运行 claw config 重新配置。\n'));
|
|
468
|
+
return;
|
|
403
469
|
}
|
|
404
470
|
|
|
405
|
-
const spinner = ora('
|
|
406
|
-
let
|
|
471
|
+
const spinner = ora('写入 Claude Code 终端环境变量...').start();
|
|
472
|
+
let file;
|
|
407
473
|
try {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
saveConfig(cfg);
|
|
411
|
-
({ result, file } = writeEnvToZshrc(cfg.baseUrl, cfg.apiKey, cfg.model, cfg.fastModel));
|
|
412
|
-
spinner.succeed(chalk.green(result === 'updated' ? `环境变量已更新 → ${file}` : `环境变量已写入 → ${file}`));
|
|
474
|
+
({ file } = writeEnvToZshrc(config.baseUrl, config.apiKey, config.model, config.fastModel));
|
|
475
|
+
spinner.succeed(chalk.green(`Claude Code 终端已接入 → ${file}`));
|
|
413
476
|
} catch (e) {
|
|
414
477
|
spinner.fail(chalk.red(`写入失败: ${e.message}`));
|
|
415
478
|
return;
|
|
416
479
|
}
|
|
417
|
-
console.log(chalk.dim(getStorageHint(file)));
|
|
418
480
|
|
|
481
|
+
console.log(chalk.dim(getStorageHint(file)));
|
|
419
482
|
console.log(boxen(
|
|
420
|
-
chalk.bold('
|
|
421
|
-
chalk.dim('
|
|
422
|
-
chalk.dim('
|
|
423
|
-
chalk.white('
|
|
483
|
+
chalk.bold('Claude Code 终端已接入\n\n') +
|
|
484
|
+
chalk.dim('Base URL ') + chalk.cyan(config.baseUrl) + '\n' +
|
|
485
|
+
chalk.dim('模型 ') + chalk.yellow(config.model) + '\n\n' +
|
|
486
|
+
chalk.white('需要启动时,在主菜单选择“启动 Claude Code”,或直接输入 ') + chalk.cyan.bold('claude') + '\n' +
|
|
487
|
+
chalk.dim(getActivationHint(file)),
|
|
424
488
|
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
425
489
|
));
|
|
490
|
+
});
|
|
426
491
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
492
|
+
program
|
|
493
|
+
.command('code-reset')
|
|
494
|
+
.description('恢复 Claude Code 终端默认配置')
|
|
495
|
+
.action(async () => {
|
|
496
|
+
const chalk = (await import('chalk')).default;
|
|
497
|
+
const ora = (await import('ora')).default;
|
|
498
|
+
const boxen = (await import('boxen')).default;
|
|
499
|
+
|
|
500
|
+
console.log(await getBanner());
|
|
430
501
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}).on('error', () => {
|
|
435
|
-
console.log(chalk.yellow('\nClaude Code 未找到,请先运行: claw install-claude'));
|
|
502
|
+
const yes = await confirm({
|
|
503
|
+
message: chalk.red('确定要恢复 Claude Code 终端默认配置吗?API 连接和桌面配置不会被清除'),
|
|
504
|
+
default: false,
|
|
436
505
|
});
|
|
506
|
+
if (!yes) {
|
|
507
|
+
console.log(chalk.dim('已取消'));
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const spinner = ora('正在恢复 Claude Code 终端默认配置...').start();
|
|
512
|
+
const cleared = clearClaudeCodeEnv();
|
|
513
|
+
await new Promise(r => setTimeout(r, 300));
|
|
514
|
+
|
|
515
|
+
if (cleared.length === 0) {
|
|
516
|
+
spinner.warn(chalk.yellow('没有找到 Claude Code 终端环境变量,无需恢复'));
|
|
517
|
+
} else {
|
|
518
|
+
spinner.succeed(chalk.green('Claude Code 终端已恢复默认'));
|
|
519
|
+
const resetNote = cleared.includes('Windows 用户环境变量')
|
|
520
|
+
? '注:当前终端的环境变量还在内存中,重新打开 PowerShell / CMD 后才彻底清除'
|
|
521
|
+
: '注:当前终端的环境变量还在内存中,重开终端或 unset 才彻底清除';
|
|
522
|
+
console.log(boxen(
|
|
523
|
+
chalk.bold('已清除以下位置中的 Claude Code 终端环境变量:\n\n') +
|
|
524
|
+
cleared.map(f => chalk.cyan(' • ' + f)).join('\n') +
|
|
525
|
+
'\n\n' + chalk.dim(resetNote),
|
|
526
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
527
|
+
));
|
|
528
|
+
}
|
|
437
529
|
});
|
|
438
530
|
|
|
439
531
|
program
|
|
440
532
|
.command('switch')
|
|
441
|
-
.description('
|
|
533
|
+
.description('快速切换模型(只更新 API 连接)')
|
|
442
534
|
.action(async () => {
|
|
443
535
|
const chalk = (await import('chalk')).default;
|
|
444
536
|
const ora = (await import('ora')).default;
|
|
@@ -447,13 +539,13 @@ program
|
|
|
447
539
|
|
|
448
540
|
const config = loadConfig();
|
|
449
541
|
if (!config) {
|
|
450
|
-
console.log(chalk.red('\n未配置,请先运行: claw
|
|
542
|
+
console.log(chalk.red('\n未配置,请先运行: claw config\n'));
|
|
451
543
|
return;
|
|
452
544
|
}
|
|
453
545
|
const configProblem = getConfigValidationMessage(config);
|
|
454
546
|
if (configProblem) {
|
|
455
547
|
console.log(chalk.red(`\n配置无效:${configProblem}`));
|
|
456
|
-
console.log(chalk.dim('请运行 claw
|
|
548
|
+
console.log(chalk.dim('请运行 claw config 重新配置。\n'));
|
|
457
549
|
return;
|
|
458
550
|
}
|
|
459
551
|
|
|
@@ -473,10 +565,9 @@ program
|
|
|
473
565
|
|
|
474
566
|
const spinner = ora('切换中...').start();
|
|
475
567
|
saveConfig(customConfig);
|
|
476
|
-
const { file } = writeEnvToZshrc(customConfig.baseUrl, customConfig.apiKey, customConfig.model, customConfig.fastModel);
|
|
477
568
|
await new Promise(r => setTimeout(r, 300));
|
|
478
|
-
spinner.succeed(chalk.green(
|
|
479
|
-
console.log(chalk.dim(
|
|
569
|
+
spinner.succeed(chalk.green(`API 连接已切换至 ${customConfig.providerName} · ${customConfig.model}`));
|
|
570
|
+
console.log(chalk.dim('如需让外部 claude 命令使用新模型,请运行 claw code。'));
|
|
480
571
|
return;
|
|
481
572
|
}
|
|
482
573
|
|
|
@@ -519,10 +610,9 @@ program
|
|
|
519
610
|
const fastModel = resolveFastModel(provider, model);
|
|
520
611
|
const newConfig = { ...config, provider: providerKey, model, fastModel, baseUrl: provider.baseUrl, apiKey };
|
|
521
612
|
saveConfig(newConfig);
|
|
522
|
-
const { file } = writeEnvToZshrc(provider.baseUrl, apiKey, model, fastModel);
|
|
523
613
|
await new Promise(r => setTimeout(r, 300));
|
|
524
|
-
spinner.succeed(chalk.green(
|
|
525
|
-
console.log(chalk.dim(
|
|
614
|
+
spinner.succeed(chalk.green(`API 连接已切换至 ${provider.name} · ${model}`));
|
|
615
|
+
console.log(chalk.dim('如需让外部 claude 命令使用新模型,请运行 claw code。'));
|
|
526
616
|
});
|
|
527
617
|
|
|
528
618
|
program
|
|
@@ -532,7 +622,7 @@ program
|
|
|
532
622
|
|
|
533
623
|
program
|
|
534
624
|
.command('desktop')
|
|
535
|
-
.description('
|
|
625
|
+
.description('接入 Claude 桌面应用使用当前模型')
|
|
536
626
|
.action(async () => {
|
|
537
627
|
const chalk = (await import('chalk')).default;
|
|
538
628
|
const ora = (await import('ora')).default;
|
|
@@ -542,13 +632,13 @@ program
|
|
|
542
632
|
|
|
543
633
|
const config = loadConfig();
|
|
544
634
|
if (!config) {
|
|
545
|
-
console.log(chalk.red('\n
|
|
635
|
+
console.log(chalk.red('\n未配置 API 连接,请先运行: claw config\n'));
|
|
546
636
|
return;
|
|
547
637
|
}
|
|
548
638
|
const configProblem = getConfigValidationMessage(config);
|
|
549
639
|
if (configProblem) {
|
|
550
640
|
console.log(chalk.red(`\n配置无效:${configProblem}`));
|
|
551
|
-
console.log(chalk.dim('请运行 claw
|
|
641
|
+
console.log(chalk.dim('请运行 claw config 重新配置。\n'));
|
|
552
642
|
return;
|
|
553
643
|
}
|
|
554
644
|
|
|
@@ -576,31 +666,72 @@ program
|
|
|
576
666
|
chalk.dim('Base URL ') + chalk.cyan(config.baseUrl) + '\n' +
|
|
577
667
|
chalk.dim('模型 ') + chalk.yellow(config.model) + '\n' +
|
|
578
668
|
chalk.dim('认证方式 ') + chalk.cyan('bearer') + '\n\n' +
|
|
579
|
-
chalk.yellow(
|
|
669
|
+
chalk.yellow(getDesktopOpenHint()) + '\n' +
|
|
580
670
|
chalk.dim('要求:网关必须支持 Anthropic POST /v1/messages,且 Base URL 必须是 HTTPS。'),
|
|
581
671
|
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'cyan', margin: { top: 1, bottom: 1 } }
|
|
582
672
|
));
|
|
583
673
|
|
|
584
674
|
if (process.platform === 'darwin') {
|
|
585
|
-
const
|
|
586
|
-
if (
|
|
587
|
-
const
|
|
675
|
+
const shouldOpen = await confirm({ message: '是否现在打开 Claude 桌面应用?', default: true });
|
|
676
|
+
if (shouldOpen) {
|
|
677
|
+
const openSpinner = ora('正在打开 Claude 桌面应用...').start();
|
|
588
678
|
try {
|
|
589
|
-
await
|
|
590
|
-
|
|
679
|
+
await openClaudeDesktop();
|
|
680
|
+
openSpinner.succeed(chalk.green('Claude 桌面应用已重新打开(旧实例已退出,新配置生效)'));
|
|
591
681
|
} catch (e) {
|
|
592
|
-
|
|
593
|
-
console.log(chalk.dim(
|
|
682
|
+
openSpinner.fail(chalk.red(`打开失败: ${e.message}`));
|
|
683
|
+
console.log(chalk.dim(getDesktopOpenHint()));
|
|
594
684
|
}
|
|
595
685
|
}
|
|
596
686
|
} else {
|
|
597
|
-
console.log(chalk.dim(
|
|
687
|
+
console.log(chalk.dim(getDesktopOpenHint()));
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
program
|
|
692
|
+
.command('desktop-reset')
|
|
693
|
+
.description('恢复 Claude 桌面应用默认配置')
|
|
694
|
+
.action(async () => {
|
|
695
|
+
const chalk = (await import('chalk')).default;
|
|
696
|
+
const ora = (await import('ora')).default;
|
|
697
|
+
const boxen = (await import('boxen')).default;
|
|
698
|
+
|
|
699
|
+
console.log(await getBanner());
|
|
700
|
+
|
|
701
|
+
if (process.platform !== 'darwin' && process.platform !== 'win32') {
|
|
702
|
+
console.log(chalk.yellow('\nClaude 桌面应用 3P 配置目前仅支持 macOS / Windows。\n'));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const yes = await confirm({
|
|
707
|
+
message: chalk.red('确定要恢复 Claude 桌面应用默认配置吗?终端配置不会被清除'),
|
|
708
|
+
default: false,
|
|
709
|
+
});
|
|
710
|
+
if (!yes) {
|
|
711
|
+
console.log(chalk.dim('已取消'));
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const spinner = ora('正在恢复 Claude 桌面应用默认配置...').start();
|
|
716
|
+
const result = clearClaudeDesktopConfig();
|
|
717
|
+
await new Promise(r => setTimeout(r, 300));
|
|
718
|
+
|
|
719
|
+
if (result.result === 'updated') {
|
|
720
|
+
spinner.succeed(chalk.green('Claude 桌面应用已恢复默认'));
|
|
721
|
+
console.log(boxen(
|
|
722
|
+
chalk.bold('已清除 Claude 桌面应用第三方推理配置:\n\n') +
|
|
723
|
+
chalk.cyan(' • ' + result.file) +
|
|
724
|
+
'\n\n' + chalk.dim('如 Claude Desktop 已打开,请完全退出后重新打开。'),
|
|
725
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
726
|
+
));
|
|
727
|
+
} else {
|
|
728
|
+
spinner.warn(chalk.yellow('没有找到 Claude 桌面应用第三方推理配置,无需恢复'));
|
|
598
729
|
}
|
|
599
730
|
});
|
|
600
731
|
|
|
601
732
|
program
|
|
602
733
|
.command('reset')
|
|
603
|
-
.description('
|
|
734
|
+
.description('清除 API 连接、终端环境变量和桌面配置')
|
|
604
735
|
.action(async () => {
|
|
605
736
|
const chalk = (await import('chalk')).default;
|
|
606
737
|
const ora = (await import('ora')).default;
|
|
@@ -671,59 +802,113 @@ async function renderStatusBar(apiStatus) {
|
|
|
671
802
|
return config ? ` ${cfgPart}` : ` ${claudeIcon} ${claudeText} ${cfgPart}`;
|
|
672
803
|
}
|
|
673
804
|
|
|
805
|
+
// 缓存上次校验的 config 哈希和结果,避免每次回菜单都重检
|
|
806
|
+
let lastCheckedHash = null;
|
|
807
|
+
let lastCheckResult; // undefined / true / false / null
|
|
808
|
+
|
|
809
|
+
function configHash(cfg) {
|
|
810
|
+
if (!cfg) return null;
|
|
811
|
+
return JSON.stringify({ p: cfg.provider, b: cfg.baseUrl, k: cfg.apiKey, m: cfg.model });
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
async function maybeCheckApi(config, forceRecheck) {
|
|
815
|
+
const hash = configHash(config);
|
|
816
|
+
if (!forceRecheck && hash === lastCheckedHash && lastCheckResult !== undefined) {
|
|
817
|
+
return lastCheckResult;
|
|
818
|
+
}
|
|
819
|
+
const result = await validateKey(config);
|
|
820
|
+
lastCheckedHash = hash;
|
|
821
|
+
lastCheckResult = result;
|
|
822
|
+
return result;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const ADVANCED_DISABLED_HINT = '需先配置 API 连接';
|
|
826
|
+
|
|
827
|
+
async function runAdvancedMenu(chalk, hasConfig) {
|
|
828
|
+
const action = await select({ loop: false,
|
|
829
|
+
message: chalk.cyan('高级选项'),
|
|
830
|
+
choices: [
|
|
831
|
+
{ name: '🔁 重新检测 API', value: 'recheck', disabled: !hasConfig && ADVANCED_DISABLED_HINT },
|
|
832
|
+
{ name: '↩️ 恢复 Claude Code 终端默认', value: 'code-reset' },
|
|
833
|
+
{ name: '↩️ 恢复 Claude 桌面默认', value: 'desktop-reset' },
|
|
834
|
+
{ name: '🗑 清除所有 yingclaw 配置', value: 'reset' },
|
|
835
|
+
{ name: chalk.dim('↩ 返回主菜单'), value: '__BACK__' },
|
|
836
|
+
],
|
|
837
|
+
});
|
|
838
|
+
return action;
|
|
839
|
+
}
|
|
840
|
+
|
|
674
841
|
async function runMenu() {
|
|
675
842
|
const chalk = (await import('chalk')).default;
|
|
676
843
|
const ora = (await import('ora')).default;
|
|
677
844
|
|
|
845
|
+
let forceRecheck = false;
|
|
846
|
+
|
|
678
847
|
while (true) {
|
|
679
848
|
console.clear();
|
|
680
849
|
console.log(await getBanner());
|
|
681
850
|
|
|
682
851
|
const config = loadConfig();
|
|
683
|
-
let apiStatus; // undefined = skipped, true/false/null = checked
|
|
684
852
|
const configProblem = config ? getConfigValidationMessage(config) : null;
|
|
853
|
+
let apiStatus;
|
|
854
|
+
|
|
685
855
|
if (config && !configProblem) {
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
856
|
+
const hash = configHash(config);
|
|
857
|
+
if (forceRecheck || hash !== lastCheckedHash || lastCheckResult === undefined) {
|
|
858
|
+
const spinner = ora('正在检测 API...').start();
|
|
859
|
+
apiStatus = await maybeCheckApi(config, true);
|
|
860
|
+
if (apiStatus === true) spinner.succeed('API 连接正常');
|
|
861
|
+
else if (apiStatus === false) spinner.fail('API Key 无效或已过期');
|
|
862
|
+
else spinner.warn('网络异常,无法连接 API');
|
|
863
|
+
} else {
|
|
864
|
+
apiStatus = lastCheckResult;
|
|
865
|
+
}
|
|
866
|
+
forceRecheck = false;
|
|
691
867
|
}
|
|
692
868
|
|
|
693
869
|
if (configProblem) {
|
|
694
870
|
console.log(chalk.red(` ● 配置无效:${configProblem}`));
|
|
695
|
-
console.log(chalk.dim('
|
|
871
|
+
console.log(chalk.dim(' 请先选择"配置 API 连接"重新配置'));
|
|
696
872
|
} else {
|
|
697
873
|
console.log(await renderStatusBar(apiStatus));
|
|
698
874
|
}
|
|
699
875
|
console.log();
|
|
700
876
|
|
|
877
|
+
const disabledHint = (!config || configProblem) && '需先配置 API 连接';
|
|
878
|
+
|
|
701
879
|
const action = await select({ loop: false,
|
|
702
880
|
message: chalk.cyan('选择操作'),
|
|
703
881
|
choices: [
|
|
704
|
-
{ name: '🤖 启动 Claude Code', value: 'launch', disabled:
|
|
882
|
+
{ name: '🤖 启动 Claude Code', value: 'launch', disabled: disabledHint },
|
|
705
883
|
{ name: '📦 安装 Claude Code', value: 'install' },
|
|
706
|
-
{ name: config ? '
|
|
707
|
-
{ name: '🔄 切换厂商或模型', value: 'switch', disabled:
|
|
708
|
-
{ name: '
|
|
709
|
-
{ name: '
|
|
710
|
-
{ name: '
|
|
711
|
-
{ name: '
|
|
884
|
+
{ name: config ? '🔑 重新配置 API 连接' : '🔑 配置 API 连接', value: 'config' },
|
|
885
|
+
{ name: '🔄 切换厂商或模型', value: 'switch', disabled: disabledHint },
|
|
886
|
+
{ name: '💻 接入 Claude Code 终端', value: 'code', disabled: disabledHint },
|
|
887
|
+
{ name: '🖥 接入 Claude 桌面应用', value: 'desktop', disabled: disabledHint },
|
|
888
|
+
{ name: '📊 查看当前配置', value: 'status', disabled: !config && '需先配置 API 连接' },
|
|
889
|
+
{ name: '🛠 高级 ›', value: 'advanced' },
|
|
712
890
|
{ name: '退出', value: 'exit' },
|
|
713
891
|
],
|
|
714
892
|
});
|
|
715
893
|
|
|
716
894
|
if (action === 'exit') return;
|
|
717
895
|
|
|
718
|
-
|
|
719
|
-
|
|
896
|
+
let resolvedAction = action;
|
|
897
|
+
if (action === 'advanced') {
|
|
898
|
+
const adv = await runAdvancedMenu(chalk, !!config && !configProblem);
|
|
899
|
+
if (adv === '__BACK__') continue;
|
|
900
|
+
resolvedAction = adv;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
if (resolvedAction === 'recheck') {
|
|
904
|
+
lastCheckResult = undefined;
|
|
905
|
+
forceRecheck = true;
|
|
720
906
|
continue;
|
|
721
907
|
}
|
|
722
908
|
|
|
723
|
-
if (
|
|
909
|
+
if (resolvedAction === 'launch') {
|
|
724
910
|
const cfg = loadConfig();
|
|
725
911
|
if (!cfg || getConfigValidationMessage(cfg)) continue;
|
|
726
|
-
// 启动 claude 后等它退出,然后回菜单
|
|
727
912
|
await new Promise((resolve) => {
|
|
728
913
|
const child = spawn('claude', [], {
|
|
729
914
|
stdio: 'inherit',
|
|
@@ -740,21 +925,29 @@ async function runMenu() {
|
|
|
740
925
|
|
|
741
926
|
const cmdMap = {
|
|
742
927
|
install: 'install-claude',
|
|
743
|
-
|
|
928
|
+
config: 'config',
|
|
929
|
+
code: 'code',
|
|
930
|
+
'code-reset': 'code-reset',
|
|
744
931
|
switch: 'switch',
|
|
745
932
|
desktop: 'desktop',
|
|
933
|
+
'desktop-reset': 'desktop-reset',
|
|
746
934
|
status: 'status',
|
|
747
935
|
reset: 'reset',
|
|
748
936
|
};
|
|
749
937
|
|
|
750
|
-
//
|
|
938
|
+
// 执行子命令(用 spawn 隔离,避免 commander 对 program 的副作用)
|
|
751
939
|
await new Promise((resolve) => {
|
|
752
|
-
const child = spawn(process.execPath, [__filename, cmdMap[
|
|
940
|
+
const child = spawn(process.execPath, [__filename, cmdMap[resolvedAction]], { stdio: 'inherit' });
|
|
753
941
|
child.on('exit', resolve);
|
|
754
942
|
child.on('error', resolve);
|
|
755
943
|
});
|
|
756
944
|
|
|
757
|
-
//
|
|
945
|
+
// 改 config 的命令需要刷新缓存
|
|
946
|
+
if (['config', 'switch', 'reset', 'code-reset'].includes(resolvedAction)) {
|
|
947
|
+
lastCheckResult = undefined;
|
|
948
|
+
lastCheckedHash = null;
|
|
949
|
+
}
|
|
950
|
+
|
|
758
951
|
console.log();
|
|
759
952
|
const next = await select({ loop: false,
|
|
760
953
|
message: chalk.cyan('下一步'),
|
package/lib/config.js
CHANGED
|
@@ -330,18 +330,10 @@ function writeEnvToZshrc(baseUrl, apiKey, model, fastModel, options = {}) {
|
|
|
330
330
|
return { result: cleaned !== current ? 'updated' : 'added', file: rcFile };
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
-
|
|
334
|
-
function resetConfig(options = {}) {
|
|
333
|
+
function clearClaudeCodeEnv(options = {}) {
|
|
335
334
|
const cleared = [];
|
|
336
|
-
const configFile = options.configFile || CONFIG_FILE;
|
|
337
335
|
const platform = options.platform || process.platform;
|
|
338
336
|
|
|
339
|
-
// 删配置文件
|
|
340
|
-
if (fs.existsSync(configFile)) {
|
|
341
|
-
fs.unlinkSync(configFile);
|
|
342
|
-
cleared.push(configFile);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
337
|
if (platform === 'win32') {
|
|
346
338
|
runWindowsEnvCommands(buildWindowsClearEnvCommands(), options.runner || spawnSync, { ignoreErrors: true });
|
|
347
339
|
cleared.push(WINDOWS_ENV_LABEL);
|
|
@@ -369,12 +361,27 @@ function resetConfig(options = {}) {
|
|
|
369
361
|
return cleared;
|
|
370
362
|
}
|
|
371
363
|
|
|
364
|
+
// 清除所有 clawai 配置(配置文件 + shell 环境变量块)
|
|
365
|
+
function resetConfig(options = {}) {
|
|
366
|
+
const cleared = [];
|
|
367
|
+
const configFile = options.configFile || CONFIG_FILE;
|
|
368
|
+
|
|
369
|
+
// 删配置文件
|
|
370
|
+
if (fs.existsSync(configFile)) {
|
|
371
|
+
fs.unlinkSync(configFile);
|
|
372
|
+
cleared.push(configFile);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return [...cleared, ...clearClaudeCodeEnv(options)];
|
|
376
|
+
}
|
|
377
|
+
|
|
372
378
|
module.exports = {
|
|
373
379
|
loadConfig,
|
|
374
380
|
saveConfig,
|
|
375
381
|
writeEnvToZshrc,
|
|
376
382
|
buildWindowsSetEnvCommands,
|
|
377
383
|
buildWindowsClearEnvCommands,
|
|
384
|
+
clearClaudeCodeEnv,
|
|
378
385
|
fetchModels,
|
|
379
386
|
resetConfig,
|
|
380
387
|
validateConfig,
|
package/lib/desktop.js
CHANGED
|
@@ -119,43 +119,49 @@ function clearClaudeDesktopConfig(options = {}) {
|
|
|
119
119
|
return { result: 'updated', file };
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
function
|
|
122
|
+
function buildClaudeDesktopOpenCommands(platform = process.platform) {
|
|
123
123
|
if (platform !== 'darwin') return null;
|
|
124
|
+
// 先 quit 再 open:避免已运行的实例缓存了旧配置
|
|
124
125
|
return [
|
|
125
|
-
{ command: 'osascript', args: ['-e', 'tell application "Claude" to quit'] },
|
|
126
|
+
{ command: 'osascript', args: ['-e', 'tell application "Claude" to quit'], optional: true },
|
|
126
127
|
{ command: 'open', args: ['-a', 'Claude'] },
|
|
127
128
|
];
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
function
|
|
131
|
-
return new Promise(
|
|
131
|
+
async function sleep(ms) {
|
|
132
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
async function
|
|
135
|
+
async function openClaudeDesktop(options = {}) {
|
|
135
136
|
const platform = options.platform || process.platform;
|
|
136
|
-
const commands =
|
|
137
|
+
const commands = buildClaudeDesktopOpenCommands(platform);
|
|
137
138
|
if (!commands) return { result: 'unsupported' };
|
|
138
139
|
|
|
139
140
|
const runner = options.runner || spawnSync;
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
141
|
+
const waitMs = options.waitMs ?? 700;
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < commands.length; i++) {
|
|
144
|
+
const { command, args, optional } = commands[i];
|
|
145
|
+
const result = runner(command, args, { stdio: 'ignore', windowsHide: true });
|
|
146
|
+
// optional 命令失败(比如 Claude 没在运行)不报错
|
|
147
|
+
if (result.status !== 0 && !optional) {
|
|
148
|
+
throw new Error('Claude 桌面应用打开失败');
|
|
149
|
+
}
|
|
150
|
+
// quit 后等一下再 open,给系统清理资源的时间
|
|
151
|
+
if (i === 0 && commands.length > 1 && options.runner === undefined) {
|
|
152
|
+
await sleep(waitMs);
|
|
153
|
+
}
|
|
148
154
|
}
|
|
149
155
|
|
|
150
|
-
return { result: '
|
|
156
|
+
return { result: 'reopened' };
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
module.exports = {
|
|
154
160
|
buildClaudeDesktopEnterpriseConfig,
|
|
155
|
-
|
|
161
|
+
buildClaudeDesktopOpenCommands,
|
|
156
162
|
clearClaudeDesktopConfig,
|
|
157
163
|
getClaudeDesktopConfigPath,
|
|
158
|
-
|
|
164
|
+
openClaudeDesktop,
|
|
159
165
|
writeClaudeDesktopConfig,
|
|
160
166
|
CLAUDE_DESKTOP_LABEL,
|
|
161
167
|
};
|