shenxiang-ai-cli 0.1.0 → 0.2.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.
Files changed (2) hide show
  1. package/dist/index.js +531 -321
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/chat.ts
7
- import readline from "readline";
7
+ import inquirer from "inquirer";
8
8
 
9
9
  // src/core/providers/openai-compatible.ts
10
10
  import OpenAI from "openai";
@@ -21,7 +21,9 @@ var OpenAICompatibleProvider = class {
21
21
  }
22
22
  const client = new OpenAI({
23
23
  apiKey: config3.apiKey,
24
- baseURL: config3.baseUrl
24
+ baseURL: config3.baseUrl,
25
+ timeout: 12e4
26
+ // 2min timeout
25
27
  });
26
28
  const openaiTools = tools.map((t2) => ({
27
29
  type: "function",
@@ -70,6 +72,7 @@ var OpenAICompatibleProvider = class {
70
72
  });
71
73
  const toolCallAccumulator = /* @__PURE__ */ new Map();
72
74
  let tokenUsage;
75
+ let toolCallStartEmitted = false;
73
76
  for await (const chunk of stream) {
74
77
  if (chunk.usage) {
75
78
  tokenUsage = {
@@ -85,6 +88,10 @@ var OpenAICompatibleProvider = class {
85
88
  yield { type: "text", content: delta.content };
86
89
  }
87
90
  if (delta.tool_calls) {
91
+ if (!toolCallStartEmitted) {
92
+ toolCallStartEmitted = true;
93
+ yield { type: "tool_call_start" };
94
+ }
88
95
  for (const tc of delta.tool_calls) {
89
96
  const idx = tc.index;
90
97
  if (!toolCallAccumulator.has(idx)) {
@@ -176,14 +183,18 @@ var OpenAICompatibleProvider = class {
176
183
  body.tools = kimiTools;
177
184
  }
178
185
  try {
186
+ const controller = new AbortController();
187
+ const connectTimeout = setTimeout(() => controller.abort(), 3e4);
179
188
  const response = await fetch(`${baseUrl}/chat/completions`, {
180
189
  method: "POST",
181
190
  headers: {
182
191
  "Content-Type": "application/json",
183
192
  "Authorization": `Bearer ${config3.apiKey}`
184
193
  },
185
- body: JSON.stringify(body)
194
+ body: JSON.stringify(body),
195
+ signal: controller.signal
186
196
  });
197
+ clearTimeout(connectTimeout);
187
198
  if (!response.ok) {
188
199
  const errText = await response.text();
189
200
  yield { type: "error", error: `${response.status} ${errText}` };
@@ -199,71 +210,83 @@ var OpenAICompatibleProvider = class {
199
210
  const toolCallAccumulator = /* @__PURE__ */ new Map();
200
211
  let tokenUsage;
201
212
  let reasoningContent = "";
202
- while (true) {
203
- const { done, value } = await reader.read();
204
- if (done) break;
205
- buffer += decoder.decode(value, { stream: true });
206
- const lines = buffer.split("\n");
207
- buffer = lines.pop() || "";
208
- for (const line of lines) {
209
- const trimmed = line.trim();
210
- if (!trimmed.startsWith("data: ")) continue;
211
- const data = trimmed.slice(6);
212
- if (data === "[DONE]") continue;
213
- let parsed;
214
- try {
215
- parsed = JSON.parse(data);
216
- } catch {
217
- continue;
218
- }
219
- if (parsed.usage) {
220
- tokenUsage = {
221
- promptTokens: parsed.usage.prompt_tokens || 0,
222
- completionTokens: parsed.usage.completion_tokens || 0,
223
- totalTokens: parsed.usage.total_tokens || 0
224
- };
225
- }
226
- const choice = parsed.choices?.[0];
227
- if (!choice) continue;
228
- const delta = choice.delta || {};
229
- if (delta.reasoning_content) {
230
- reasoningContent += delta.reasoning_content;
231
- }
232
- if (delta.content) {
233
- yield { type: "text", content: delta.content };
234
- }
235
- if (delta.tool_calls) {
236
- for (const tc of delta.tool_calls) {
237
- const idx = tc.index ?? 0;
238
- if (!toolCallAccumulator.has(idx)) {
239
- toolCallAccumulator.set(idx, { id: tc.id || "", functionName: tc.function?.name || "", arguments: "" });
240
- }
241
- const acc = toolCallAccumulator.get(idx);
242
- if (tc.id) acc.id = tc.id;
243
- if (tc.function?.name) acc.functionName = tc.function.name;
244
- if (tc.function?.arguments) acc.arguments += tc.function.arguments;
213
+ let finished = false;
214
+ let toolCallStartEmitted = false;
215
+ const READ_TIMEOUT_MS = 12e4;
216
+ try {
217
+ while (true) {
218
+ const readPromise = reader.read();
219
+ const timeoutPromise = new Promise(
220
+ (resolve) => setTimeout(() => resolve({ done: true, value: void 0 }), READ_TIMEOUT_MS)
221
+ );
222
+ const { done, value } = await Promise.race([readPromise, timeoutPromise]);
223
+ if (done) break;
224
+ buffer += decoder.decode(value, { stream: true });
225
+ const lines = buffer.split("\n");
226
+ buffer = lines.pop() || "";
227
+ for (const line of lines) {
228
+ const trimmed = line.trim();
229
+ if (!trimmed.startsWith("data: ")) continue;
230
+ const data = trimmed.slice(6);
231
+ if (data === "[DONE]") continue;
232
+ let parsed;
233
+ try {
234
+ parsed = JSON.parse(data);
235
+ } catch {
236
+ continue;
245
237
  }
246
- }
247
- if (choice.finish_reason === "tool_calls" || choice.finish_reason === "stop") {
248
- for (const [_, acc] of toolCallAccumulator) {
249
- const toolCall = {
250
- id: acc.id,
251
- type: "function",
252
- function: { name: acc.functionName, arguments: acc.arguments }
238
+ if (parsed.usage) {
239
+ tokenUsage = {
240
+ promptTokens: parsed.usage.prompt_tokens || 0,
241
+ completionTokens: parsed.usage.completion_tokens || 0,
242
+ totalTokens: parsed.usage.total_tokens || 0
253
243
  };
254
- yield { type: "tool_call", toolCall };
255
244
  }
256
- yield {
257
- type: "done",
258
- usage: tokenUsage,
259
- content: reasoningContent || void 0
260
- };
261
- return;
245
+ const choice = parsed.choices?.[0];
246
+ if (!choice) continue;
247
+ const delta = choice.delta || {};
248
+ if (delta.reasoning_content) {
249
+ reasoningContent += delta.reasoning_content;
250
+ }
251
+ if (delta.content) {
252
+ yield { type: "text", content: delta.content };
253
+ }
254
+ if (delta.tool_calls) {
255
+ if (!toolCallStartEmitted) {
256
+ toolCallStartEmitted = true;
257
+ yield { type: "tool_call_start" };
258
+ }
259
+ for (const tc of delta.tool_calls) {
260
+ const idx = tc.index ?? 0;
261
+ if (!toolCallAccumulator.has(idx)) {
262
+ toolCallAccumulator.set(idx, { id: tc.id || "", functionName: tc.function?.name || "", arguments: "" });
263
+ }
264
+ const acc = toolCallAccumulator.get(idx);
265
+ if (tc.id) acc.id = tc.id;
266
+ if (tc.function?.name) acc.functionName = tc.function.name;
267
+ if (tc.function?.arguments) acc.arguments += tc.function.arguments;
268
+ }
269
+ }
270
+ if (choice.finish_reason === "tool_calls" || choice.finish_reason === "stop") {
271
+ finished = true;
272
+ break;
273
+ }
262
274
  }
275
+ if (finished) break;
276
+ }
277
+ } finally {
278
+ try {
279
+ reader.cancel();
280
+ } catch {
263
281
  }
264
282
  }
265
283
  for (const [_, acc] of toolCallAccumulator) {
266
- yield { type: "tool_call", toolCall: { id: acc.id, type: "function", function: { name: acc.functionName, arguments: acc.arguments } } };
284
+ const toolCall = {
285
+ id: acc.id,
286
+ type: "function",
287
+ function: { name: acc.functionName, arguments: acc.arguments }
288
+ };
289
+ yield { type: "tool_call", toolCall };
267
290
  }
268
291
  yield { type: "done", usage: tokenUsage, content: reasoningContent || void 0 };
269
292
  } catch (err) {
@@ -403,22 +426,49 @@ var AnthropicProvider = class {
403
426
 
404
427
  // src/core/providers/types.ts
405
428
  var MODEL_PROVIDERS = {
406
- // Kimi (月之暗面 Moonshot)
407
- "kimi-k2.5": { provider: "kimi", displayName: "Kimi K2.5 (\u6700\u65B0\u591A\u6A21\u6001)" },
408
- "kimi-k2-0905-preview": { provider: "kimi", displayName: "Kimi K2" },
409
- "moonshot-v1-auto": { provider: "kimi", displayName: "Moonshot v1 Auto" },
410
- // OpenAI
411
- "gpt-4o": { provider: "openai", displayName: "GPT-4o" },
412
- "gpt-4o-mini": { provider: "openai", displayName: "GPT-4o Mini" },
413
- // Anthropic
414
- "claude-sonnet-4-20250514": { provider: "anthropic", displayName: "Claude Sonnet 4" },
415
- "claude-3-5-sonnet-20241022": { provider: "anthropic", displayName: "Claude 3.5 Sonnet" },
416
- // DeepSeek
417
- "deepseek-chat": { provider: "deepseek", displayName: "DeepSeek Chat" },
418
- "deepseek-coder": { provider: "deepseek", displayName: "DeepSeek Coder" },
419
- // Qwen (通义千问)
420
- "qwen-turbo": { provider: "qwen", displayName: "\u901A\u4E49\u5343\u95EE Turbo" },
421
- "qwen-plus": { provider: "qwen", displayName: "\u901A\u4E49\u5343\u95EE Plus" }
429
+ // ── Kimi (月之暗面 Moonshot) ──────────────────
430
+ "kimi-k2.5": { provider: "kimi", displayName: "Kimi K2.5", group: "Kimi \u6708\u4E4B\u6697\u9762" },
431
+ "kimi-k2-0905-preview": { provider: "kimi", displayName: "Kimi K2", group: "Kimi \u6708\u4E4B\u6697\u9762" },
432
+ "moonshot-v1-auto": { provider: "kimi", displayName: "Moonshot v1 Auto", group: "Kimi \u6708\u4E4B\u6697\u9762" },
433
+ // ── OpenAI ────────────────────────────────────
434
+ "gpt-4o": { provider: "openai", displayName: "GPT-4o", group: "OpenAI" },
435
+ "gpt-4o-mini": { provider: "openai", displayName: "GPT-4o Mini", group: "OpenAI" },
436
+ "gpt-4.1": { provider: "openai", displayName: "GPT-4.1", group: "OpenAI" },
437
+ "gpt-4.1-mini": { provider: "openai", displayName: "GPT-4.1 Mini", group: "OpenAI" },
438
+ "o3-mini": { provider: "openai", displayName: "o3-mini (\u63A8\u7406)", group: "OpenAI" },
439
+ // ── Anthropic Claude ──────────────────────────
440
+ "claude-sonnet-4-20250514": { provider: "anthropic", displayName: "Claude Sonnet 4", group: "Anthropic Claude" },
441
+ "claude-3-5-sonnet-20241022": { provider: "anthropic", displayName: "Claude 3.5 Sonnet", group: "Anthropic Claude" },
442
+ "claude-3-5-haiku-20241022": { provider: "anthropic", displayName: "Claude 3.5 Haiku", group: "Anthropic Claude" },
443
+ // ── DeepSeek ──────────────────────────────────
444
+ "deepseek-chat": { provider: "deepseek", displayName: "DeepSeek V3", group: "DeepSeek \u6DF1\u5EA6\u6C42\u7D22" },
445
+ "deepseek-reasoner": { provider: "deepseek", displayName: "DeepSeek R1 (\u63A8\u7406)", group: "DeepSeek \u6DF1\u5EA6\u6C42\u7D22" },
446
+ // ── 通义千问 Qwen ────────────────────────────
447
+ "qwen-max": { provider: "qwen", displayName: "\u901A\u4E49\u5343\u95EE Max", group: "\u901A\u4E49\u5343\u95EE Qwen" },
448
+ "qwen-plus": { provider: "qwen", displayName: "\u901A\u4E49\u5343\u95EE Plus", group: "\u901A\u4E49\u5343\u95EE Qwen" },
449
+ "qwen-turbo": { provider: "qwen", displayName: "\u901A\u4E49\u5343\u95EE Turbo", group: "\u901A\u4E49\u5343\u95EE Qwen" },
450
+ "qwen-coder-plus": { provider: "qwen", displayName: "\u901A\u4E49\u5343\u95EE Coder Plus", group: "\u901A\u4E49\u5343\u95EE Qwen" },
451
+ // ── 智谱 GLM ──────────────────────────────────
452
+ "glm-4-plus": { provider: "zhipu", displayName: "GLM-4 Plus", group: "\u667A\u8C31 GLM" },
453
+ "glm-4-flash": { provider: "zhipu", displayName: "GLM-4 Flash (\u514D\u8D39)", group: "\u667A\u8C31 GLM" },
454
+ "glm-4-long": { provider: "zhipu", displayName: "GLM-4 Long (\u957F\u6587\u672C)", group: "\u667A\u8C31 GLM" },
455
+ "codegeex-4": { provider: "zhipu", displayName: "CodeGeeX 4 (\u7F16\u7A0B)", group: "\u667A\u8C31 GLM" },
456
+ // ── 豆包 Doubao (字节跳动) ────────────────────
457
+ "doubao-1.5-pro-256k": { provider: "doubao", displayName: "\u8C46\u5305 1.5 Pro 256K", group: "\u8C46\u5305 Doubao" },
458
+ "doubao-1.5-lite-32k": { provider: "doubao", displayName: "\u8C46\u5305 1.5 Lite 32K", group: "\u8C46\u5305 Doubao" },
459
+ // ── 百川 Baichuan ────────────────────────────
460
+ "Baichuan4-Turbo": { provider: "baichuan", displayName: "\u767E\u5DDD4 Turbo", group: "\u767E\u5DDD Baichuan" },
461
+ // ── 零一万物 Yi ───────────────────────────────
462
+ "yi-lightning": { provider: "lingyiwanwu", displayName: "Yi Lightning", group: "\u96F6\u4E00\u4E07\u7269 Yi" },
463
+ // ── Gemini (Google) ───────────────────────────
464
+ "gemini-2.5-flash": { provider: "gemini", displayName: "Gemini 2.5 Flash", group: "Google Gemini" },
465
+ "gemini-2.5-pro": { provider: "gemini", displayName: "Gemini 2.5 Pro", group: "Google Gemini" },
466
+ // ── Mistral ───────────────────────────────────
467
+ "mistral-large-latest": { provider: "mistral", displayName: "Mistral Large", group: "Mistral" },
468
+ "codestral-latest": { provider: "mistral", displayName: "Codestral (\u7F16\u7A0B)", group: "Mistral" },
469
+ // ── Groq (超快推理) ──────────────────────────
470
+ "llama-3.3-70b-versatile": { provider: "groq", displayName: "Llama 3.3 70B", group: "Groq" },
471
+ "llama-3.1-8b-instant": { provider: "groq", displayName: "Llama 3.1 8B (\u6781\u901F)", group: "Groq" }
422
472
  };
423
473
 
424
474
  // src/core/providers/index.ts
@@ -427,6 +477,13 @@ var providers = {
427
477
  deepseek: new OpenAICompatibleProvider("deepseek"),
428
478
  qwen: new OpenAICompatibleProvider("qwen"),
429
479
  kimi: new OpenAICompatibleProvider("kimi"),
480
+ zhipu: new OpenAICompatibleProvider("zhipu"),
481
+ doubao: new OpenAICompatibleProvider("doubao"),
482
+ baichuan: new OpenAICompatibleProvider("baichuan"),
483
+ lingyiwanwu: new OpenAICompatibleProvider("lingyiwanwu"),
484
+ gemini: new OpenAICompatibleProvider("gemini"),
485
+ mistral: new OpenAICompatibleProvider("mistral"),
486
+ groq: new OpenAICompatibleProvider("groq"),
430
487
  anthropic: new AnthropicProvider()
431
488
  };
432
489
  var DEFAULT_BASE_URLS = {
@@ -434,7 +491,14 @@ var DEFAULT_BASE_URLS = {
434
491
  deepseek: "https://api.deepseek.com",
435
492
  qwen: "https://dashscope.aliyuncs.com/compatible-mode/v1",
436
493
  kimi: "https://api.moonshot.cn/v1",
437
- anthropic: "https://api.anthropic.com"
494
+ anthropic: "https://api.anthropic.com",
495
+ zhipu: "https://open.bigmodel.cn/api/paas/v4",
496
+ doubao: "https://ark.cn-beijing.volces.com/api/v3",
497
+ baichuan: "https://api.baichuan-ai.com/v1",
498
+ lingyiwanwu: "https://api.lingyiwanwu.com/v1",
499
+ gemini: "https://generativelanguage.googleapis.com/v1beta/openai",
500
+ mistral: "https://api.mistral.ai/v1",
501
+ groq: "https://api.groq.com/openai/v1"
438
502
  };
439
503
  function getProvider(model) {
440
504
  const info = MODEL_PROVIDERS[model];
@@ -453,7 +517,8 @@ function listModels() {
453
517
  return Object.entries(MODEL_PROVIDERS).map(([id, info]) => ({
454
518
  id,
455
519
  name: info.displayName,
456
- provider: info.provider
520
+ provider: info.provider,
521
+ group: info.group
457
522
  }));
458
523
  }
459
524
 
@@ -1090,22 +1155,25 @@ var theme = {
1090
1155
  tokenValue: chalk.hex("#A78BFA")
1091
1156
  };
1092
1157
  function banner() {
1093
- const line = theme.brandDim("\u2501".repeat(46));
1094
- const border = theme.brandDim("\u2503");
1095
- const topLeft = theme.brandDim("\u250F");
1096
- const topRight = theme.brandDim("\u2513");
1097
- const botLeft = theme.brandDim("\u2517");
1098
- const botRight = theme.brandDim("\u251B");
1099
- return `
1100
- ${topLeft}${line}${topRight}
1101
- ${border} ${border}
1102
- ${border} ${theme.brandBold("\u6C88\u7FD4\u7684AI\u52A9\u624B")} ${theme.muted("v0.1.0")} ${border}
1103
- ${border} ${theme.dim("\u7EC8\u7AEF\u91CC\u7684AI\u5168\u6808\u5F00\u53D1\u642D\u6863")} ${border}
1104
- ${border} ${border}
1105
- ${botLeft}${line}${botRight}`;
1158
+ const p = theme.brand;
1159
+ const b = theme.brandBold;
1160
+ const d = theme.dim;
1161
+ const m = theme.muted;
1162
+ const y = chalk.hex("#FBBF24");
1163
+ const r = chalk.hex("#F87171");
1164
+ const w = chalk.white.bold;
1165
+ const lines = [
1166
+ ``,
1167
+ ` ${p("/\\_/\\")} ${b("\u6C88\u7FD4\u7684AI\u52A9\u624B")} ${m("v0.2.1")}`,
1168
+ ` ${p("(")} ${y("o")}${p(".")}${y("o")} ${p(")")} ${d("\u7EC8\u7AEF\u91CC\u7684AI\u5168\u6808\u5F00\u53D1\u642D\u6863")}`,
1169
+ ` ${p("> ")}${r("^")}${p(" <")}`,
1170
+ ` ${p("/|")}${p(" |")}${p("\\")} ${d("\u4F60\u7684\u7F16\u7A0B\u597D\u4F19\u4F34")} ${chalk.hex("#A78BFA")("\u{1F43E}")}`,
1171
+ ` ${p("(_| |_)")}`
1172
+ ];
1173
+ return lines.join("\n");
1106
1174
  }
1107
1175
  function separator() {
1108
- return theme.dim("\u2500".repeat(46));
1176
+ return theme.dim("\u2500".repeat(40));
1109
1177
  }
1110
1178
 
1111
1179
  // src/ui/renderer.ts
@@ -1636,13 +1704,41 @@ function getFrameworkGuide(framework) {
1636
1704
 
1637
1705
  // src/utils/config.ts
1638
1706
  import Conf from "conf";
1707
+ var PROVIDER_KEY_MAP = {
1708
+ openai: "openaiApiKey",
1709
+ anthropic: "anthropicApiKey",
1710
+ deepseek: "deepseekApiKey",
1711
+ kimi: "kimiApiKey",
1712
+ qwen: "qwenApiKey",
1713
+ zhipu: "zhipuApiKey",
1714
+ doubao: "doubaoApiKey",
1715
+ baichuan: "baichuanApiKey",
1716
+ lingyiwanwu: "lingyiwanwuApiKey",
1717
+ gemini: "geminiApiKey",
1718
+ mistral: "mistralApiKey",
1719
+ groq: "groqApiKey"
1720
+ };
1721
+ var PROVIDER_NAMES = {
1722
+ openai: "OpenAI",
1723
+ anthropic: "Anthropic Claude",
1724
+ deepseek: "DeepSeek \u6DF1\u5EA6\u6C42\u7D22",
1725
+ kimi: "Kimi \u6708\u4E4B\u6697\u9762",
1726
+ qwen: "\u901A\u4E49\u5343\u95EE Qwen",
1727
+ zhipu: "\u667A\u8C31 GLM",
1728
+ doubao: "\u8C46\u5305 Doubao",
1729
+ baichuan: "\u767E\u5DDD Baichuan",
1730
+ lingyiwanwu: "\u96F6\u4E00\u4E07\u7269 Yi",
1731
+ gemini: "Google Gemini",
1732
+ mistral: "Mistral",
1733
+ groq: "Groq"
1734
+ };
1639
1735
  var defaults = {
1640
1736
  locale: "zh",
1641
1737
  model: "kimi-k2.5",
1642
1738
  apiBaseUrl: "http://localhost:3210"
1643
1739
  };
1644
1740
  var config = new Conf({
1645
- projectName: "devpilot",
1741
+ projectName: "sxai",
1646
1742
  defaults
1647
1743
  });
1648
1744
  function getConfig() {
@@ -1655,11 +1751,29 @@ function getConfig() {
1655
1751
  openaiApiKey: config.get("openaiApiKey"),
1656
1752
  anthropicApiKey: config.get("anthropicApiKey"),
1657
1753
  deepseekApiKey: config.get("deepseekApiKey"),
1658
- kimiApiKey: config.get("kimiApiKey")
1754
+ kimiApiKey: config.get("kimiApiKey"),
1755
+ qwenApiKey: config.get("qwenApiKey"),
1756
+ zhipuApiKey: config.get("zhipuApiKey"),
1757
+ doubaoApiKey: config.get("doubaoApiKey"),
1758
+ baichuanApiKey: config.get("baichuanApiKey"),
1759
+ lingyiwanwuApiKey: config.get("lingyiwanwuApiKey"),
1760
+ geminiApiKey: config.get("geminiApiKey"),
1761
+ mistralApiKey: config.get("mistralApiKey"),
1762
+ groqApiKey: config.get("groqApiKey")
1659
1763
  };
1660
1764
  }
1661
1765
  function setConfig(key, value) {
1662
- config.set(key, value);
1766
+ if (value === void 0) {
1767
+ config.delete(key);
1768
+ } else {
1769
+ config.set(key, value);
1770
+ }
1771
+ }
1772
+ function getProviderApiKey(provider) {
1773
+ const cfg = getConfig();
1774
+ const keyName = PROVIDER_KEY_MAP[provider];
1775
+ if (!keyName) return void 0;
1776
+ return cfg[keyName];
1663
1777
  }
1664
1778
  function getConfigPath() {
1665
1779
  return config.path;
@@ -1733,6 +1847,8 @@ var Agent = class {
1733
1847
  let reasoningContent = "";
1734
1848
  const spinner = ora({ text: t("thinking"), spinner: "dots" }).start();
1735
1849
  let spinnerStopped = false;
1850
+ let hasTextOutput = false;
1851
+ let toolSpinnerStarted = false;
1736
1852
  try {
1737
1853
  for await (const chunk of provider.chatStream(this.messages, this.tools, config3)) {
1738
1854
  if (!spinnerStopped && (chunk.type === "text" || chunk.type === "tool_call")) {
@@ -1741,12 +1857,26 @@ var Agent = class {
1741
1857
  if (chunk.type === "text") {
1742
1858
  process.stdout.write("\n");
1743
1859
  }
1860
+ if (chunk.type === "tool_call" && !hasTextOutput) {
1861
+ spinner.text = "\u6B63\u5728\u51C6\u5907\u64CD\u4F5C...";
1862
+ spinner.start();
1863
+ toolSpinnerStarted = true;
1864
+ }
1744
1865
  }
1745
1866
  switch (chunk.type) {
1746
1867
  case "text":
1747
1868
  responseText += chunk.content || "";
1869
+ hasTextOutput = true;
1748
1870
  streamChunk(chunk.content || "");
1749
1871
  break;
1872
+ case "tool_call_start":
1873
+ if (!toolSpinnerStarted) {
1874
+ if (hasTextOutput) process.stdout.write("\n");
1875
+ spinner.text = "\u6B63\u5728\u51C6\u5907\u64CD\u4F5C...";
1876
+ spinner.start();
1877
+ toolSpinnerStarted = true;
1878
+ }
1879
+ break;
1750
1880
  case "tool_call":
1751
1881
  if (chunk.toolCall) {
1752
1882
  toolCalls.push(chunk.toolCall);
@@ -1770,11 +1900,13 @@ var Agent = class {
1770
1900
  }
1771
1901
  } catch (err) {
1772
1902
  if (!spinnerStopped) spinner.stop();
1903
+ if (toolSpinnerStarted) spinner.stop();
1773
1904
  const msg = err instanceof Error ? err.message : String(err);
1774
1905
  printError(t("errors.apiError", { message: msg }));
1775
1906
  return "";
1776
1907
  }
1777
1908
  if (!spinnerStopped) spinner.stop();
1909
+ if (toolSpinnerStarted) spinner.stop();
1778
1910
  if (responseText) {
1779
1911
  process.stdout.write("\n");
1780
1912
  }
@@ -1816,43 +1948,29 @@ var Agent = class {
1816
1948
  return finalResponse;
1817
1949
  }
1818
1950
  /**
1819
- * Get provider configuration based on current settings
1951
+ * Get provider configuration based on current settings.
1952
+ * Priority: user's own API key (BYOK, direct) > server proxy (via authToken)
1820
1953
  */
1821
1954
  getProviderConfig() {
1822
1955
  const cfg = getConfig();
1823
1956
  const model = cfg.model;
1824
1957
  const providerName = getProviderName(model);
1825
- let apiKey = "";
1826
- if (cfg.authToken) {
1827
- apiKey = cfg.authToken;
1828
- } else {
1829
- switch (providerName) {
1830
- case "openai":
1831
- apiKey = cfg.openaiApiKey || "";
1832
- break;
1833
- case "anthropic":
1834
- apiKey = cfg.anthropicApiKey || "";
1835
- break;
1836
- case "deepseek":
1837
- apiKey = cfg.deepseekApiKey || "";
1838
- break;
1839
- case "kimi":
1840
- apiKey = cfg.kimiApiKey || "";
1841
- break;
1842
- case "qwen":
1843
- apiKey = cfg.openaiApiKey || "";
1844
- break;
1845
- default:
1846
- apiKey = cfg.openaiApiKey || "";
1847
- }
1958
+ const ownKey = getProviderApiKey(providerName);
1959
+ if (ownKey) {
1960
+ return {
1961
+ apiKey: ownKey,
1962
+ baseUrl: getDefaultBaseUrl(providerName),
1963
+ model
1964
+ };
1848
1965
  }
1849
- let baseUrl;
1850
1966
  if (cfg.authToken) {
1851
- baseUrl = `${cfg.apiBaseUrl}/api/ai/proxy`;
1852
- } else {
1853
- baseUrl = getDefaultBaseUrl(providerName);
1967
+ return {
1968
+ apiKey: cfg.authToken,
1969
+ baseUrl: `${cfg.apiBaseUrl}/api/ai/proxy`,
1970
+ model
1971
+ };
1854
1972
  }
1855
- return { apiKey, baseUrl, model };
1973
+ return { apiKey: "", baseUrl: getDefaultBaseUrl(providerName), model };
1856
1974
  }
1857
1975
  /**
1858
1976
  * Trim old messages to prevent context overflow
@@ -1887,31 +2005,44 @@ var Agent = class {
1887
2005
  };
1888
2006
 
1889
2007
  // src/commands/chat.ts
1890
- function question(rl, prompt) {
1891
- return new Promise((resolve, reject) => {
1892
- const onClose = () => reject(new Error("readline closed"));
1893
- rl.once("close", onClose);
1894
- rl.question(prompt, (answer) => {
1895
- rl.removeListener("close", onClose);
1896
- resolve(answer);
1897
- });
1898
- });
1899
- }
1900
2008
  async function chatCommand() {
1901
2009
  const config3 = getConfig();
1902
2010
  setLocale(config3.locale);
2011
+ process.on("unhandledRejection", (reason) => {
2012
+ const msg = reason instanceof Error ? reason.message : String(reason);
2013
+ if (msg.includes("abort") || msg.includes("cancel") || msg.includes("ABORT")) return;
2014
+ console.error(theme.error(`
2015
+ \u26A0 \u5F02\u6B65\u9519\u8BEF: ${msg}`));
2016
+ });
2017
+ process.on("uncaughtException", (err) => {
2018
+ console.error(theme.error(`
2019
+ \u26A0 \u672A\u6355\u83B7\u9519\u8BEF: ${err.message}`));
2020
+ });
1903
2021
  console.log(banner());
1904
- const currentModel = config3.model;
1905
- console.log(theme.dim(` \u6A21\u578B ${theme.brandBold(currentModel)} \xB7 ${t("exitHint")}`));
1906
- const hasAnyKey = config3.authToken || config3.kimiApiKey || config3.openaiApiKey || config3.anthropicApiKey || config3.deepseekApiKey;
1907
- if (!hasAnyKey) {
2022
+ console.log(theme.dim(` ${t("exitHint")}`));
2023
+ const providerName = getProviderName(config3.model);
2024
+ const hasOwnKey = !!getProviderApiKey(providerName);
2025
+ if (hasOwnKey) {
2026
+ } else if (!config3.authToken || !config3.userEmail) {
2027
+ console.log();
2028
+ printInfo("\u9996\u6B21\u4F7F\u7528\uFF0C\u8BF7\u6CE8\u518C\u8D26\u6237\uFF08\u914D\u7F6E\u81EA\u5DF1\u7684API Key\u53EF\u8DF3\u8FC7\u6CE8\u518C\uFF0C\u672C\u5730\u4F7F\u7528\uFF09");
2029
+ console.log(theme.dim(" \u63D0\u793A: sxai set-key kimi <your-key> \u5373\u53EF\u514D\u767B\u5F55\u4F7F\u7528"));
2030
+ console.log();
2031
+ const loggedIn = await doInlineRegister();
2032
+ if (!loggedIn) {
2033
+ return;
2034
+ }
1908
2035
  console.log();
1909
- printWarning("\u5C1A\u672A\u914D\u7F6EAPI\u5BC6\u94A5\u3002\u8BF7\u8FD0\u884C\u4EE5\u4E0B\u547D\u4EE4\u4E4B\u4E00:");
1910
- console.log(theme.dim(" sxai set-key kimi <your-key> # Kimi (\u6708\u4E4B\u6697\u9762)"));
1911
- console.log(theme.dim(" sxai set-key deepseek <your-key>"));
1912
- console.log(theme.dim(" sxai set-key openai <your-key>"));
1913
- console.log(theme.dim(" sxai config # \u4EA4\u4E92\u5F0F\u914D\u7F6E"));
1914
2036
  }
2037
+ registerQuestionFn(async (prompt) => {
2038
+ const { answer } = await inquirer.prompt([{
2039
+ type: "input",
2040
+ name: "answer",
2041
+ message: prompt,
2042
+ prefix: ""
2043
+ }]);
2044
+ return answer;
2045
+ });
1915
2046
  const agent = new Agent();
1916
2047
  try {
1917
2048
  await agent.init();
@@ -1929,36 +2060,20 @@ async function chatCommand() {
1929
2060
  printError(`\u521D\u59CB\u5316\u5931\u8D25: ${err instanceof Error ? err.message : err}`);
1930
2061
  process.exit(1);
1931
2062
  }
1932
- const rl = readline.createInterface({
1933
- input: process.stdin,
1934
- output: process.stdout,
1935
- terminal: true
1936
- });
1937
- registerQuestionFn((prompt2) => question(rl, prompt2));
1938
- let ctrlCCount = 0;
1939
- process.on("SIGINT", () => {
1940
- ctrlCCount++;
1941
- if (ctrlCCount >= 2) {
1942
- console.log();
1943
- printSuccess(t("exit"));
1944
- process.exit(0);
1945
- }
1946
- console.log(theme.dim("\n(\u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA)"));
1947
- setTimeout(() => {
1948
- ctrlCCount = 0;
1949
- }, 2e3);
1950
- });
1951
- const prompt = theme.user(t("prompt"));
1952
2063
  while (true) {
1953
2064
  let input;
1954
2065
  try {
1955
- input = await question(rl, prompt);
2066
+ const result = await inquirer.prompt([{
2067
+ type: "input",
2068
+ name: "input",
2069
+ message: theme.user("\u4F60:"),
2070
+ prefix: ""
2071
+ }]);
2072
+ input = result.input.trim();
1956
2073
  } catch {
1957
2074
  break;
1958
2075
  }
1959
- input = input.trim();
1960
2076
  if (!input) continue;
1961
- ctrlCCount = 0;
1962
2077
  if (input.startsWith("/")) {
1963
2078
  const result = await handleSlashCommand(input, agent);
1964
2079
  if (result === "exit") break;
@@ -1977,11 +2092,75 @@ async function chatCommand() {
1977
2092
  }
1978
2093
  console.log();
1979
2094
  }
1980
- rl.close();
1981
2095
  console.log();
1982
2096
  printSuccess(t("exit"));
1983
2097
  process.exit(0);
1984
2098
  }
2099
+ async function doInlineRegister() {
2100
+ const config3 = getConfig();
2101
+ const { email } = await inquirer.prompt([{
2102
+ type: "input",
2103
+ name: "email",
2104
+ message: "\u90AE\u7BB1:",
2105
+ validate: (v) => v.includes("@") ? true : "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u90AE\u7BB1"
2106
+ }]);
2107
+ const { password } = await inquirer.prompt([{
2108
+ type: "password",
2109
+ name: "password",
2110
+ message: "\u8BBE\u7F6E\u5BC6\u7801 (\u81F3\u5C116\u4F4D):",
2111
+ mask: "*",
2112
+ validate: (v) => v.length >= 6 ? true : "\u5BC6\u7801\u81F3\u5C116\u4F4D"
2113
+ }]);
2114
+ const { name } = await inquirer.prompt([{
2115
+ type: "input",
2116
+ name: "name",
2117
+ message: "\u6635\u79F0:",
2118
+ default: email.split("@")[0]
2119
+ }]);
2120
+ try {
2121
+ const regResp = await fetch(`${config3.apiBaseUrl}/api/auth/register`, {
2122
+ method: "POST",
2123
+ headers: { "Content-Type": "application/json" },
2124
+ body: JSON.stringify({ email, password, name })
2125
+ });
2126
+ if (regResp.ok) {
2127
+ const data = await regResp.json();
2128
+ setConfig("authToken", data.token);
2129
+ setConfig("userEmail", data.user.email);
2130
+ console.log();
2131
+ printSuccess(`\u6CE8\u518C\u6210\u529F\uFF01\u6B22\u8FCE ${data.user.name}`);
2132
+ console.log(theme.dim(` \u8BA1\u5212: ${data.user.plan} \xB7 \u6BCF\u65E5 ${data.user.dailyLimit} \u6B21\u8BF7\u6C42`));
2133
+ return true;
2134
+ }
2135
+ const regErr = await regResp.json();
2136
+ if (regErr.error.includes("\u5DF2\u6CE8\u518C")) {
2137
+ printInfo("\u8BE5\u90AE\u7BB1\u5DF2\u6CE8\u518C\uFF0C\u6B63\u5728\u5C1D\u8BD5\u767B\u5F55...");
2138
+ const loginResp = await fetch(`${config3.apiBaseUrl}/api/auth/login`, {
2139
+ method: "POST",
2140
+ headers: { "Content-Type": "application/json" },
2141
+ body: JSON.stringify({ email, password })
2142
+ });
2143
+ if (loginResp.ok) {
2144
+ const data = await loginResp.json();
2145
+ setConfig("authToken", data.token);
2146
+ setConfig("userEmail", data.user.email);
2147
+ const roleTip = data.user.role === "admin" ? theme.error(" [\u7BA1\u7406\u5458]") : "";
2148
+ printSuccess(`\u767B\u5F55\u6210\u529F\uFF01\u6B22\u8FCE\u56DE\u6765 ${data.user.name || data.user.email}${roleTip}`);
2149
+ return true;
2150
+ } else {
2151
+ printError("\u5BC6\u7801\u9519\u8BEF\u3002\u8BF7\u91CD\u65B0\u8FD0\u884C sxai \u518D\u8BD5\u3002");
2152
+ return false;
2153
+ }
2154
+ }
2155
+ printError(`\u6CE8\u518C\u5931\u8D25: ${regErr.error}`);
2156
+ return false;
2157
+ } catch {
2158
+ printError("\u7F51\u7EDC\u8FDE\u63A5\u5931\u8D25\u3002\u8BF7\u786E\u8BA4\u540E\u7AEF\u670D\u52A1\u6B63\u5728\u8FD0\u884C\u3002");
2159
+ console.log(theme.dim(` \u540E\u7AEF\u5730\u5740: ${config3.apiBaseUrl}`));
2160
+ console.log(theme.dim(" \u542F\u52A8\u540E\u7AEF: cd packages/server && pnpm dev"));
2161
+ return false;
2162
+ }
2163
+ }
1985
2164
  async function handleSlashCommand(input, agent) {
1986
2165
  const [cmd, ...args] = input.split(" ");
1987
2166
  switch (cmd) {
@@ -1991,30 +2170,43 @@ async function handleSlashCommand(input, agent) {
1991
2170
  return "exit";
1992
2171
  case "/help":
1993
2172
  case "/h":
1994
- console.log(t("help"));
2173
+ console.log(`
2174
+ \u53EF\u7528\u547D\u4EE4:
2175
+ /help \u663E\u793A\u5E2E\u52A9
2176
+ /model \u67E5\u770B/\u5207\u6362\u6A21\u578B
2177
+ /clear \u6E05\u9664\u5BF9\u8BDD\u5386\u53F2
2178
+ /account \u67E5\u770B\u8D26\u6237\u4FE1\u606F
2179
+ /status \u67E5\u770B\u9879\u76EE\u4FE1\u606F
2180
+ /config \u67E5\u770B\u914D\u7F6E
2181
+ /logout \u9000\u51FA\u767B\u5F55
2182
+ /exit \u9000\u51FA\u7A0B\u5E8F
2183
+ `);
1995
2184
  break;
1996
2185
  case "/clear":
1997
2186
  agent.clearHistory();
1998
2187
  printSuccess("\u5BF9\u8BDD\u5386\u53F2\u5DF2\u6E05\u9664\u3002");
1999
2188
  break;
2000
2189
  case "/model": {
2001
- const models = listModels();
2002
- const currentModel = agent.getCurrentModel();
2003
- console.log("\n\u53EF\u7528\u6A21\u578B:");
2004
- for (const m of models) {
2005
- const marker = m.id === currentModel ? theme.success(" \u25C9") : " \u25CB";
2006
- console.log(`${marker} ${theme.bold(m.name)} ${theme.dim(`(${m.id})`)}`);
2007
- }
2008
2190
  if (args[0]) {
2009
- const target = args[0];
2010
- if (models.find((m) => m.id === target)) {
2011
- setConfig("model", target);
2012
- printSuccess(`\u6A21\u578B\u5DF2\u5207\u6362\u4E3A: ${target}`);
2013
- } else {
2014
- printError(`\u672A\u77E5\u6A21\u578B: ${target}`);
2015
- }
2191
+ setConfig("model", args[0]);
2192
+ printSuccess(`\u6A21\u578B\u5DF2\u5207\u6362\u4E3A: ${args[0]}`);
2016
2193
  } else {
2017
- console.log(theme.dim("\n\u4F7F\u7528 /model <model-id> \u5207\u6362\u6A21\u578B"));
2194
+ const models = listModels();
2195
+ const currentModel = agent.getCurrentModel();
2196
+ const groups = /* @__PURE__ */ new Map();
2197
+ for (const m of models) {
2198
+ if (!groups.has(m.group)) groups.set(m.group, []);
2199
+ groups.get(m.group).push(m);
2200
+ }
2201
+ for (const [group, groupModels] of groups) {
2202
+ console.log(theme.dim(`
2203
+ \u2500\u2500 ${group} \u2500\u2500`));
2204
+ for (const m of groupModels) {
2205
+ const marker = m.id === currentModel ? theme.success(" \u25C9") : " \u25CB";
2206
+ console.log(` ${marker} ${m.name} ${theme.dim(`(${m.id})`)}`);
2207
+ }
2208
+ }
2209
+ console.log(theme.dim("\n \u4F7F\u7528 /model <model-id> \u5207\u6362\u6A21\u578B"));
2018
2210
  }
2019
2211
  break;
2020
2212
  }
@@ -2031,126 +2223,139 @@ async function handleSlashCommand(input, agent) {
2031
2223
  }
2032
2224
  break;
2033
2225
  }
2226
+ case "/account": {
2227
+ const cfg = getConfig();
2228
+ if (cfg.authToken) {
2229
+ try {
2230
+ const resp = await fetch(`${cfg.apiBaseUrl}/api/auth/me`, {
2231
+ headers: { "Authorization": `Bearer ${cfg.authToken}` }
2232
+ });
2233
+ if (resp.ok) {
2234
+ const data = await resp.json();
2235
+ console.log(`
2236
+ \u90AE\u7BB1: ${data.user.email}`);
2237
+ console.log(` \u8BA1\u5212: ${data.user.plan}`);
2238
+ if (data.quota) {
2239
+ console.log(` \u4ECA\u65E5: ${data.quota.daily.used}/${data.quota.daily.limit}\u6B21`);
2240
+ console.log(` \u603B\u989D: ${fmtTokens(data.quota.lifetimeTokens.used)}/${fmtTokens(data.quota.lifetimeTokens.limit)} tokens`);
2241
+ }
2242
+ }
2243
+ } catch {
2244
+ }
2245
+ } else {
2246
+ printWarning("\u672A\u767B\u5F55");
2247
+ }
2248
+ break;
2249
+ }
2034
2250
  case "/config": {
2035
- const config3 = getConfig();
2251
+ const cfg = getConfig();
2036
2252
  console.log("\n\u5F53\u524D\u914D\u7F6E:");
2037
- console.log(` \u6A21\u578B: ${config3.model}`);
2038
- console.log(` \u8BED\u8A00: ${config3.locale}`);
2039
- console.log(` \u540E\u7AEF: ${config3.apiBaseUrl}`);
2040
- console.log(` \u767B\u5F55: ${config3.userEmail || "\u672A\u767B\u5F55"}`);
2041
- console.log(` Kimi Key: ${config3.kimiApiKey ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`);
2042
- console.log(` OpenAI Key: ${config3.openaiApiKey ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`);
2043
- console.log(` Anthropic Key: ${config3.anthropicApiKey ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`);
2044
- console.log(` DeepSeek Key: ${config3.deepseekApiKey ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`);
2253
+ console.log(` \u6A21\u578B: ${cfg.model}`);
2254
+ console.log(` \u8BED\u8A00: ${cfg.locale}`);
2255
+ console.log(` \u540E\u7AEF: ${cfg.apiBaseUrl}`);
2256
+ console.log(` \u767B\u5F55: ${cfg.userEmail || "\u672A\u767B\u5F55"}`);
2045
2257
  break;
2046
2258
  }
2047
- case "/login":
2048
- printInfo("\u8BF7\u4F7F\u7528 sxai login \u547D\u4EE4\u767B\u5F55\u3002");
2049
- break;
2050
2259
  case "/logout":
2051
2260
  setConfig("authToken", void 0);
2052
2261
  setConfig("userEmail", void 0);
2053
- printSuccess(t("auth.logoutSuccess"));
2054
- break;
2262
+ printSuccess("\u5DF2\u9000\u51FA\u767B\u5F55\u3002");
2263
+ return "exit";
2055
2264
  default:
2056
- printError(t("errors.unknownCommand", { command: cmd }));
2265
+ printError(`\u672A\u77E5\u547D\u4EE4: ${cmd}\u3002\u8F93\u5165 /help \u67E5\u770B\u5E2E\u52A9\u3002`);
2057
2266
  }
2058
2267
  }
2268
+ function fmtTokens(n) {
2269
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
2270
+ if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
2271
+ return String(n);
2272
+ }
2059
2273
 
2060
2274
  // src/commands/config.ts
2061
- import inquirer from "inquirer";
2275
+ import inquirer2 from "inquirer";
2062
2276
  async function configCommand() {
2063
2277
  const config3 = getConfig();
2064
2278
  console.log(`
2065
- \u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84: ${getConfigPath()}
2279
+ \u914D\u7F6E\u6587\u4EF6: ${theme.dim(getConfigPath())}
2066
2280
  `);
2067
- const { action } = await inquirer.prompt([{
2281
+ const { action } = await inquirer2.prompt([{
2068
2282
  type: "list",
2069
2283
  name: "action",
2070
2284
  message: "\u9009\u62E9\u8981\u914D\u7F6E\u7684\u9879\u76EE:",
2071
2285
  choices: [
2072
- { name: "\u8BBE\u7F6EAI\u6A21\u578B", value: "model" },
2073
- { name: "\u8BBE\u7F6EKimi API Key (\u6708\u4E4B\u6697\u9762)", value: "kimi" },
2074
- { name: "\u8BBE\u7F6EOpenAI API Key", value: "openai" },
2075
- { name: "\u8BBE\u7F6EAnthropic API Key", value: "anthropic" },
2076
- { name: "\u8BBE\u7F6EDeepSeek API Key", value: "deepseek" },
2077
- { name: "\u8BBE\u7F6E\u540E\u7AEF\u670D\u52A1\u5730\u5740", value: "apiBaseUrl" },
2078
- { name: "\u8BBE\u7F6E\u754C\u9762\u8BED\u8A00", value: "locale" },
2079
- { name: "\u67E5\u770B\u5F53\u524D\u914D\u7F6E", value: "show" },
2286
+ { name: "\u{1F916} \u8BBE\u7F6EAI\u6A21\u578B", value: "model" },
2287
+ new inquirer2.Separator("\u2500\u2500 API Key \u8BBE\u7F6E \u2500\u2500"),
2288
+ { name: "\u{1F319} Kimi \u6708\u4E4B\u6697\u9762", value: "key_kimi" },
2289
+ { name: "\u{1F9E0} OpenAI", value: "key_openai" },
2290
+ { name: "\u{1F3AD} Anthropic Claude", value: "key_anthropic" },
2291
+ { name: "\u{1F50D} DeepSeek \u6DF1\u5EA6\u6C42\u7D22", value: "key_deepseek" },
2292
+ { name: "\u2601\uFE0F \u901A\u4E49\u5343\u95EE Qwen", value: "key_qwen" },
2293
+ { name: "\u{1F52E} \u667A\u8C31 GLM", value: "key_zhipu" },
2294
+ { name: "\u{1FAD8} \u8C46\u5305 Doubao", value: "key_doubao" },
2295
+ { name: "\u{1F48E} Google Gemini", value: "key_gemini" },
2296
+ { name: "\u{1F32C}\uFE0F Mistral", value: "key_mistral" },
2297
+ { name: "\u26A1 Groq", value: "key_groq" },
2298
+ { name: "\u{1F3D4}\uFE0F \u767E\u5DDD Baichuan", value: "key_baichuan" },
2299
+ { name: "\u{1F31F} \u96F6\u4E00\u4E07\u7269 Yi", value: "key_lingyiwanwu" },
2300
+ new inquirer2.Separator("\u2500\u2500 \u5176\u4ED6\u8BBE\u7F6E \u2500\u2500"),
2301
+ { name: "\u{1F310} \u8BBE\u7F6E\u540E\u7AEF\u670D\u52A1\u5730\u5740", value: "apiBaseUrl" },
2302
+ { name: "\u{1F30D} \u8BBE\u7F6E\u754C\u9762\u8BED\u8A00", value: "locale" },
2303
+ { name: "\u{1F4CB} \u67E5\u770B\u5F53\u524D\u914D\u7F6E", value: "show" },
2080
2304
  { name: "\u9000\u51FA", value: "exit" }
2081
2305
  ]
2082
2306
  }]);
2307
+ if (action.startsWith("key_")) {
2308
+ const provider = action.replace("key_", "");
2309
+ const displayName = PROVIDER_NAMES[provider] || provider;
2310
+ const configKey = PROVIDER_KEY_MAP[provider];
2311
+ const currentKey = configKey ? config3[configKey] : void 0;
2312
+ if (currentKey) {
2313
+ printInfo(`\u5F53\u524D\u5DF2\u914D\u7F6E ${displayName} Key`);
2314
+ }
2315
+ const { key } = await inquirer2.prompt([{
2316
+ type: "password",
2317
+ name: "key",
2318
+ message: `\u8F93\u5165 ${displayName} API Key:`,
2319
+ mask: "*"
2320
+ }]);
2321
+ if (key && configKey) {
2322
+ setConfig(configKey, key);
2323
+ printSuccess(`${displayName} API Key \u5DF2\u4FDD\u5B58\u3002\u4F7F\u7528\u8BE5\u6A21\u578B\u4E0D\u6D88\u8017\u5E73\u53F0\u989D\u5EA6\u3002`);
2324
+ }
2325
+ return;
2326
+ }
2083
2327
  switch (action) {
2084
2328
  case "model": {
2085
2329
  const models = listModels();
2086
- const { model } = await inquirer.prompt([{
2330
+ const groups = /* @__PURE__ */ new Map();
2331
+ for (const m of models) {
2332
+ if (!groups.has(m.group)) groups.set(m.group, []);
2333
+ groups.get(m.group).push(m);
2334
+ }
2335
+ const choices = [];
2336
+ for (const [group, groupModels] of groups) {
2337
+ choices.push(new inquirer2.Separator(`\u2500\u2500 ${group} \u2500\u2500`));
2338
+ for (const m of groupModels) {
2339
+ choices.push({
2340
+ name: `${m.name} (${m.id})`,
2341
+ value: m.id
2342
+ });
2343
+ }
2344
+ }
2345
+ const { model } = await inquirer2.prompt([{
2087
2346
  type: "list",
2088
2347
  name: "model",
2089
2348
  message: "\u9009\u62E9AI\u6A21\u578B:",
2090
- choices: models.map((m) => ({
2091
- name: `${m.name} (${m.provider})`,
2092
- value: m.id
2093
- })),
2094
- default: config3.model
2349
+ choices,
2350
+ default: config3.model,
2351
+ pageSize: 20
2095
2352
  }]);
2096
2353
  setConfig("model", model);
2097
2354
  printSuccess(`\u6A21\u578B\u5DF2\u8BBE\u7F6E\u4E3A: ${model}`);
2098
2355
  break;
2099
2356
  }
2100
- case "kimi": {
2101
- const { key } = await inquirer.prompt([{
2102
- type: "password",
2103
- name: "key",
2104
- message: "\u8F93\u5165Kimi API Key (\u6708\u4E4B\u6697\u9762):",
2105
- mask: "*"
2106
- }]);
2107
- if (key) {
2108
- setConfig("kimiApiKey", key);
2109
- printSuccess("Kimi API Key \u5DF2\u4FDD\u5B58\u3002");
2110
- }
2111
- break;
2112
- }
2113
- case "openai": {
2114
- const { key } = await inquirer.prompt([{
2115
- type: "password",
2116
- name: "key",
2117
- message: "\u8F93\u5165OpenAI API Key:",
2118
- mask: "*"
2119
- }]);
2120
- if (key) {
2121
- setConfig("openaiApiKey", key);
2122
- printSuccess("OpenAI API Key \u5DF2\u4FDD\u5B58\u3002");
2123
- }
2124
- break;
2125
- }
2126
- case "anthropic": {
2127
- const { key } = await inquirer.prompt([{
2128
- type: "password",
2129
- name: "key",
2130
- message: "\u8F93\u5165Anthropic API Key:",
2131
- mask: "*"
2132
- }]);
2133
- if (key) {
2134
- setConfig("anthropicApiKey", key);
2135
- printSuccess("Anthropic API Key \u5DF2\u4FDD\u5B58\u3002");
2136
- }
2137
- break;
2138
- }
2139
- case "deepseek": {
2140
- const { key } = await inquirer.prompt([{
2141
- type: "password",
2142
- name: "key",
2143
- message: "\u8F93\u5165DeepSeek API Key:",
2144
- mask: "*"
2145
- }]);
2146
- if (key) {
2147
- setConfig("deepseekApiKey", key);
2148
- printSuccess("DeepSeek API Key \u5DF2\u4FDD\u5B58\u3002");
2149
- }
2150
- break;
2151
- }
2152
2357
  case "apiBaseUrl": {
2153
- const { url } = await inquirer.prompt([{
2358
+ const { url } = await inquirer2.prompt([{
2154
2359
  type: "input",
2155
2360
  name: "url",
2156
2361
  message: "\u8F93\u5165\u540E\u7AEF\u670D\u52A1\u5730\u5740:",
@@ -2161,7 +2366,7 @@ async function configCommand() {
2161
2366
  break;
2162
2367
  }
2163
2368
  case "locale": {
2164
- const { locale } = await inquirer.prompt([{
2369
+ const { locale } = await inquirer2.prompt([{
2165
2370
  type: "list",
2166
2371
  name: "locale",
2167
2372
  message: "\u9009\u62E9\u754C\u9762\u8BED\u8A00:",
@@ -2177,14 +2382,18 @@ async function configCommand() {
2177
2382
  }
2178
2383
  case "show": {
2179
2384
  console.log("\n\u5F53\u524D\u914D\u7F6E:");
2180
- console.log(` \u6A21\u578B: ${config3.model}`);
2385
+ console.log(` \u6A21\u578B: ${theme.brandBold(config3.model)}`);
2181
2386
  console.log(` \u8BED\u8A00: ${config3.locale}`);
2182
2387
  console.log(` \u540E\u7AEF: ${config3.apiBaseUrl}`);
2183
- console.log(` \u767B\u5F55: ${config3.userEmail || "\u672A\u767B\u5F55"}`);
2184
- console.log(` Kimi Key: ${config3.kimiApiKey ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`);
2185
- console.log(` OpenAI Key: ${config3.openaiApiKey ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`);
2186
- console.log(` Anthropic Key: ${config3.anthropicApiKey ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`);
2187
- console.log(` DeepSeek Key: ${config3.deepseekApiKey ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`);
2388
+ console.log(` \u767B\u5F55: ${config3.userEmail || theme.dim("\u672A\u767B\u5F55")}`);
2389
+ console.log();
2390
+ console.log(" API Key \u914D\u7F6E:");
2391
+ for (const [provider, keyName] of Object.entries(PROVIDER_KEY_MAP)) {
2392
+ const displayName = (PROVIDER_NAMES[provider] || provider).padEnd(20);
2393
+ const hasKey = !!config3[keyName];
2394
+ const status = hasKey ? theme.success("\u2713 \u5DF2\u914D\u7F6E") : theme.dim("\u672A\u914D\u7F6E");
2395
+ console.log(` ${displayName} ${status}`);
2396
+ }
2188
2397
  break;
2189
2398
  }
2190
2399
  case "exit":
@@ -2193,12 +2402,12 @@ async function configCommand() {
2193
2402
  }
2194
2403
 
2195
2404
  // src/commands/login.ts
2196
- import inquirer2 from "inquirer";
2405
+ import inquirer3 from "inquirer";
2197
2406
  async function loginCommand() {
2198
2407
  const config3 = getConfig();
2199
2408
  if (config3.authToken && config3.userEmail) {
2200
2409
  printInfo(`\u5F53\u524D\u5DF2\u767B\u5F55: ${config3.userEmail}`);
2201
- const { relogin } = await inquirer2.prompt([{
2410
+ const { relogin } = await inquirer3.prompt([{
2202
2411
  type: "confirm",
2203
2412
  name: "relogin",
2204
2413
  message: "\u662F\u5426\u91CD\u65B0\u767B\u5F55\uFF1F",
@@ -2207,7 +2416,7 @@ async function loginCommand() {
2207
2416
  if (!relogin) return;
2208
2417
  }
2209
2418
  console.log(theme.brandBold("\n\u{1F511} \u767B\u5F55\u6C88\u7FD4\u7684AI\u52A9\u624B\n"));
2210
- const { email } = await inquirer2.prompt([{
2419
+ const { email } = await inquirer3.prompt([{
2211
2420
  type: "input",
2212
2421
  name: "email",
2213
2422
  message: "\u90AE\u7BB1:",
@@ -2216,7 +2425,7 @@ async function loginCommand() {
2216
2425
  return true;
2217
2426
  }
2218
2427
  }]);
2219
- const { password } = await inquirer2.prompt([{
2428
+ const { password } = await inquirer3.prompt([{
2220
2429
  type: "password",
2221
2430
  name: "password",
2222
2431
  message: "\u5BC6\u7801:",
@@ -2242,7 +2451,7 @@ async function loginCommand() {
2242
2451
  console.log();
2243
2452
  } else if (response.status === 404) {
2244
2453
  printInfo("\u8BE5\u90AE\u7BB1\u5C1A\u672A\u6CE8\u518C");
2245
- const { register } = await inquirer2.prompt([{
2454
+ const { register } = await inquirer3.prompt([{
2246
2455
  type: "confirm",
2247
2456
  name: "register",
2248
2457
  message: "\u662F\u5426\u7ACB\u5373\u6CE8\u518C\uFF1F",
@@ -2276,12 +2485,12 @@ async function loginCommand() {
2276
2485
  }
2277
2486
 
2278
2487
  // src/commands/register.ts
2279
- import inquirer3 from "inquirer";
2488
+ import inquirer4 from "inquirer";
2280
2489
  async function registerCommand() {
2281
2490
  const config3 = getConfig();
2282
2491
  if (config3.authToken && config3.userEmail) {
2283
2492
  printInfo(`\u5F53\u524D\u5DF2\u767B\u5F55: ${config3.userEmail}`);
2284
- const { proceed } = await inquirer3.prompt([{
2493
+ const { proceed } = await inquirer4.prompt([{
2285
2494
  type: "confirm",
2286
2495
  name: "proceed",
2287
2496
  message: "\u662F\u5426\u6CE8\u518C\u65B0\u8D26\u6237\uFF1F\uFF08\u5C06\u8986\u76D6\u5F53\u524D\u767B\u5F55\u72B6\u6001\uFF09",
@@ -2290,7 +2499,7 @@ async function registerCommand() {
2290
2499
  if (!proceed) return;
2291
2500
  }
2292
2501
  console.log(theme.brandBold("\n\u{1F4DD} \u6CE8\u518C\u6C88\u7FD4\u7684AI\u52A9\u624B\u8D26\u6237\n"));
2293
- const { email } = await inquirer3.prompt([{
2502
+ const { email } = await inquirer4.prompt([{
2294
2503
  type: "input",
2295
2504
  name: "email",
2296
2505
  message: "\u90AE\u7BB1:",
@@ -2299,13 +2508,13 @@ async function registerCommand() {
2299
2508
  return true;
2300
2509
  }
2301
2510
  }]);
2302
- const { name } = await inquirer3.prompt([{
2511
+ const { name } = await inquirer4.prompt([{
2303
2512
  type: "input",
2304
2513
  name: "name",
2305
2514
  message: "\u6635\u79F0 (\u53EF\u9009):",
2306
2515
  default: email.split("@")[0]
2307
2516
  }]);
2308
- const { password } = await inquirer3.prompt([{
2517
+ const { password } = await inquirer4.prompt([{
2309
2518
  type: "password",
2310
2519
  name: "password",
2311
2520
  message: "\u5BC6\u7801 (\u81F3\u5C116\u4F4D):",
@@ -2315,7 +2524,7 @@ async function registerCommand() {
2315
2524
  return true;
2316
2525
  }
2317
2526
  }]);
2318
- const { confirmPassword } = await inquirer3.prompt([{
2527
+ const { confirmPassword } = await inquirer4.prompt([{
2319
2528
  type: "password",
2320
2529
  name: "confirmPassword",
2321
2530
  message: "\u786E\u8BA4\u5BC6\u7801:",
@@ -2399,9 +2608,9 @@ async function accountCommand() {
2399
2608
  const dailyBar = progressBar(quota.daily.used, quota.daily.limit);
2400
2609
  console.log(` \u4ECA\u65E5\u8BF7\u6C42 ${dailyBar} ${quota.daily.used}/${quota.daily.limit}`);
2401
2610
  const monthBar = progressBar(quota.monthlyTokens.used, quota.monthlyTokens.limit);
2402
- console.log(` \u672C\u6708Token ${monthBar} ${fmtTokens(quota.monthlyTokens.used)}/${fmtTokens(quota.monthlyTokens.limit)}`);
2611
+ console.log(` \u672C\u6708Token ${monthBar} ${fmtTokens2(quota.monthlyTokens.used)}/${fmtTokens2(quota.monthlyTokens.limit)}`);
2403
2612
  const lifeBar = progressBar(quota.lifetimeTokens.used, quota.lifetimeTokens.limit);
2404
- console.log(` \u514D\u8D39\u989D\u5EA6 ${lifeBar} ${fmtTokens(quota.lifetimeTokens.used)}/${fmtTokens(quota.lifetimeTokens.limit)}`);
2613
+ console.log(` \u514D\u8D39\u989D\u5EA6 ${lifeBar} ${fmtTokens2(quota.lifetimeTokens.used)}/${fmtTokens2(quota.lifetimeTokens.limit)}`);
2405
2614
  if (!quota.allowed) {
2406
2615
  console.log();
2407
2616
  printWarning(quota.reason || "\u989D\u5EA6\u5DF2\u7528\u5B8C");
@@ -2411,9 +2620,9 @@ async function accountCommand() {
2411
2620
  console.log();
2412
2621
  console.log(theme.brandBold(" \u{1F4C8} \u7528\u91CF\u7EDF\u8BA1"));
2413
2622
  console.log(theme.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2414
- console.log(` \u4ECA\u65E5 ${usage.today.requests}\u6B21\u8BF7\u6C42 \xB7 ${fmtTokens(usage.today.tokens)} tokens`);
2415
- console.log(` \u672C\u6708 ${usage.thisMonth.requests}\u6B21\u8BF7\u6C42 \xB7 ${fmtTokens(usage.thisMonth.tokens)} tokens`);
2416
- console.log(` \u7D2F\u8BA1 ${usage.lifetime.requests}\u6B21\u8BF7\u6C42 \xB7 ${fmtTokens(usage.lifetime.tokens)} tokens`);
2623
+ console.log(` \u4ECA\u65E5 ${usage.today.requests}\u6B21\u8BF7\u6C42 \xB7 ${fmtTokens2(usage.today.tokens)} tokens`);
2624
+ console.log(` \u672C\u6708 ${usage.thisMonth.requests}\u6B21\u8BF7\u6C42 \xB7 ${fmtTokens2(usage.thisMonth.tokens)} tokens`);
2625
+ console.log(` \u7D2F\u8BA1 ${usage.lifetime.requests}\u6B21\u8BF7\u6C42 \xB7 ${fmtTokens2(usage.lifetime.tokens)} tokens`);
2417
2626
  }
2418
2627
  console.log();
2419
2628
  } catch (err) {
@@ -2433,7 +2642,7 @@ function planLabel(plan) {
2433
2642
  return plan;
2434
2643
  }
2435
2644
  }
2436
- function fmtTokens(n) {
2645
+ function fmtTokens2(n) {
2437
2646
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
2438
2647
  if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
2439
2648
  return String(n);
@@ -2448,7 +2657,7 @@ function progressBar(used, limit, width = 16) {
2448
2657
  }
2449
2658
 
2450
2659
  // src/commands/admin.ts
2451
- import inquirer4 from "inquirer";
2660
+ import inquirer5 from "inquirer";
2452
2661
  async function adminCommand() {
2453
2662
  const config3 = getConfig();
2454
2663
  if (!config3.authToken) {
@@ -2456,7 +2665,7 @@ async function adminCommand() {
2456
2665
  return;
2457
2666
  }
2458
2667
  console.log(theme.brandBold("\n\u2699\uFE0F \u7BA1\u7406\u5458\u63A7\u5236\u53F0\n"));
2459
- const { action } = await inquirer4.prompt([{
2668
+ const { action } = await inquirer5.prompt([{
2460
2669
  type: "list",
2461
2670
  name: "action",
2462
2671
  message: "\u9009\u62E9\u64CD\u4F5C:",
@@ -2532,7 +2741,7 @@ async function listUsers(config3) {
2532
2741
  const roleTag = u.role === "admin" ? theme.error("\u7BA1\u7406\u5458") : "\u7528\u6237 ";
2533
2742
  const planTag = u.plan === "free" ? theme.dim("\u514D\u8D39 ") : u.plan === "pro" ? theme.success("\u4E13\u4E1A ") : theme.info("\u4F01\u4E1A ");
2534
2743
  console.log(
2535
- ` ${(u.email + status).padEnd(28)} ${planTag} ${roleTag} ${String(u.dailyLimit).padEnd(6)} ${fmtTokens2(u.totalFreeTokens).padEnd(12)} ${fmtTokens2(u.totalTokensUsed)}`
2744
+ ` ${(u.email + status).padEnd(28)} ${planTag} ${roleTag} ${String(u.dailyLimit).padEnd(6)} ${fmtTokens3(u.totalFreeTokens).padEnd(12)} ${fmtTokens3(u.totalTokensUsed)}`
2536
2745
  );
2537
2746
  }
2538
2747
  console.log();
@@ -2543,7 +2752,7 @@ async function listUsers(config3) {
2543
2752
  async function setUserQuota(config3) {
2544
2753
  const users = await fetchUserList(config3);
2545
2754
  if (!users) return;
2546
- const { userId } = await inquirer4.prompt([{
2755
+ const { userId } = await inquirer5.prompt([{
2547
2756
  type: "list",
2548
2757
  name: "userId",
2549
2758
  message: "\u9009\u62E9\u7528\u6237:",
@@ -2551,21 +2760,21 @@ async function setUserQuota(config3) {
2551
2760
  }]);
2552
2761
  const selectedUser = users.find((u) => u.id === userId);
2553
2762
  console.log(theme.dim(`
2554
- \u5F53\u524D\u989D\u5EA6: \u65E5\u9650=${selectedUser.dailyLimit}\u6B21 \u6708Token=${fmtTokens2(selectedUser.monthlyTokenLimit)} \u514D\u8D39\u603B\u989D=${fmtTokens2(selectedUser.totalFreeTokens)}
2763
+ \u5F53\u524D\u989D\u5EA6: \u65E5\u9650=${selectedUser.dailyLimit}\u6B21 \u6708Token=${fmtTokens3(selectedUser.monthlyTokenLimit)} \u514D\u8D39\u603B\u989D=${fmtTokens3(selectedUser.totalFreeTokens)}
2555
2764
  `));
2556
- const { dailyLimit } = await inquirer4.prompt([{
2765
+ const { dailyLimit } = await inquirer5.prompt([{
2557
2766
  type: "number",
2558
2767
  name: "dailyLimit",
2559
2768
  message: "\u6BCF\u65E5\u8BF7\u6C42\u4E0A\u9650:",
2560
2769
  default: selectedUser.dailyLimit
2561
2770
  }]);
2562
- const { monthlyTokenLimit } = await inquirer4.prompt([{
2771
+ const { monthlyTokenLimit } = await inquirer5.prompt([{
2563
2772
  type: "number",
2564
2773
  name: "monthlyTokenLimit",
2565
2774
  message: "\u6BCF\u6708Token\u4E0A\u9650:",
2566
2775
  default: selectedUser.monthlyTokenLimit
2567
2776
  }]);
2568
- const { totalFreeTokens } = await inquirer4.prompt([{
2777
+ const { totalFreeTokens } = await inquirer5.prompt([{
2569
2778
  type: "number",
2570
2779
  name: "totalFreeTokens",
2571
2780
  message: "\u514D\u8D39Token\u603B\u989D:",
@@ -2591,13 +2800,13 @@ async function setUserQuota(config3) {
2591
2800
  async function setUserPlan(config3) {
2592
2801
  const users = await fetchUserList(config3);
2593
2802
  if (!users) return;
2594
- const { userId } = await inquirer4.prompt([{
2803
+ const { userId } = await inquirer5.prompt([{
2595
2804
  type: "list",
2596
2805
  name: "userId",
2597
2806
  message: "\u9009\u62E9\u7528\u6237:",
2598
2807
  choices: users.map((u) => ({ name: `${u.email} (\u5F53\u524D: ${u.plan})`, value: u.id }))
2599
2808
  }]);
2600
- const { plan } = await inquirer4.prompt([{
2809
+ const { plan } = await inquirer5.prompt([{
2601
2810
  type: "list",
2602
2811
  name: "plan",
2603
2812
  message: "\u9009\u62E9\u8BA1\u5212:",
@@ -2627,7 +2836,7 @@ async function setUserPlan(config3) {
2627
2836
  async function setUserRole(config3) {
2628
2837
  const users = await fetchUserList(config3);
2629
2838
  if (!users) return;
2630
- const { userId } = await inquirer4.prompt([{
2839
+ const { userId } = await inquirer5.prompt([{
2631
2840
  type: "list",
2632
2841
  name: "userId",
2633
2842
  message: "\u9009\u62E9\u7528\u6237:",
@@ -2636,7 +2845,7 @@ async function setUserRole(config3) {
2636
2845
  value: u.id
2637
2846
  }))
2638
2847
  }]);
2639
- const { role } = await inquirer4.prompt([{
2848
+ const { role } = await inquirer5.prompt([{
2640
2849
  type: "list",
2641
2850
  name: "role",
2642
2851
  message: "\u8BBE\u7F6E\u89D2\u8272:",
@@ -2665,16 +2874,16 @@ async function setUserRole(config3) {
2665
2874
  async function resetUsage(config3) {
2666
2875
  const users = await fetchUserList(config3);
2667
2876
  if (!users) return;
2668
- const { userId } = await inquirer4.prompt([{
2877
+ const { userId } = await inquirer5.prompt([{
2669
2878
  type: "list",
2670
2879
  name: "userId",
2671
2880
  message: "\u9009\u62E9\u7528\u6237:",
2672
2881
  choices: users.map((u) => ({
2673
- name: `${u.email} (\u5DF2\u7528 ${fmtTokens2(u.totalTokensUsed)} tokens)`,
2882
+ name: `${u.email} (\u5DF2\u7528 ${fmtTokens3(u.totalTokensUsed)} tokens)`,
2674
2883
  value: u.id
2675
2884
  }))
2676
2885
  }]);
2677
- const { confirm } = await inquirer4.prompt([{
2886
+ const { confirm } = await inquirer5.prompt([{
2678
2887
  type: "confirm",
2679
2888
  name: "confirm",
2680
2889
  message: "\u786E\u5B9A\u8981\u91CD\u7F6E\u8BE5\u7528\u6237\u7684\u7D2F\u8BA1\u7528\u91CF\u5417\uFF1F",
@@ -2699,7 +2908,7 @@ async function resetUsage(config3) {
2699
2908
  async function toggleUserStatus(config3) {
2700
2909
  const users = await fetchUserList(config3);
2701
2910
  if (!users) return;
2702
- const { userId } = await inquirer4.prompt([{
2911
+ const { userId } = await inquirer5.prompt([{
2703
2912
  type: "list",
2704
2913
  name: "userId",
2705
2914
  message: "\u9009\u62E9\u7528\u6237:",
@@ -2737,7 +2946,7 @@ async function showStats(config3) {
2737
2946
  console.log(` \u7528\u6237\u603B\u6570 ${data.users.total}`);
2738
2947
  console.log(` \u6D3B\u8DC3\u7528\u6237 ${data.users.active}`);
2739
2948
  console.log(` \u603B\u8BF7\u6C42\u6570 ${data.usage.totalRequests}`);
2740
- console.log(` \u603BToken\u7528\u91CF ${fmtTokens2(Number(data.usage.totalTokens))}`);
2949
+ console.log(` \u603BToken\u7528\u91CF ${fmtTokens3(Number(data.usage.totalTokens))}`);
2741
2950
  console.log();
2742
2951
  } catch {
2743
2952
  printError("\u83B7\u53D6\u7EDF\u8BA1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5\u3002");
@@ -2758,7 +2967,7 @@ async function fetchUserList(config3) {
2758
2967
  return null;
2759
2968
  }
2760
2969
  }
2761
- function fmtTokens2(n) {
2970
+ function fmtTokens3(n) {
2762
2971
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
2763
2972
  if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
2764
2973
  return String(n);
@@ -2795,19 +3004,20 @@ program.command("admin").description("\u7BA1\u7406\u5458\u63A7\u5236\u53F0\uFF08
2795
3004
  program.command("config").description("\u914D\u7F6E\u6C88\u7FD4\u7684AI\u52A9\u624B").action(async () => {
2796
3005
  await configCommand();
2797
3006
  });
2798
- program.command("set-key <provider> <key>").description("\u8BBE\u7F6EAPI\u5BC6\u94A5 (provider: openai, anthropic, deepseek, kimi)").action((provider, key) => {
2799
- const keyMap = {
2800
- openai: "openaiApiKey",
2801
- anthropic: "anthropicApiKey",
2802
- deepseek: "deepseekApiKey",
2803
- kimi: "kimiApiKey"
2804
- };
2805
- const configKey = keyMap[provider];
3007
+ var providerList = Object.keys(PROVIDER_KEY_MAP).join(", ");
3008
+ program.command("set-key <provider> <key>").description(`\u8BBE\u7F6EAPI\u5BC6\u94A5 (${providerList})`).action((provider, key) => {
3009
+ const configKey = PROVIDER_KEY_MAP[provider];
2806
3010
  if (!configKey) {
2807
- console.error(`\u672A\u77E5\u7684\u63D0\u4F9B\u5546: ${provider}\u3002\u652F\u6301: openai, anthropic, deepseek, kimi`);
3011
+ printError(`\u672A\u77E5\u7684\u63D0\u4F9B\u5546: ${provider}`);
3012
+ console.log(`\u652F\u6301\u7684\u63D0\u4F9B\u5546: ${providerList}`);
2808
3013
  process.exit(1);
2809
3014
  }
2810
3015
  setConfig(configKey, key);
2811
- printSuccess(`${provider} API Key \u5DF2\u4FDD\u5B58\u3002`);
3016
+ const displayName = PROVIDER_NAMES[provider] || provider;
3017
+ printSuccess(`${displayName} API Key \u5DF2\u4FDD\u5B58\u3002\u914D\u7F6E\u81EA\u5DF1\u7684Key\u540E\uFF0C\u4F7F\u7528\u8BE5\u6A21\u578B\u4E0D\u6D88\u8017\u5E73\u53F0\u989D\u5EA6\u3002`);
3018
+ });
3019
+ program.command("use <model>").description("\u5207\u6362AI\u6A21\u578B (\u5982: sxai use gpt-4o)").action((model) => {
3020
+ setConfig("model", model);
3021
+ printSuccess(`\u6A21\u578B\u5DF2\u5207\u6362\u4E3A: ${model}`);
2812
3022
  });
2813
3023
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shenxiang-ai-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "沈翔的AI助手 - 终端里的AI全栈开发搭档",
5
5
  "type": "module",
6
6
  "bin": {