yingclaw 1.3.0 → 1.5.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 +94 -39
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -119,8 +119,10 @@ program
|
|
|
119
119
|
choices: [
|
|
120
120
|
{ name: '有梯子 / 海外网络(走官方)', value: 'vpn' },
|
|
121
121
|
{ name: '国内网络 / 没有梯子(走镜像)', value: 'cn' },
|
|
122
|
+
{ name: chalk.dim('↩ 返回主菜单'), value: '__BACK__' },
|
|
122
123
|
],
|
|
123
124
|
});
|
|
125
|
+
if (network === '__BACK__') return;
|
|
124
126
|
|
|
125
127
|
const cmd = network === 'vpn'
|
|
126
128
|
? 'npm install -g @anthropic-ai/claude-code'
|
|
@@ -183,42 +185,61 @@ program
|
|
|
183
185
|
if (!overwrite) return;
|
|
184
186
|
}
|
|
185
187
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
188
|
+
let providerKey, provider, apiKey, model;
|
|
189
|
+
let step = 'provider';
|
|
190
|
+
|
|
191
|
+
while (true) {
|
|
192
|
+
if (step === 'provider') {
|
|
193
|
+
providerKey = await select({
|
|
194
|
+
message: chalk.cyan('选择 AI 厂商'),
|
|
195
|
+
choices: [
|
|
196
|
+
...Object.entries(PROVIDERS).map(([value, p]) => ({ name: p.name, value })),
|
|
197
|
+
{ name: chalk.dim('↩ 返回主菜单'), value: '__BACK__' },
|
|
198
|
+
],
|
|
199
|
+
});
|
|
200
|
+
if (providerKey === '__BACK__') return;
|
|
201
|
+
provider = PROVIDERS[providerKey];
|
|
202
|
+
step = 'apikey';
|
|
203
|
+
} else if (step === 'apikey') {
|
|
204
|
+
const k = await input({
|
|
205
|
+
message: chalk.cyan(`${provider.name} API Key(输入 b 返回上一步)`),
|
|
206
|
+
transformer: (v) => v && v !== 'b' ? chalk.dim('•'.repeat(v.length)) : v,
|
|
207
|
+
validate: (v) => v.trim().length > 0 ? true : 'API Key 不能为空',
|
|
208
|
+
});
|
|
209
|
+
if (k.trim() === 'b') { step = 'provider'; continue; }
|
|
210
|
+
apiKey = k.trim();
|
|
211
|
+
step = 'model';
|
|
212
|
+
} else if (step === 'model') {
|
|
213
|
+
const fetchSpinner = ora('正在获取可用模型...').start();
|
|
214
|
+
const onlineModels = await fetchModels(providerKey, apiKey);
|
|
215
|
+
let modelChoices;
|
|
216
|
+
if (onlineModels && onlineModels.length > 0) {
|
|
217
|
+
fetchSpinner.succeed(chalk.green(`已获取 ${onlineModels.length} 个可用模型`));
|
|
218
|
+
modelChoices = onlineModels.map(id => ({ name: id, value: id }));
|
|
219
|
+
} else {
|
|
220
|
+
fetchSpinner.warn(chalk.yellow('无法获取在线列表,使用内置默认列表'));
|
|
221
|
+
modelChoices = provider.models;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const m = await select({
|
|
225
|
+
message: chalk.cyan('选择模型'),
|
|
226
|
+
choices: [
|
|
227
|
+
...modelChoices,
|
|
228
|
+
{ name: chalk.dim('↩ 返回上一步(重新输入 Key)'), value: '__BACK__' },
|
|
229
|
+
],
|
|
230
|
+
});
|
|
231
|
+
if (m === '__BACK__') { step = 'apikey'; continue; }
|
|
232
|
+
model = m;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
209
235
|
}
|
|
210
236
|
|
|
211
|
-
const model = await select({
|
|
212
|
-
message: chalk.cyan('选择模型'),
|
|
213
|
-
choices: modelChoices,
|
|
214
|
-
});
|
|
215
|
-
|
|
216
237
|
const spinner = ora('写入配置...').start();
|
|
217
238
|
let result, file;
|
|
218
239
|
try {
|
|
219
|
-
const
|
|
220
|
-
saveConfig(
|
|
221
|
-
({ result, file } = writeEnvToZshrc(provider.baseUrl, apiKey
|
|
240
|
+
const cfg = { provider: providerKey, model, apiKey, baseUrl: provider.baseUrl };
|
|
241
|
+
saveConfig(cfg);
|
|
242
|
+
({ result, file } = writeEnvToZshrc(provider.baseUrl, apiKey));
|
|
222
243
|
spinner.succeed(chalk.green(result === 'updated' ? `环境变量已更新 → ${file}` : `环境变量已写入 → ${file}`));
|
|
223
244
|
} catch (e) {
|
|
224
245
|
spinner.fail(chalk.red(`写入失败: ${e.message}`));
|
|
@@ -229,7 +250,7 @@ program
|
|
|
229
250
|
console.log(boxen(
|
|
230
251
|
chalk.bold('配置完成!\n\n') +
|
|
231
252
|
chalk.dim('ANTHROPIC_BASE_URL ') + chalk.cyan(provider.baseUrl) + '\n' +
|
|
232
|
-
chalk.dim('ANTHROPIC_API_KEY ') + chalk.cyan(apiKey.
|
|
253
|
+
chalk.dim('ANTHROPIC_API_KEY ') + chalk.cyan(apiKey.slice(0, 10) + '...') + '\n\n' +
|
|
233
254
|
chalk.white('下次直接输入 ') + chalk.cyan.bold('claude') + chalk.white(' 即可使用'),
|
|
234
255
|
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
235
256
|
));
|
|
@@ -240,7 +261,7 @@ program
|
|
|
240
261
|
|
|
241
262
|
spawn('claude', [], {
|
|
242
263
|
stdio: 'inherit',
|
|
243
|
-
env: { ...process.env, ANTHROPIC_BASE_URL: provider.baseUrl, ANTHROPIC_API_KEY: apiKey
|
|
264
|
+
env: { ...process.env, ANTHROPIC_BASE_URL: provider.baseUrl, ANTHROPIC_API_KEY: apiKey },
|
|
244
265
|
}).on('error', () => {
|
|
245
266
|
console.log(chalk.yellow('\nClaude Code 未找到,请先运行: claw install-claude'));
|
|
246
267
|
});
|
|
@@ -263,8 +284,12 @@ program
|
|
|
263
284
|
|
|
264
285
|
const providerKey = await select({
|
|
265
286
|
message: chalk.cyan('选择 AI 厂商'),
|
|
266
|
-
choices:
|
|
287
|
+
choices: [
|
|
288
|
+
...Object.entries(PROVIDERS).map(([value, p]) => ({ name: p.name, value })),
|
|
289
|
+
{ name: chalk.dim('↩ 返回主菜单'), value: '__BACK__' },
|
|
290
|
+
],
|
|
267
291
|
});
|
|
292
|
+
if (providerKey === '__BACK__') return;
|
|
268
293
|
|
|
269
294
|
const provider = PROVIDERS[providerKey];
|
|
270
295
|
|
|
@@ -296,8 +321,12 @@ program
|
|
|
296
321
|
|
|
297
322
|
const model = await select({
|
|
298
323
|
message: chalk.cyan('选择模型'),
|
|
299
|
-
choices:
|
|
324
|
+
choices: [
|
|
325
|
+
...modelChoices,
|
|
326
|
+
{ name: chalk.dim('↩ 返回主菜单'), value: '__BACK__' },
|
|
327
|
+
],
|
|
300
328
|
});
|
|
329
|
+
if (model === '__BACK__') return;
|
|
301
330
|
|
|
302
331
|
const spinner = ora('切换中...').start();
|
|
303
332
|
const newConfig = { ...config, provider: providerKey, model, baseUrl: provider.baseUrl, apiKey };
|
|
@@ -313,7 +342,7 @@ program
|
|
|
313
342
|
.description('查看当前配置和 Key 有效性')
|
|
314
343
|
.action(showStatus);
|
|
315
344
|
|
|
316
|
-
async function renderStatusBar() {
|
|
345
|
+
async function renderStatusBar(apiStatus) {
|
|
317
346
|
const chalk = (await import('chalk')).default;
|
|
318
347
|
const config = loadConfig();
|
|
319
348
|
const claudeInstalled = (() => {
|
|
@@ -326,7 +355,16 @@ async function renderStatusBar() {
|
|
|
326
355
|
let cfgPart;
|
|
327
356
|
if (config) {
|
|
328
357
|
const provName = PROVIDERS[config.provider]?.name || config.provider;
|
|
329
|
-
|
|
358
|
+
let dot;
|
|
359
|
+
if (apiStatus === true) dot = chalk.green('●');
|
|
360
|
+
else if (apiStatus === false) dot = chalk.red('●');
|
|
361
|
+
else if (apiStatus === null) dot = chalk.yellow('●');
|
|
362
|
+
else dot = chalk.dim('●'); // 未检测
|
|
363
|
+
const apiTag = apiStatus === true ? chalk.green(' API ✓')
|
|
364
|
+
: apiStatus === false ? chalk.red(' API ✘')
|
|
365
|
+
: apiStatus === null ? chalk.yellow(' 网络异常')
|
|
366
|
+
: '';
|
|
367
|
+
cfgPart = `${dot} ${chalk.white(provName)}${chalk.dim(' · ')}${chalk.yellow(config.model)}${apiTag}`;
|
|
330
368
|
} else {
|
|
331
369
|
cfgPart = chalk.red('●') + ' ' + chalk.dim('未配置');
|
|
332
370
|
}
|
|
@@ -336,14 +374,25 @@ async function renderStatusBar() {
|
|
|
336
374
|
|
|
337
375
|
async function runMenu() {
|
|
338
376
|
const chalk = (await import('chalk')).default;
|
|
377
|
+
const ora = (await import('ora')).default;
|
|
339
378
|
|
|
340
379
|
while (true) {
|
|
341
380
|
console.clear();
|
|
342
381
|
console.log(await getBanner());
|
|
343
|
-
console.log(await renderStatusBar());
|
|
344
|
-
console.log();
|
|
345
382
|
|
|
346
383
|
const config = loadConfig();
|
|
384
|
+
let apiStatus; // undefined = skipped, true/false/null = checked
|
|
385
|
+
if (config) {
|
|
386
|
+
const spinner = ora('正在检测 API 是否通畅...').start();
|
|
387
|
+
apiStatus = await validateKey(config);
|
|
388
|
+
if (apiStatus === true) spinner.succeed('API 连接正常');
|
|
389
|
+
else if (apiStatus === false) spinner.fail('API Key 无效或已过期');
|
|
390
|
+
else spinner.warn('网络异常,无法连接 API');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
console.log(await renderStatusBar(apiStatus));
|
|
394
|
+
console.log();
|
|
395
|
+
|
|
347
396
|
const action = await select({
|
|
348
397
|
message: chalk.cyan('选择操作'),
|
|
349
398
|
choices: [
|
|
@@ -352,12 +401,18 @@ async function runMenu() {
|
|
|
352
401
|
{ name: config ? '⚙️ 重新配置(输入新的 API Key)' : '⚙️ 首次配置 API Key 和模型', value: 'setup' },
|
|
353
402
|
{ name: '🔄 切换厂商/模型(保留当前 Key)', value: 'switch', disabled: !config && '需先完成配置' },
|
|
354
403
|
{ name: '📊 查看当前配置', value: 'status', disabled: !config && '需先完成配置' },
|
|
404
|
+
{ name: '🔁 重新检测 API', value: 'recheck', disabled: !config && '需先完成配置' },
|
|
355
405
|
{ name: '退出', value: 'exit' },
|
|
356
406
|
],
|
|
357
407
|
});
|
|
358
408
|
|
|
359
409
|
if (action === 'exit') return;
|
|
360
410
|
|
|
411
|
+
if (action === 'recheck') {
|
|
412
|
+
// 仅刷新菜单,下次循环会重新检测
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
|
|
361
416
|
if (action === 'launch') {
|
|
362
417
|
const cfg = loadConfig();
|
|
363
418
|
if (!cfg) continue;
|