@nick848/sf-cli 1.0.11 → 1.0.13

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/CHANGELOG.md CHANGED
@@ -5,6 +5,67 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## v1.0.12 (2026-03-22)
9
+
10
+ **交互优化 - API Key 配置引导**
11
+
12
+ - ✨ 启动时检查 API Key 配置状态,未配置时显示引导信息
13
+ - ✨ 自然语言处理功能实现 - 可直接与 AI 对话
14
+ - 🔧 简化权限逻辑:
15
+ - `/model` 等基础命令始终可用
16
+ - `/new` 需要先配置 API Key
17
+ - 自然语言输入始终可用(内部检查 API Key)
18
+
19
+ **引导流程**
20
+
21
+ ```
22
+ 启动 CLI
23
+
24
+ 检查 API Key
25
+ ↓ 未配置
26
+ ━━━━━━━━━━━━━━━━━━━
27
+ ⚠️ 未配置 API Key
28
+
29
+ 请先配置模型以启用 AI 功能:
30
+ /model - 交互式选择模型并配置 API Key
31
+ /model list - 查看可用模型列表
32
+
33
+ 支持: GLM-5, GPT-4o, Claude
34
+ ━━━━━━━━━━━━━━━━━━━
35
+ ↓ 配置完成
36
+ ✓ 已配置模型: GLM-5
37
+ 💡 使用 /new <需求描述> 启动新工作流
38
+ 或直接输入自然语言与 AI 交互
39
+ ```
40
+
41
+ ## v1.0.11 (2026-03-22)
42
+
43
+ **重大更新 - 规格确认后自动执行开发流程**
44
+
45
+ - ✨ 规格确认后自动执行:TDD 测试生成 → 开发实现 → 代码审核
46
+ - 🤖 `executeDevelopment()` - AI 自动生成代码实现
47
+ - 🔍 `executeReview()` - AI 自动进行代码审核
48
+ - 📦 自动解析代码块并写入文件
49
+ - 🎯 审核通过自动归档,审核失败可重新审核或回退
50
+
51
+ **新增函数**
52
+
53
+ - `executeDevelopment()` - 调用 AI 生成代码,自动写入文件
54
+ - `executeReview()` - 调用 AI 审核代码,返回问题和建议
55
+ - `buildDevelopmentPrompt()` - 构建开发提示词
56
+ - `parseCodeBlocks()` - 解析 AI 返回的代码块
57
+
58
+ **流程优化**
59
+
60
+ ```
61
+ 规格确认(y)
62
+ → 自动生成测试
63
+ → 自动生成代码
64
+ → 自动审核
65
+ → 通过:自动归档
66
+ → 失败:y 重新审核 / n 回退规格
67
+ ```
68
+
8
69
  ## v1.0.10 (2026-03-22)
9
70
 
10
71
  **重大重构 - 工作流系统重写**
package/dist/cli/index.js CHANGED
@@ -1971,18 +1971,67 @@ async function executeWorkflow(ctx) {
1971
1971
  lines.push("");
1972
1972
  lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 7/8: \u5F00\u53D1\u5B9E\u73B0 \u2501\u2501\u2501"));
1973
1973
  lines.push("");
1974
- lines.push(chalk9__default.default.yellow(" \u{1F4DD} \u5F00\u53D1\u9636\u6BB5"));
1975
- lines.push(chalk9__default.default.gray(" \u8BF7\u8C03\u7528 $frontend-dev \u6267\u884C\u5F00\u53D1\u4EFB\u52A1"));
1976
- lines.push(chalk9__default.default.gray(" \u6216\u624B\u52A8\u5B9E\u73B0\u4EE3\u7801\u540E\u8F93\u5165 continue \u7EE7\u7EED"));
1977
- return { output: lines.join("\n") };
1974
+ lines.push(chalk9__default.default.yellow(" \u{1F680} \u6B63\u5728\u8C03\u7528 AI \u751F\u6210\u4EE3\u7801..."));
1975
+ try {
1976
+ const developResult = await executeDevelopment(ctx, activeSession);
1977
+ if (developResult.success) {
1978
+ lines.push(chalk9__default.default.green(" \u2713 \u4EE3\u7801\u751F\u6210\u5B8C\u6210"));
1979
+ for (const file of developResult.files) {
1980
+ lines.push(chalk9__default.default.gray(` - ${file}`));
1981
+ }
1982
+ activeSession.implFiles = developResult.files;
1983
+ activeSession.phase = "review";
1984
+ } else {
1985
+ lines.push(chalk9__default.default.red(` \u2717 \u4EE3\u7801\u751F\u6210\u5931\u8D25: ${developResult.error}`));
1986
+ lines.push(chalk9__default.default.gray(" \u8BF7\u624B\u52A8\u5B9E\u73B0\u4EE3\u7801\u540E\u8F93\u5165 continue \u7EE7\u7EED"));
1987
+ return { output: lines.join("\n") };
1988
+ }
1989
+ } catch (error) {
1990
+ lines.push(chalk9__default.default.red(` \u2717 \u5F00\u53D1\u9636\u6BB5\u51FA\u9519: ${error.message}`));
1991
+ lines.push(chalk9__default.default.gray(" \u8BF7\u624B\u52A8\u5B9E\u73B0\u4EE3\u7801\u540E\u8F93\u5165 continue \u7EE7\u7EED"));
1992
+ return { output: lines.join("\n") };
1993
+ }
1978
1994
  }
1979
1995
  if (activeSession.phase === "review") {
1980
1996
  lines.push("");
1981
1997
  lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 8/8: \u4EE3\u7801\u5BA1\u6838 \u2501\u2501\u2501"));
1982
1998
  lines.push("");
1983
- lines.push(chalk9__default.default.yellow(" \u{1F50D} \u4EE3\u7801\u5BA1\u6838\u9636\u6BB5"));
1984
- lines.push(chalk9__default.default.gray(" \u8BF7\u8C03\u7528 $code-reviewer \u6267\u884C\u4EE3\u7801\u5BA1\u6838"));
1985
- lines.push(chalk9__default.default.gray(" \u6216\u8F93\u5165 review \u5B8C\u6210\u5BA1\u6838"));
1999
+ lines.push(chalk9__default.default.yellow(" \u{1F50D} \u6B63\u5728\u8FDB\u884C\u4EE3\u7801\u5BA1\u6838..."));
2000
+ try {
2001
+ const reviewResult = await executeReview(ctx, activeSession);
2002
+ if (reviewResult.passed) {
2003
+ lines.push(chalk9__default.default.green(" \u2713 \u4EE3\u7801\u5BA1\u6838\u901A\u8FC7"));
2004
+ if (reviewResult.suggestions.length > 0) {
2005
+ lines.push(chalk9__default.default.gray(" \u5EFA\u8BAE:"));
2006
+ for (const s of reviewResult.suggestions.slice(0, 3)) {
2007
+ lines.push(chalk9__default.default.gray(` - ${s}`));
2008
+ }
2009
+ }
2010
+ await archiveWorkflow(ctx.options.workingDirectory);
2011
+ const summary = activeSession.refinedRequirement;
2012
+ activeSession = null;
2013
+ lines.push("");
2014
+ lines.push(chalk9__default.default.green.bold("\u{1F389} \u5DE5\u4F5C\u6D41\u5DF2\u5B8C\u6210\uFF01"));
2015
+ lines.push(chalk9__default.default.gray(`\u9700\u6C42: ${summary.slice(0, 60)}${summary.length > 60 ? "..." : ""}`));
2016
+ lines.push("");
2017
+ lines.push(chalk9__default.default.cyan("\u4F7F\u7528 /new <\u9700\u6C42> \u5F00\u59CB\u65B0\u7684\u5DE5\u4F5C\u6D41"));
2018
+ return { output: lines.join("\n") };
2019
+ } else {
2020
+ lines.push(chalk9__default.default.red(" \u2717 \u4EE3\u7801\u5BA1\u6838\u672A\u901A\u8FC7"));
2021
+ lines.push(chalk9__default.default.gray(" \u95EE\u9898:"));
2022
+ for (const issue of reviewResult.issues.slice(0, 5)) {
2023
+ lines.push(chalk9__default.default.red(` - ${issue}`));
2024
+ }
2025
+ lines.push("");
2026
+ lines.push(chalk9__default.default.yellow(" \u8BF7\u4FEE\u590D\u95EE\u9898\u540E\u8F93\u5165 y \u91CD\u65B0\u5BA1\u6838"));
2027
+ lines.push(chalk9__default.default.gray(" \u6216\u8F93\u5165 n \u56DE\u9000\u5230\u89C4\u683C\u9636\u6BB5"));
2028
+ return { output: lines.join("\n") };
2029
+ }
2030
+ } catch (error) {
2031
+ lines.push(chalk9__default.default.red(` \u2717 \u5BA1\u6838\u9636\u6BB5\u51FA\u9519: ${error.message}`));
2032
+ lines.push(chalk9__default.default.gray(" \u8F93\u5165 pass \u5F3A\u5236\u901A\u8FC7\uFF0C\u6216 fix \u4FEE\u590D\u95EE\u9898"));
2033
+ return { output: lines.join("\n") };
2034
+ }
1986
2035
  }
1987
2036
  return { output: lines.join("\n") };
1988
2037
  } catch (error) {
@@ -2065,19 +2114,25 @@ async function handleWorkflowInput(input, ctx) {
2065
2114
  }
2066
2115
  }
2067
2116
  if (activeSession.phase === "review") {
2068
- if (trimmed === "review" || trimmed === "\u5BA1\u6838" || trimmed === "pass" || trimmed === "\u901A\u8FC7") {
2117
+ if (trimmed === "pass" || trimmed === "\u901A\u8FC7" || trimmed === "\u5F3A\u5236\u901A\u8FC7") {
2069
2118
  await archiveWorkflow(ctx.options.workingDirectory);
2070
2119
  const summary = activeSession.refinedRequirement;
2071
2120
  activeSession = null;
2072
2121
  return {
2073
2122
  output: chalk9__default.default.green("\u2713 \u5DE5\u4F5C\u6D41\u5DF2\u5B8C\u6210") + chalk9__default.default.gray(`
2074
- \u9700\u6C42: ${summary.slice(0, 60)}...`) + chalk9__default.default.cyan("\n\n\u4F7F\u7528 /new <\u9700\u6C42> \u5F00\u59CB\u65B0\u7684\u5DE5\u4F5C\u6D41")
2123
+ \u9700\u6C42: ${summary.slice(0, 60)}${summary.length > 60 ? "..." : ""}`) + chalk9__default.default.cyan("\n\n\u4F7F\u7528 /new <\u9700\u6C42> \u5F00\u59CB\u65B0\u7684\u5DE5\u4F5C\u6D41")
2075
2124
  };
2076
2125
  }
2077
- if (trimmed === "fail" || trimmed === "\u5931\u8D25" || trimmed === "reject" || trimmed === "\u62D2\u7EDD") {
2126
+ if (trimmed === "y" || trimmed === "yes" || trimmed === "\u91CD\u65B0\u5BA1\u6838" || trimmed === "retry") {
2127
+ return executeWorkflow(ctx);
2128
+ }
2129
+ if (trimmed === "n" || trimmed === "no" || trimmed === "\u56DE\u9000" || trimmed === "rollback") {
2078
2130
  activeSession.phase = "spec";
2131
+ return executeWorkflow(ctx);
2132
+ }
2133
+ if (trimmed === "fix" || trimmed === "\u4FEE\u590D") {
2079
2134
  return {
2080
- output: chalk9__default.default.yellow("\u21A9\uFE0F \u5BA1\u6838\u672A\u901A\u8FC7\uFF0C\u56DE\u9000\u5230\u89C4\u683C\u9636\u6BB5") + chalk9__default.default.gray("\n\u8BF7\u91CD\u65B0\u786E\u8BA4\u6216\u4FEE\u6539\u89C4\u683C") + chalk9__default.default.yellow("\n\n\u4F7F\u7528 y \u786E\u8BA4\uFF0Cn \u91CD\u65B0\u751F\u6210")
2135
+ output: chalk9__default.default.yellow("\u{1F4DD} \u8BF7\u624B\u52A8\u4FEE\u590D\u4EE3\u7801\u95EE\u9898") + chalk9__default.default.gray("\n\u4FEE\u590D\u5B8C\u6210\u540E\u8F93\u5165 y \u91CD\u65B0\u5BA1\u6838") + chalk9__default.default.gray("\n\u6216\u8F93\u5165 n \u56DE\u9000\u5230\u89C4\u683C\u9636\u6BB5")
2081
2136
  };
2082
2137
  }
2083
2138
  }
@@ -2199,6 +2254,207 @@ function getCategoryLabel(category) {
2199
2254
  };
2200
2255
  return labels[category] || category;
2201
2256
  }
2257
+ async function executeDevelopment(ctx, session) {
2258
+ const workingDir = ctx.options.workingDirectory;
2259
+ const files = [];
2260
+ try {
2261
+ const systemPrompt = buildDevelopmentPrompt(session);
2262
+ const messages = [
2263
+ {
2264
+ role: "system",
2265
+ content: `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u524D\u7AEF\u5F00\u53D1\u5DE5\u7A0B\u5E08\u3002\u8BF7\u6839\u636E\u9700\u6C42\u89C4\u683C\u751F\u6210\u4EE3\u7801\u5B9E\u73B0\u3002
2266
+
2267
+ \u8981\u6C42\uFF1A
2268
+ 1. \u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801
2269
+ 2. \u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u4EE3\u7801\u98CE\u683C\u548C\u89C4\u8303
2270
+ 3. \u4F7F\u7528\u9879\u76EE\u6307\u5B9A\u7684\u6280\u672F\u6808
2271
+ 4. \u4EE3\u7801\u8981\u6709\u9002\u5F53\u7684\u6CE8\u91CA
2272
+ 5. \u8FD4\u56DE\u683C\u5F0F\uFF1A\u6BCF\u4E2A\u6587\u4EF6\u7528 \`\`\`filename \u4EE3\u7801 \`\`\` \u5305\u88F9
2273
+
2274
+ \u9879\u76EE\u4FE1\u606F\uFF1A
2275
+ - \u540D\u79F0: ${session.context?.name}
2276
+ - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
2277
+ - \u6280\u672F\u6808: ${session.context?.techStack.join(", ") || "\u672A\u6307\u5B9A"}
2278
+
2279
+ ${session.context?.devStandards ? `\u5F00\u53D1\u89C4\u8303\uFF1A
2280
+ ${session.context.devStandards.slice(0, 2e3)}` : ""}`
2281
+ },
2282
+ {
2283
+ role: "user",
2284
+ content: systemPrompt
2285
+ }
2286
+ ];
2287
+ const response = await ctx.modelService.sendMessage(messages, {
2288
+ temperature: 0.3,
2289
+ maxTokens: 8e3,
2290
+ agent: "frontend-dev"
2291
+ });
2292
+ const codeBlocks = parseCodeBlocks(response.content);
2293
+ for (const block of codeBlocks) {
2294
+ const filePath = path7__namespace.join(workingDir, block.filename);
2295
+ const dir = path7__namespace.dirname(filePath);
2296
+ await fs7__namespace.mkdir(dir, { recursive: true });
2297
+ await fs7__namespace.writeFile(filePath, block.code, "utf-8");
2298
+ files.push(block.filename);
2299
+ }
2300
+ if (files.length === 0) {
2301
+ const implDir = path7__namespace.join(workingDir, "src", "features");
2302
+ await fs7__namespace.mkdir(implDir, { recursive: true });
2303
+ const featureName = session.specItems[0]?.title || "feature";
2304
+ const fileName = `${featureName.replace(/[^a-zA-Z0-9]/g, "_")}.ts`;
2305
+ const filePath = path7__namespace.join(implDir, fileName);
2306
+ const stubCode = `/**
2307
+ * ${session.requirement}
2308
+ *
2309
+ * TODO: \u6B64\u6587\u4EF6\u7531 AI \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u6839\u636E\u9700\u6C42\u5B8C\u5584\u5B9E\u73B0
2310
+ */
2311
+
2312
+ export function ${featureName.replace(/[^a-zA-Z0-9]/g, "")}() {
2313
+ // TODO: \u5B9E\u73B0\u529F\u80FD
2314
+ console.log('${featureName} - \u5F85\u5B9E\u73B0');
2315
+ }
2316
+ `;
2317
+ await fs7__namespace.writeFile(filePath, stubCode, "utf-8");
2318
+ files.push(`src/features/${fileName}`);
2319
+ }
2320
+ return { success: true, files };
2321
+ } catch (error) {
2322
+ return {
2323
+ success: false,
2324
+ files: [],
2325
+ error: error.message
2326
+ };
2327
+ }
2328
+ }
2329
+ function buildDevelopmentPrompt(session) {
2330
+ const lines = [];
2331
+ lines.push("## \u9700\u6C42\u63CF\u8FF0");
2332
+ lines.push(session.refinedRequirement);
2333
+ lines.push("");
2334
+ lines.push("## BDD \u573A\u666F");
2335
+ for (const scenario of session.bddScenarios) {
2336
+ lines.push(`### ${scenario.feature}`);
2337
+ for (const s of scenario.scenarios) {
2338
+ lines.push(`- ${s.name}`);
2339
+ }
2340
+ }
2341
+ lines.push("");
2342
+ lines.push("## \u4EFB\u52A1\u5217\u8868");
2343
+ for (const item of session.specItems) {
2344
+ lines.push(`- [${item.id}] ${item.title}: ${item.description}`);
2345
+ }
2346
+ lines.push("");
2347
+ lines.push("\u8BF7\u6839\u636E\u4EE5\u4E0A\u9700\u6C42\u89C4\u683C\u751F\u6210\u4EE3\u7801\u5B9E\u73B0\u3002");
2348
+ return lines.join("\n");
2349
+ }
2350
+ function parseCodeBlocks(content) {
2351
+ const blocks = [];
2352
+ const regex = /```(\S+)\n([\s\S]*?)```/g;
2353
+ let match;
2354
+ while ((match = regex.exec(content)) !== null) {
2355
+ const filename = match[1];
2356
+ const code = match[2].trim();
2357
+ if (["text", "json", "markdown", "md"].includes(filename.toLowerCase())) {
2358
+ continue;
2359
+ }
2360
+ blocks.push({ filename, code });
2361
+ }
2362
+ return blocks;
2363
+ }
2364
+ async function executeReview(ctx, session) {
2365
+ const workingDir = ctx.options.workingDirectory;
2366
+ const issues = [];
2367
+ const suggestions = [];
2368
+ try {
2369
+ const codeContents = [];
2370
+ for (const file of session.implFiles) {
2371
+ try {
2372
+ const content = await fs7__namespace.readFile(path7__namespace.join(workingDir, file), "utf-8");
2373
+ codeContents.push(`// ${file}
2374
+ ${content}`);
2375
+ } catch {
2376
+ }
2377
+ }
2378
+ const testContents = [];
2379
+ for (const file of session.testFiles) {
2380
+ try {
2381
+ const content = await fs7__namespace.readFile(path7__namespace.join(workingDir, file), "utf-8");
2382
+ testContents.push(`// ${file}
2383
+ ${content}`);
2384
+ } catch {
2385
+ }
2386
+ }
2387
+ const messages = [
2388
+ {
2389
+ role: "system",
2390
+ content: `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u4EE3\u7801\u5BA1\u6838\u4E13\u5BB6\u3002\u8BF7\u5BA1\u6838\u4EE5\u4E0B\u4EE3\u7801\u5B9E\u73B0\u662F\u5426\u7B26\u5408\u9700\u6C42\u89C4\u683C\u3002
2391
+
2392
+ \u5BA1\u6838\u6807\u51C6\uFF1A
2393
+ 1. \u4EE3\u7801\u662F\u5426\u6B63\u786E\u5B9E\u73B0\u4E86\u9700\u6C42\u89C4\u683C\u4E2D\u7684\u529F\u80FD
2394
+ 2. \u4EE3\u7801\u8D28\u91CF\uFF08\u53EF\u8BFB\u6027\u3001\u53EF\u7EF4\u62A4\u6027\uFF09
2395
+ 3. \u6F5C\u5728\u7684 bug \u548C\u8FB9\u754C\u60C5\u51B5\u5904\u7406
2396
+ 4. \u4EE3\u7801\u98CE\u683C\u662F\u5426\u7B26\u5408\u9879\u76EE\u89C4\u8303
2397
+ 5. \u6D4B\u8BD5\u8986\u76D6\u662F\u5426\u5145\u5206
2398
+
2399
+ \u8FD4\u56DE\u683C\u5F0F\uFF1A
2400
+ - \u5982\u679C\u901A\u8FC7\u5BA1\u6838\uFF0C\u8FD4\u56DE "\u5BA1\u6838\u901A\u8FC7" \u5E76\u5217\u51FA\u6539\u8FDB\u5EFA\u8BAE
2401
+ - \u5982\u679C\u4E0D\u901A\u8FC7\uFF0C\u5217\u51FA\u5177\u4F53\u95EE\u9898
2402
+
2403
+ \u9879\u76EE\u4FE1\u606F\uFF1A
2404
+ - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
2405
+ - \u6280\u672F\u6808: ${session.context?.techStack.join(", ") || "\u672A\u6307\u5B9A"}
2406
+
2407
+ ${session.context?.devStandards ? `\u5F00\u53D1\u89C4\u8303\uFF1A
2408
+ ${session.context.devStandards.slice(0, 1e3)}` : ""}`
2409
+ },
2410
+ {
2411
+ role: "user",
2412
+ content: `## \u9700\u6C42\u89C4\u683C
2413
+ ${session.refinedRequirement}
2414
+
2415
+ ## \u4EFB\u52A1\u5217\u8868
2416
+ ${session.specItems.map((item) => `- [${item.id}] ${item.title}`).join("\n")}
2417
+
2418
+ ## \u5B9E\u73B0\u4EE3\u7801
2419
+ ${codeContents.join("\n\n") || "\uFF08\u65E0\u4EE3\u7801\u6587\u4EF6\uFF09"}
2420
+
2421
+ ## \u6D4B\u8BD5\u4EE3\u7801
2422
+ ${testContents.join("\n\n") || "\uFF08\u65E0\u6D4B\u8BD5\u6587\u4EF6\uFF09"}
2423
+
2424
+ \u8BF7\u5BA1\u6838\u4EE5\u4E0A\u4EE3\u7801\u5B9E\u73B0\u3002`
2425
+ }
2426
+ ];
2427
+ const response = await ctx.modelService.sendMessage(messages, {
2428
+ temperature: 0.2,
2429
+ maxTokens: 2e3,
2430
+ agent: "code-reviewer"
2431
+ });
2432
+ const result = response.content;
2433
+ const passed = result.includes("\u5BA1\u6838\u901A\u8FC7") || result.includes("\u901A\u8FC7") || !result.includes("\u4E0D\u901A\u8FC7");
2434
+ const issueMatches = result.match(/问题[::]\s*([\s\S]*?)(?=建议|$)/i);
2435
+ if (issueMatches) {
2436
+ const issueList = issueMatches[1].split(/[\n-]/).filter((s) => s.trim());
2437
+ issues.push(...issueList.map((s) => s.trim()).filter((s) => s));
2438
+ }
2439
+ const suggestionMatches = result.match(/建议[::]\s*([\s\S]*?)$/i);
2440
+ if (suggestionMatches) {
2441
+ const suggestionList = suggestionMatches[1].split(/[\n-]/).filter((s) => s.trim());
2442
+ suggestions.push(...suggestionList.map((s) => s.trim()).filter((s) => s));
2443
+ }
2444
+ if (issues.length === 0 && !passed) {
2445
+ const lines = result.split("\n").filter((l) => l.includes("\u95EE\u9898") || l.includes("\u9519\u8BEF") || l.includes("\u7F3A\u9677"));
2446
+ issues.push(...lines.map((l) => l.replace(/^[-*]\s*/, "").trim()).filter((l) => l));
2447
+ }
2448
+ if (suggestions.length === 0) {
2449
+ const lines = result.split("\n").filter((l) => l.includes("\u5EFA\u8BAE") || l.includes("\u6539\u8FDB") || l.includes("\u4F18\u5316"));
2450
+ suggestions.push(...lines.map((l) => l.replace(/^[-*]\s*/, "").trim()).filter((l) => l));
2451
+ }
2452
+ return { passed, issues, suggestions };
2453
+ } catch (error) {
2454
+ suggestions.push(`\u5BA1\u6838\u8FC7\u7A0B\u51FA\u9519: ${error.message}`);
2455
+ return { passed: true, issues, suggestions };
2456
+ }
2457
+ }
2202
2458
  async function readProjectContext(workingDir) {
2203
2459
  const context = {
2204
2460
  name: path7__namespace.basename(workingDir),
@@ -6680,7 +6936,7 @@ async function executeShell(command, ctx) {
6680
6936
  init_cjs_shims();
6681
6937
  init_new();
6682
6938
  async function handleNaturalLanguage(input, ctx) {
6683
- input.trim().toLowerCase();
6939
+ const trimmedInput = input.trim();
6684
6940
  const session = getActiveSession();
6685
6941
  if (session) {
6686
6942
  const result = await handleWorkflowInput(input, ctx);
@@ -6691,81 +6947,163 @@ async function handleNaturalLanguage(input, ctx) {
6691
6947
  };
6692
6948
  }
6693
6949
  }
6694
- ctx.contextManager.addMessage({
6695
- role: "user",
6696
- content: input
6697
- });
6698
- return {
6699
- output: chalk9__default.default.cyan("\u6B63\u5728\u601D\u8003...") + chalk9__default.default.yellow("\n\n\u81EA\u7136\u8BED\u8A00\u5904\u7406\u529F\u80FD\u5F00\u53D1\u4E2D...") + chalk9__default.default.gray(`
6700
- \u8F93\u5165: ${input}`),
6701
- contextUsed: input.length
6702
- };
6950
+ const apiKey = ctx.configManager.get("apiKey");
6951
+ if (!apiKey) {
6952
+ return {
6953
+ output: chalk9__default.default.yellow("\u26A0\uFE0F \u672A\u914D\u7F6E API Key\uFF0C\u65E0\u6CD5\u4F7F\u7528 AI \u529F\u80FD") + chalk9__default.default.gray("\n\n\u8BF7\u5148\u6267\u884C /model \u914D\u7F6E\u6A21\u578B") + chalk9__default.default.gray("\n\u652F\u6301: GLM-5, GPT-4o, Claude"),
6954
+ contextUsed: 0
6955
+ };
6956
+ }
6957
+ try {
6958
+ const response = await ctx.modelService.sendMessage(
6959
+ [
6960
+ {
6961
+ role: "system",
6962
+ content: buildSystemPrompt(ctx)
6963
+ },
6964
+ {
6965
+ role: "user",
6966
+ content: trimmedInput
6967
+ }
6968
+ ],
6969
+ {
6970
+ temperature: 0.7,
6971
+ maxTokens: 2e3
6972
+ }
6973
+ );
6974
+ ctx.contextManager.addMessage({
6975
+ role: "user",
6976
+ content: trimmedInput
6977
+ });
6978
+ ctx.contextManager.addMessage({
6979
+ role: "assistant",
6980
+ content: response.content
6981
+ });
6982
+ return {
6983
+ output: response.content,
6984
+ contextUsed: response.usage?.totalTokens || 0
6985
+ };
6986
+ } catch (error) {
6987
+ const errorMessage = error.message;
6988
+ if (errorMessage.includes("\u672A\u914D\u7F6E") || errorMessage.includes("\u672A\u521D\u59CB\u5316")) {
6989
+ return {
6990
+ output: chalk9__default.default.yellow("\u26A0\uFE0F \u6A21\u578B\u672A\u6B63\u786E\u914D\u7F6E") + chalk9__default.default.gray("\n\n\u8BF7\u6267\u884C /model \u91CD\u65B0\u914D\u7F6E"),
6991
+ contextUsed: 0
6992
+ };
6993
+ }
6994
+ if (errorMessage.includes("timeout") || errorMessage.includes("\u8D85\u65F6")) {
6995
+ return {
6996
+ output: chalk9__default.default.red("\u2717 \u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"),
6997
+ contextUsed: 0
6998
+ };
6999
+ }
7000
+ return {
7001
+ output: chalk9__default.default.red(`\u2717 \u5904\u7406\u5931\u8D25: ${errorMessage}`),
7002
+ contextUsed: 0
7003
+ };
7004
+ }
7005
+ }
7006
+ function buildSystemPrompt(ctx) {
7007
+ const parts = [
7008
+ "\u4F60\u662F sf-cli \u7684 AI \u52A9\u624B\uFF0C\u4E00\u4E2A\u4E13\u4E1A\u7684\u8F6F\u4EF6\u5F00\u53D1\u52A9\u624B\u3002",
7009
+ "",
7010
+ "\u4F60\u53EF\u4EE5\u5E2E\u52A9\u7528\u6237\uFF1A",
7011
+ "1. \u7406\u89E3\u548C\u5206\u6790\u9700\u6C42",
7012
+ "2. \u63D0\u4F9B\u4EE3\u7801\u5EFA\u8BAE\u548C\u5B9E\u73B0\u65B9\u6848",
7013
+ "3. \u56DE\u7B54\u6280\u672F\u95EE\u9898",
7014
+ "4. \u8F85\u52A9\u8FDB\u884C\u4EE3\u7801\u5BA1\u67E5",
7015
+ "",
7016
+ "\u5F53\u524D\u9879\u76EE\u4FE1\u606F\uFF1A",
7017
+ `- \u5DE5\u4F5C\u76EE\u5F55: ${ctx.options.workingDirectory}`,
7018
+ `- \u6A21\u578B: ${ctx.modelService.getCurrentModel() || "\u672A\u6307\u5B9A"}`
7019
+ ];
7020
+ return parts.join("\n");
6703
7021
  }
6704
7022
 
6705
7023
  // src/cli/executor.ts
6706
7024
  init_new();
6707
- var BASIC_COMMANDS = [
7025
+ var ALWAYS_ALLOWED = [
6708
7026
  "help",
6709
7027
  "h",
6710
7028
  "?",
6711
- "init",
6712
- "i",
6713
7029
  "model",
6714
7030
  "m",
6715
- "new",
6716
- "n",
6717
7031
  "exit",
6718
7032
  "e",
6719
7033
  "q",
6720
7034
  "quit",
6721
7035
  "clear",
6722
7036
  "c",
6723
- "update",
6724
- "u",
6725
7037
  "version",
6726
7038
  "v"
6727
7039
  ];
7040
+ var REQUIRES_API_KEY = [
7041
+ "new",
7042
+ "n",
7043
+ "init",
7044
+ "i",
7045
+ "update",
7046
+ "u"
7047
+ ];
6728
7048
  var CommandExecutor = class {
6729
7049
  async execute(parseResult, ctx) {
6730
7050
  if (!parseResult.success || !parseResult.command) {
6731
7051
  return { output: chalk9__default.default.red(`\u9519\u8BEF: ${parseResult.error}`) };
6732
7052
  }
6733
7053
  const { command } = parseResult;
7054
+ const hasApiKey = !!ctx.configManager.get("apiKey");
6734
7055
  const hasActiveWorkflow = getActiveSession() !== null;
6735
- if (!hasActiveWorkflow) {
6736
- if (command.type === "slash" /* SLASH */) {
6737
- const cmd = command.command?.toLowerCase() || "";
6738
- if (!BASIC_COMMANDS.includes(cmd)) {
7056
+ if (command.type === "slash" /* SLASH */) {
7057
+ const cmd = command.command?.toLowerCase() || "";
7058
+ if (ALWAYS_ALLOWED.includes(cmd)) {
7059
+ return this.executeSlashCommand(command, ctx);
7060
+ }
7061
+ if (REQUIRES_API_KEY.includes(cmd)) {
7062
+ if (!hasApiKey) {
6739
7063
  return {
6740
- output: chalk9__default.default.yellow("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9__default.default.gray("\n\u8BF7\u5148\u4F7F\u7528 ") + chalk9__default.default.cyan("/new <\u9700\u6C42\u63CF\u8FF0>") + chalk9__default.default.gray(" \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41")
7064
+ output: chalk9__default.default.yellow("\u26A0\uFE0F \u8BF7\u5148\u914D\u7F6E API Key") + chalk9__default.default.gray("\n\n\u6267\u884C /model \u9009\u62E9\u6A21\u578B\u5E76\u914D\u7F6E API Key")
6741
7065
  };
6742
7066
  }
6743
- } else if (command.type === "dollar" /* DOLLAR */) {
7067
+ return this.executeSlashCommand(command, ctx);
7068
+ }
7069
+ if (!hasActiveWorkflow) {
7070
+ return {
7071
+ output: chalk9__default.default.yellow("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41") + chalk9__default.default.gray("\n\u8BF7\u5148\u4F7F\u7528 ") + chalk9__default.default.cyan("/new <\u9700\u6C42\u63CF\u8FF0>") + chalk9__default.default.gray(" \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41")
7072
+ };
7073
+ }
7074
+ return this.executeSlashCommand(command, ctx);
7075
+ }
7076
+ if (command.type === "dollar" /* DOLLAR */) {
7077
+ if (!hasActiveWorkflow) {
6744
7078
  return {
6745
7079
  output: chalk9__default.default.yellow("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41\uFF0C\u65E0\u6CD5\u8C03\u7528 Agent") + chalk9__default.default.gray("\n\u8BF7\u5148\u4F7F\u7528 ") + chalk9__default.default.cyan("/new <\u9700\u6C42\u63CF\u8FF0>") + chalk9__default.default.gray(" \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41")
6746
7080
  };
6747
- } else if (command.type === "shell" /* SHELL */) {
7081
+ }
7082
+ if (!hasApiKey) {
7083
+ return {
7084
+ output: chalk9__default.default.yellow("\u26A0\uFE0F \u8BF7\u5148\u914D\u7F6E API Key") + chalk9__default.default.gray("\n\n\u6267\u884C /model \u914D\u7F6E\u6A21\u578B")
7085
+ };
7086
+ }
7087
+ return this.executeAgent(command, ctx);
7088
+ }
7089
+ if (command.type === "shell" /* SHELL */) {
7090
+ if (!hasActiveWorkflow) {
6748
7091
  return {
6749
7092
  output: chalk9__default.default.yellow("\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u5DE5\u4F5C\u6D41\uFF0C\u65E0\u6CD5\u6267\u884C Shell \u547D\u4EE4") + chalk9__default.default.gray("\n\u8BF7\u5148\u4F7F\u7528 ") + chalk9__default.default.cyan("/new <\u9700\u6C42\u63CF\u8FF0>") + chalk9__default.default.gray(" \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41")
6750
7093
  };
6751
7094
  }
7095
+ return this.executeShell(command, ctx);
6752
7096
  }
6753
- switch (command.type) {
6754
- case "slash" /* SLASH */:
6755
- return this.executeSlashCommand(command, ctx);
6756
- case "at" /* AT */:
6757
- return this.executeFileReference(command, ctx);
6758
- case "dollar" /* DOLLAR */:
6759
- return this.executeAgent(command, ctx);
6760
- case "shell" /* SHELL */:
6761
- return this.executeShell(command, ctx);
6762
- case "natural" /* NATURAL */:
6763
- return this.executeNaturalLanguage(command, ctx);
6764
- case "yolo" /* YOLO */:
6765
- return this.executeYolo(ctx);
6766
- default:
6767
- return { output: chalk9__default.default.red("\u672A\u77E5\u7684\u547D\u4EE4\u7C7B\u578B") };
7097
+ if (command.type === "at" /* AT */) {
7098
+ return this.executeFileReference(command, ctx);
7099
+ }
7100
+ if (command.type === "natural" /* NATURAL */) {
7101
+ return this.executeNaturalLanguage(command, ctx);
6768
7102
  }
7103
+ if (command.type === "yolo" /* YOLO */) {
7104
+ return this.executeYolo(ctx);
7105
+ }
7106
+ return { output: chalk9__default.default.red("\u672A\u77E5\u7684\u547D\u4EE4\u7C7B\u578B") };
6769
7107
  }
6770
7108
  async executeSlashCommand(command, ctx) {
6771
7109
  const result = await runSlashCommand(
@@ -7414,6 +7752,7 @@ async function startInteractiveMode(options) {
7414
7752
  contextLimit: 512 * 1024
7415
7753
  };
7416
7754
  const currentModel = modelService.getCurrentModel() || "GLM-5";
7755
+ const hasApiKey = !!configManager.get("apiKey");
7417
7756
  const activeSession2 = getActiveSession();
7418
7757
  displayStatus({
7419
7758
  directory: options.workingDirectory,
@@ -7422,14 +7761,34 @@ async function startInteractiveMode(options) {
7422
7761
  services: [],
7423
7762
  workflowStep: activeSession2?.phase
7424
7763
  });
7425
- if (activeSession2) {
7426
- console.log(chalk9__default.default.cyan(`
7764
+ if (!hasApiKey) {
7765
+ console.log("");
7766
+ console.log(chalk9__default.default.yellow.bold("\u26A0\uFE0F \u672A\u914D\u7F6E API Key"));
7767
+ console.log("");
7768
+ console.log(chalk9__default.default.gray("\u8BF7\u5148\u914D\u7F6E\u6A21\u578B\u4EE5\u542F\u7528 AI \u529F\u80FD:"));
7769
+ console.log("");
7770
+ console.log(chalk9__default.default.cyan(" /model ") + chalk9__default.default.gray("- \u4EA4\u4E92\u5F0F\u9009\u62E9\u6A21\u578B\u5E76\u914D\u7F6E API Key"));
7771
+ console.log(chalk9__default.default.cyan(" /model list ") + chalk9__default.default.gray("- \u67E5\u770B\u53EF\u7528\u6A21\u578B\u5217\u8868"));
7772
+ console.log(chalk9__default.default.cyan(" /model set <model-id> ") + chalk9__default.default.gray("- \u76F4\u63A5\u8BBE\u7F6E\u6A21\u578B"));
7773
+ console.log("");
7774
+ console.log(chalk9__default.default.gray("\u652F\u6301\u7684\u6A21\u578B:"));
7775
+ console.log(chalk9__default.default.gray(" \u2022 GLM-5 (\u667A\u8C31AI) - \u63A8\u8350"));
7776
+ console.log(chalk9__default.default.gray(" \u2022 GPT-4o (OpenAI)"));
7777
+ console.log(chalk9__default.default.gray(" \u2022 Claude (Anthropic)"));
7778
+ console.log("");
7779
+ console.log(chalk9__default.default.gray("\u2500".repeat(50)));
7780
+ } else {
7781
+ if (activeSession2) {
7782
+ console.log(chalk9__default.default.cyan(`
7427
7783
  \u{1F4CB} \u6D3B\u8DC3\u5DE5\u4F5C\u6D41: ${activeSession2.requirement.slice(0, 40)}...`));
7428
- console.log(chalk9__default.default.gray(` \u9636\u6BB5: ${activeSession2.phase}`));
7429
- console.log(chalk9__default.default.gray(` \u8F93\u5165\u4EFB\u610F\u5185\u5BB9\u7EE7\u7EED
7784
+ console.log(chalk9__default.default.gray(` \u9636\u6BB5: ${activeSession2.phase}`));
7785
+ console.log(chalk9__default.default.gray(` \u8F93\u5165\u4EFB\u610F\u5185\u5BB9\u7EE7\u7EED
7430
7786
  `));
7431
- } else {
7432
- console.log(chalk9__default.default.gray("\n\u{1F4A1} \u4F7F\u7528 /new <\u9700\u6C42\u63CF\u8FF0> \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41\n"));
7787
+ } else {
7788
+ console.log(chalk9__default.default.green("\n\u2713 \u5DF2\u914D\u7F6E\u6A21\u578B: ") + chalk9__default.default.white(currentModel));
7789
+ console.log(chalk9__default.default.gray("\n\u{1F4A1} \u4F7F\u7528 /new <\u9700\u6C42\u63CF\u8FF0> \u542F\u52A8\u65B0\u5DE5\u4F5C\u6D41"));
7790
+ console.log(chalk9__default.default.gray(" \u6216\u76F4\u63A5\u8F93\u5165\u81EA\u7136\u8BED\u8A00\u4E0E AI \u4EA4\u4E92\n"));
7791
+ }
7433
7792
  }
7434
7793
  const saveHistory = async () => {
7435
7794
  try {