yingclaw 1.1.0 → 1.3.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/bin/cli.js +110 -44
- package/lib/config.js +48 -1
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { Command } = require('commander');
|
|
4
4
|
const { select, input, confirm } = require('@inquirer/prompts');
|
|
5
|
-
const { loadConfig, saveConfig, writeEnvToZshrc, PROVIDERS } = require('../lib/config');
|
|
5
|
+
const { loadConfig, saveConfig, writeEnvToZshrc, fetchModels, PROVIDERS } = require('../lib/config');
|
|
6
6
|
const { execSync, spawn, spawnSync } = require('child_process');
|
|
7
7
|
const https = require('https');
|
|
8
8
|
const pkg = require('../package.json');
|
|
@@ -190,17 +190,29 @@ program
|
|
|
190
190
|
|
|
191
191
|
const provider = PROVIDERS[providerKey];
|
|
192
192
|
|
|
193
|
-
const model = await select({
|
|
194
|
-
message: chalk.cyan('选择模型'),
|
|
195
|
-
choices: provider.models,
|
|
196
|
-
});
|
|
197
|
-
|
|
198
193
|
const apiKey = await input({
|
|
199
194
|
message: chalk.cyan(`${provider.name} API Key`),
|
|
200
195
|
transformer: (v) => v ? chalk.dim('•'.repeat(v.length)) : '',
|
|
201
196
|
validate: (v) => v.trim().length > 0 ? true : 'API Key 不能为空',
|
|
202
197
|
});
|
|
203
198
|
|
|
199
|
+
// 联网拉取支持的模型列表
|
|
200
|
+
const fetchSpinner = ora('正在获取可用模型...').start();
|
|
201
|
+
const onlineModels = await fetchModels(providerKey, apiKey.trim());
|
|
202
|
+
let modelChoices;
|
|
203
|
+
if (onlineModels && onlineModels.length > 0) {
|
|
204
|
+
fetchSpinner.succeed(chalk.green(`已获取 ${onlineModels.length} 个可用模型`));
|
|
205
|
+
modelChoices = onlineModels.map(id => ({ name: id, value: id }));
|
|
206
|
+
} else {
|
|
207
|
+
fetchSpinner.warn(chalk.yellow('无法获取在线列表,使用内置默认列表'));
|
|
208
|
+
modelChoices = provider.models;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const model = await select({
|
|
212
|
+
message: chalk.cyan('选择模型'),
|
|
213
|
+
choices: modelChoices,
|
|
214
|
+
});
|
|
215
|
+
|
|
204
216
|
const spinner = ora('写入配置...').start();
|
|
205
217
|
let result, file;
|
|
206
218
|
try {
|
|
@@ -256,11 +268,6 @@ program
|
|
|
256
268
|
|
|
257
269
|
const provider = PROVIDERS[providerKey];
|
|
258
270
|
|
|
259
|
-
const model = await select({
|
|
260
|
-
message: chalk.cyan('选择模型'),
|
|
261
|
-
choices: provider.models,
|
|
262
|
-
});
|
|
263
|
-
|
|
264
271
|
// 切换厂商时询问是否更换 Key
|
|
265
272
|
let apiKey = config.apiKey;
|
|
266
273
|
if (providerKey !== config.provider) {
|
|
@@ -275,6 +282,23 @@ program
|
|
|
275
282
|
}
|
|
276
283
|
}
|
|
277
284
|
|
|
285
|
+
// 联网拉模型
|
|
286
|
+
const fetchSpinner = ora('正在获取可用模型...').start();
|
|
287
|
+
const onlineModels = await fetchModels(providerKey, apiKey);
|
|
288
|
+
let modelChoices;
|
|
289
|
+
if (onlineModels && onlineModels.length > 0) {
|
|
290
|
+
fetchSpinner.succeed(chalk.green(`已获取 ${onlineModels.length} 个可用模型`));
|
|
291
|
+
modelChoices = onlineModels.map(id => ({ name: id, value: id }));
|
|
292
|
+
} else {
|
|
293
|
+
fetchSpinner.warn(chalk.yellow('无法获取在线列表,使用内置默认列表'));
|
|
294
|
+
modelChoices = provider.models;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const model = await select({
|
|
298
|
+
message: chalk.cyan('选择模型'),
|
|
299
|
+
choices: modelChoices,
|
|
300
|
+
});
|
|
301
|
+
|
|
278
302
|
const spinner = ora('切换中...').start();
|
|
279
303
|
const newConfig = { ...config, provider: providerKey, model, baseUrl: provider.baseUrl, apiKey };
|
|
280
304
|
saveConfig(newConfig);
|
|
@@ -289,34 +313,44 @@ program
|
|
|
289
313
|
.description('查看当前配置和 Key 有效性')
|
|
290
314
|
.action(showStatus);
|
|
291
315
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
316
|
+
async function renderStatusBar() {
|
|
317
|
+
const chalk = (await import('chalk')).default;
|
|
318
|
+
const config = loadConfig();
|
|
319
|
+
const claudeInstalled = (() => {
|
|
320
|
+
try { execSync('claude --version', { stdio: 'pipe' }); return true; } catch { return false; }
|
|
321
|
+
})();
|
|
322
|
+
|
|
323
|
+
const claudeIcon = claudeInstalled ? chalk.green('●') : chalk.red('●');
|
|
324
|
+
const claudeText = chalk.dim('Claude');
|
|
296
325
|
|
|
326
|
+
let cfgPart;
|
|
327
|
+
if (config) {
|
|
328
|
+
const provName = PROVIDERS[config.provider]?.name || config.provider;
|
|
329
|
+
cfgPart = chalk.green('●') + ' ' + chalk.white(provName) + chalk.dim(' · ') + chalk.yellow(config.model);
|
|
330
|
+
} else {
|
|
331
|
+
cfgPart = chalk.red('●') + ' ' + chalk.dim('未配置');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return ` ${claudeIcon} ${claudeText} ${cfgPart}`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function runMenu() {
|
|
338
|
+
const chalk = (await import('chalk')).default;
|
|
339
|
+
|
|
340
|
+
while (true) {
|
|
341
|
+
console.clear();
|
|
297
342
|
console.log(await getBanner());
|
|
343
|
+
console.log(await renderStatusBar());
|
|
344
|
+
console.log();
|
|
298
345
|
|
|
299
346
|
const config = loadConfig();
|
|
300
|
-
const claudeInstalled = (() => {
|
|
301
|
-
try { execSync('claude --version', { stdio: 'pipe' }); return true; } catch { return false; }
|
|
302
|
-
})();
|
|
303
|
-
|
|
304
|
-
// 根据当前状态生成提示
|
|
305
|
-
if (config) {
|
|
306
|
-
console.log(chalk.green(` 当前:${PROVIDERS[config.provider]?.name || config.provider} · ${config.model}\n`));
|
|
307
|
-
} else if (claudeInstalled) {
|
|
308
|
-
console.log(chalk.yellow(' Claude Code 已安装,还未配置 API\n'));
|
|
309
|
-
} else {
|
|
310
|
-
console.log(chalk.dim(' 首次使用,请先安装 Claude Code\n'));
|
|
311
|
-
}
|
|
312
|
-
|
|
313
347
|
const action = await select({
|
|
314
348
|
message: chalk.cyan('选择操作'),
|
|
315
349
|
choices: [
|
|
316
350
|
{ name: '🤖 启动 Claude Code', value: 'launch', disabled: !config && '需先完成配置' },
|
|
317
351
|
{ name: '📦 安装 Claude Code', value: 'install' },
|
|
318
|
-
{ name: '⚙️
|
|
319
|
-
{ name: '🔄
|
|
352
|
+
{ name: config ? '⚙️ 重新配置(输入新的 API Key)' : '⚙️ 首次配置 API Key 和模型', value: 'setup' },
|
|
353
|
+
{ name: '🔄 切换厂商/模型(保留当前 Key)', value: 'switch', disabled: !config && '需先完成配置' },
|
|
320
354
|
{ name: '📊 查看当前配置', value: 'status', disabled: !config && '需先完成配置' },
|
|
321
355
|
{ name: '退出', value: 'exit' },
|
|
322
356
|
],
|
|
@@ -324,24 +358,56 @@ if (process.argv.length === 2) {
|
|
|
324
358
|
|
|
325
359
|
if (action === 'exit') return;
|
|
326
360
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
361
|
+
if (action === 'launch') {
|
|
362
|
+
const cfg = loadConfig();
|
|
363
|
+
if (!cfg) continue;
|
|
364
|
+
// 启动 claude 后等它退出,然后回菜单
|
|
365
|
+
await new Promise((resolve) => {
|
|
366
|
+
const child = spawn('claude', [], {
|
|
332
367
|
stdio: 'inherit',
|
|
333
|
-
env: { ...process.env, ANTHROPIC_BASE_URL:
|
|
334
|
-
})
|
|
368
|
+
env: { ...process.env, ANTHROPIC_BASE_URL: cfg.baseUrl, ANTHROPIC_API_KEY: cfg.apiKey },
|
|
369
|
+
});
|
|
370
|
+
child.on('error', () => {
|
|
335
371
|
console.log(chalk.yellow('\nClaude Code 未找到,请先选择"安装 Claude Code"'));
|
|
372
|
+
resolve();
|
|
336
373
|
});
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
374
|
+
child.on('exit', resolve);
|
|
375
|
+
});
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const cmdMap = {
|
|
380
|
+
install: 'install-claude',
|
|
381
|
+
setup: 'setup',
|
|
382
|
+
switch: 'switch',
|
|
383
|
+
status: 'status',
|
|
342
384
|
};
|
|
343
|
-
|
|
344
|
-
|
|
385
|
+
|
|
386
|
+
// 执行子命令
|
|
387
|
+
await new Promise((resolve) => {
|
|
388
|
+
const child = spawn(process.execPath, [__filename, cmdMap[action]], { stdio: 'inherit' });
|
|
389
|
+
child.on('exit', resolve);
|
|
390
|
+
child.on('error', resolve);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// 操作结束,提示返回菜单
|
|
394
|
+
console.log();
|
|
395
|
+
const next = await select({
|
|
396
|
+
message: chalk.cyan('下一步'),
|
|
397
|
+
choices: [
|
|
398
|
+
{ name: '↩ 返回主菜单', value: 'menu' },
|
|
399
|
+
{ name: '退出', value: 'exit' },
|
|
400
|
+
],
|
|
401
|
+
});
|
|
402
|
+
if (next === 'exit') return;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (process.argv.length === 2) {
|
|
407
|
+
runMenu().catch((e) => {
|
|
408
|
+
if (e?.name === 'ExitPromptError') return; // Ctrl+C
|
|
409
|
+
console.error(e);
|
|
410
|
+
});
|
|
345
411
|
} else {
|
|
346
412
|
program.parse(process.argv);
|
|
347
413
|
}
|
package/lib/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
|
+
const https = require('https');
|
|
4
5
|
|
|
5
6
|
const CONFIG_FILE = path.join(os.homedir(), '.clawai.json');
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ const PROVIDERS = {
|
|
|
8
9
|
deepseek: {
|
|
9
10
|
name: 'DeepSeek',
|
|
10
11
|
baseUrl: 'https://api.deepseek.com/anthropic',
|
|
12
|
+
modelsUrl: 'https://api.deepseek.com/v1/models',
|
|
11
13
|
models: [
|
|
12
14
|
{ name: 'DeepSeek V4 Flash(快速)', value: 'deepseek-v4-flash' },
|
|
13
15
|
{ name: 'DeepSeek V4 Pro(强力)', value: 'deepseek-v4-pro' },
|
|
@@ -16,6 +18,7 @@ const PROVIDERS = {
|
|
|
16
18
|
qwen: {
|
|
17
19
|
name: '阿里云百炼 (Qwen)',
|
|
18
20
|
baseUrl: 'https://dashscope.aliyuncs.com/apps/anthropic',
|
|
21
|
+
modelsUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1/models',
|
|
19
22
|
models: [
|
|
20
23
|
{ name: 'Qwen3 Max(强力)', value: 'qwen3-max' },
|
|
21
24
|
{ name: 'Qwen3 Plus(均衡)', value: 'qwen3-plus' },
|
|
@@ -25,6 +28,7 @@ const PROVIDERS = {
|
|
|
25
28
|
minimax: {
|
|
26
29
|
name: 'MiniMax',
|
|
27
30
|
baseUrl: 'https://api.minimaxi.com/anthropic',
|
|
31
|
+
modelsUrl: 'https://api.minimaxi.com/v1/models',
|
|
28
32
|
models: [
|
|
29
33
|
{ name: 'MiniMax M2.7(旗舰)', value: 'MiniMax-M2.7' },
|
|
30
34
|
{ name: 'MiniMax M2.7 Turbo(快速)', value: 'MiniMax-M2.7-Turbo' },
|
|
@@ -34,6 +38,7 @@ const PROVIDERS = {
|
|
|
34
38
|
glm: {
|
|
35
39
|
name: '智谱 GLM',
|
|
36
40
|
baseUrl: 'https://open.bigmodel.cn/api/anthropic',
|
|
41
|
+
modelsUrl: 'https://open.bigmodel.cn/api/paas/v4/models',
|
|
37
42
|
models: [
|
|
38
43
|
{ name: 'GLM-4.7(旗舰)', value: 'GLM-4.7' },
|
|
39
44
|
{ name: 'GLM-5.1(强力)', value: 'GLM-5.1' },
|
|
@@ -44,12 +49,54 @@ const PROVIDERS = {
|
|
|
44
49
|
mimo: {
|
|
45
50
|
name: '小米 MiMo',
|
|
46
51
|
baseUrl: 'https://api.xiaomimimo.com/anthropic',
|
|
52
|
+
modelsUrl: 'https://api.xiaomimimo.com/v1/models',
|
|
47
53
|
models: [
|
|
48
54
|
{ name: 'MiMo V2.5 Pro(旗舰)', value: 'mimo-v2.5-pro' },
|
|
49
55
|
],
|
|
50
56
|
},
|
|
51
57
|
};
|
|
52
58
|
|
|
59
|
+
// 联网拉取厂商支持的模型列表,失败返回 null
|
|
60
|
+
function fetchModels(providerKey, apiKey) {
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
const provider = PROVIDERS[providerKey];
|
|
63
|
+
if (!provider?.modelsUrl) return resolve(null);
|
|
64
|
+
|
|
65
|
+
let url;
|
|
66
|
+
try { url = new URL(provider.modelsUrl); } catch { return resolve(null); }
|
|
67
|
+
|
|
68
|
+
const req = https.request({
|
|
69
|
+
hostname: url.hostname,
|
|
70
|
+
path: url.pathname,
|
|
71
|
+
method: 'GET',
|
|
72
|
+
timeout: 6000,
|
|
73
|
+
headers: {
|
|
74
|
+
'authorization': `Bearer ${apiKey}`,
|
|
75
|
+
'api-key': apiKey, // MiMo 用这个 header
|
|
76
|
+
},
|
|
77
|
+
}, (res) => {
|
|
78
|
+
let data = '';
|
|
79
|
+
res.on('data', (c) => data += c);
|
|
80
|
+
res.on('end', () => {
|
|
81
|
+
try {
|
|
82
|
+
const parsed = JSON.parse(data);
|
|
83
|
+
// OpenAI 格式: { data: [{ id, ... }] }
|
|
84
|
+
// GLM 格式: { data: [{ id, ... }] } 类似
|
|
85
|
+
const list = parsed.data || parsed.models || [];
|
|
86
|
+
const ids = list.map(m => m.id || m.model || m.name).filter(Boolean);
|
|
87
|
+
if (ids.length === 0) return resolve(null);
|
|
88
|
+
resolve(ids);
|
|
89
|
+
} catch {
|
|
90
|
+
resolve(null);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
req.on('error', () => resolve(null));
|
|
95
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
96
|
+
req.end();
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
53
100
|
function loadConfig() {
|
|
54
101
|
try {
|
|
55
102
|
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
@@ -94,4 +141,4 @@ function writeEnvToZshrc(baseUrl, apiKey) {
|
|
|
94
141
|
}
|
|
95
142
|
}
|
|
96
143
|
|
|
97
|
-
module.exports = { loadConfig, saveConfig, writeEnvToZshrc, PROVIDERS, CONFIG_FILE };
|
|
144
|
+
module.exports = { loadConfig, saveConfig, writeEnvToZshrc, fetchModels, PROVIDERS, CONFIG_FILE };
|