shenxiang-ai-cli 0.4.4 → 0.5.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 +166 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,6 +5,9 @@ import { Command } from "commander";
5
5
 
6
6
  // src/commands/chat.ts
7
7
  import inquirer from "inquirer";
8
+ import fs5 from "fs/promises";
9
+ import path5 from "path";
10
+ import { glob as glob4 } from "glob";
8
11
 
9
12
  // src/core/providers/openai-compatible.ts
10
13
  import OpenAI from "openai";
@@ -737,7 +740,7 @@ function banner() {
737
740
  const w = chalk.white.bold;
738
741
  const lines = [
739
742
  ``,
740
- ` ${p("/\\_/\\")} ${b("\u6C88\u7FD4\u7684AI\u52A9\u624B")} ${m("v0.4.4")}`,
743
+ ` ${p("/\\_/\\")} ${b("\u6C88\u7FD4\u7684AI\u52A9\u624B")} ${m("v0.5.1")}`,
741
744
  ` ${p("(")} ${y("o")}${p(".")}${y("o")} ${p(")")} ${d("\u7EC8\u7AEF\u91CC\u7684AI\u5168\u6808\u5F00\u53D1\u642D\u6863")}`,
742
745
  ` ${p("> ")}${r("^")}${p(" <")}`,
743
746
  ` ${p("/|")}${p(" |")}${p("\\")} ${d("\u4F60\u7684\u7F16\u7A0B\u597D\u4F19\u4F34")} ${chalk.hex("#A78BFA")("\u{1F43E}")}`,
@@ -890,7 +893,8 @@ var writeFileTool = {
890
893
  return { success: false, output: "", error: `Failed to write file: ${msg}` };
891
894
  }
892
895
  },
893
- requiresConfirmation: true
896
+ requiresConfirmation: false
897
+ // auto-approve for speed; use /auto off + delete_file for safety
894
898
  };
895
899
  var editFileTool = {
896
900
  definition: {
@@ -953,7 +957,8 @@ var editFileTool = {
953
957
  return { success: false, output: "", error: `Failed to edit file: ${msg}` };
954
958
  }
955
959
  },
956
- requiresConfirmation: true
960
+ requiresConfirmation: false
961
+ // auto-approve for speed
957
962
  };
958
963
  var deleteFileTool = {
959
964
  definition: {
@@ -1673,7 +1678,8 @@ function generateSystemPrompt(ctx) {
1673
1678
  [\u89D2\u8272] \u7528\u6237\u662F\u4EA7\u54C1\u8D1F\u8D23\u4EBA\uFF0C\u4F60\u6765\u5B9E\u73B0\u3002\u901A\u4FD7\u89E3\u91CA\uFF0C\u4E0D\u5806\u672F\u8BED\u3002\u65B9\u5411\u4E0D\u5BF9\u5C31\u63D0\u9192\u3002\u5BF9\u5C40\u9650\u6027\u8BDA\u5B9E\u3002`);
1674
1679
  parts.push(`[\u534F\u4F5C] \u63A2\u7D22\u9700\u6C42\u2192\u89C4\u5212\u65B9\u6848\u2192\u5206\u9636\u6BB5\u6784\u5EFA\u2192\u6253\u78E8\u7EC6\u8282\u2192\u4EA4\u4ED8\u90E8\u7F72\u3002\u5173\u952E\u51B3\u7B56\u70B9\u505C\u4E0B\u786E\u8BA4\uFF0C\u9047\u95EE\u9898\u7ED9\u9009\u9879\u800C\u975E\u66FF\u7528\u6237\u51B3\u5B9A\u3002`);
1675
1680
  parts.push(`
1676
- [\u539F\u5219] \u5148\u8BFB\u518D\u6539\uFF1Bedit_file\u5C40\u90E8\u7F16\u8F91\uFF1B\u7834\u574F\u6027\u64CD\u4F5C\u8C28\u614E\uFF1B\u9075\u5FAA\u9879\u76EE\u98CE\u683C\uFF1B\u4E0D\u786E\u5B9A\u5C31\u5148\u641C\u7D22\uFF1B\u7528${ctx.packageManager}\u6267\u884C\u547D\u4EE4\uFF1B\u591A\u6587\u4EF6\u4FEE\u6539\u5148\u8BF4\u8BA1\u5212\u3002`);
1681
+ [\u539F\u5219] \u5148\u8BFB\u518D\u6539\uFF1Bedit_file\u5C40\u90E8\u7F16\u8F91\uFF1B\u7834\u574F\u6027\u64CD\u4F5C\u8C28\u614E\uFF1B\u9075\u5FAA\u9879\u76EE\u98CE\u683C\uFF1B\u4E0D\u786E\u5B9A\u5C31\u5148\u641C\u7D22\uFF1B\u7528${ctx.packageManager}\u6267\u884C\u547D\u4EE4\u3002`);
1682
+ parts.push(`[\u6548\u7387] \u91CD\u8981\uFF1A\u5C3D\u91CF\u5728\u4E00\u6B21\u56DE\u590D\u4E2D\u8FD4\u56DE\u591A\u4E2A\u5DE5\u5177\u8C03\u7528\uFF08\u5E76\u884C\uFF09\uFF0C\u4E0D\u8981\u4E00\u4E2A\u4E00\u4E2A\u6765\u3002\u4F8B\u5982\u9700\u8981\u521B\u5EFA3\u4E2A\u6587\u4EF6\uFF0C\u4E00\u6B21\u6027\u8FD4\u56DE3\u4E2Awrite_file\u8C03\u7528\uFF0C\u800C\u4E0D\u662F\u5199\u5B8C\u4E00\u4E2A\u518D\u5199\u4E0B\u4E00\u4E2A\u3002\u8FD9\u6837\u5927\u5E45\u51CF\u5C11\u7B49\u5F85\u65F6\u95F4\u3002`);
1677
1683
  if (ctx.projectType !== "unknown") {
1678
1684
  const info = [`\u76EE\u5F55:${ctx.rootDir}`, `\u7C7B\u578B:${ctx.projectType}`];
1679
1685
  if (ctx.projectName) info.push(`\u540D\u79F0:${ctx.projectName}`);
@@ -2014,7 +2020,8 @@ var Agent = class {
2014
2020
  if (reasoningContent) assistantMsg.reasoning_content = reasoningContent;
2015
2021
  this.messages.push(assistantMsg);
2016
2022
  console.log();
2017
- for (const tc of toolCalls) {
2023
+ if (toolCalls.length === 1) {
2024
+ const tc = toolCalls[0];
2018
2025
  let args;
2019
2026
  try {
2020
2027
  args = JSON.parse(tc.function.arguments);
@@ -2028,6 +2035,26 @@ var Agent = class {
2028
2035
  tool_call_id: tc.id,
2029
2036
  name: tc.function.name
2030
2037
  });
2038
+ } else {
2039
+ const tasks = toolCalls.map(async (tc) => {
2040
+ let args;
2041
+ try {
2042
+ args = JSON.parse(tc.function.arguments);
2043
+ } catch {
2044
+ args = {};
2045
+ }
2046
+ const result = await executeTool(tc.function.name, args);
2047
+ return { tc, result };
2048
+ });
2049
+ const results = await Promise.all(tasks);
2050
+ for (const { tc, result } of results) {
2051
+ this.messages.push({
2052
+ role: "tool",
2053
+ content: result.error || result.output,
2054
+ tool_call_id: tc.id,
2055
+ name: tc.function.name
2056
+ });
2057
+ }
2031
2058
  }
2032
2059
  }
2033
2060
  if (iteration >= MAX_TOOL_ITERATIONS) {
@@ -2071,6 +2098,30 @@ var Agent = class {
2071
2098
  printWarning(t("errors.tokenLimit"));
2072
2099
  }
2073
2100
  }
2101
+ /**
2102
+ * Add file content to conversation context (for /read command)
2103
+ */
2104
+ addFileContext(filePath, content) {
2105
+ const maxChars = 5e4;
2106
+ const truncated = content.length > maxChars ? content.substring(0, maxChars) + `
2107
+
2108
+ ... [\u6587\u4EF6\u88AB\u622A\u65AD\uFF0C\u5171 ${content.length} \u5B57\u7B26\uFF0C\u53EA\u663E\u793A\u524D ${maxChars} \u5B57\u7B26]` : content;
2109
+ this.messages.push({
2110
+ role: "user",
2111
+ content: `[\u7528\u6237\u52A0\u8F7D\u4E86\u6587\u4EF6: ${filePath}]
2112
+
2113
+ \u4EE5\u4E0B\u662F\u6587\u4EF6\u5185\u5BB9\uFF0C\u8BF7\u8BB0\u4F4F\u5B83\uFF0C\u540E\u7EED\u6211\u4F1A\u57FA\u4E8E\u8FD9\u4E2A\u6587\u4EF6\u5185\u5BB9\u7ED9\u4F60\u6307\u4EE4\uFF1A
2114
+
2115
+ ---
2116
+ ${truncated}
2117
+ ---`
2118
+ });
2119
+ this.messages.push({
2120
+ role: "assistant",
2121
+ content: `\u597D\u7684\uFF0C\u6211\u5DF2\u7ECF\u8BFB\u53D6\u4E86 ${filePath} \u7684\u5185\u5BB9\u3002\u8BF7\u544A\u8BC9\u6211\u4F60\u60F3\u57FA\u4E8E\u8FD9\u4E2A\u6587\u4EF6\u505A\u4EC0\u4E48\u3002`
2122
+ });
2123
+ this.trimMessages();
2124
+ }
2074
2125
  /**
2075
2126
  * Clear conversation history (keep system prompt)
2076
2127
  */
@@ -2240,6 +2291,25 @@ async function chatCommand(options = {}) {
2240
2291
  if (projectCtx?.projectMemory) {
2241
2292
  console.log(theme.dim(` \u{1F4CB} \u5DF2\u52A0\u8F7D SXAI.md \u9879\u76EE\u6307\u4EE4`));
2242
2293
  }
2294
+ try {
2295
+ const docHintPatterns = ["*.md", "\u9700\u6C42*.md", "PRD*", "prd*", "\u8BBE\u8BA1*.md", "docs/*.md"];
2296
+ const foundDocs = [];
2297
+ for (const p of docHintPatterns) {
2298
+ const files = await glob4(p, {
2299
+ cwd: process.cwd(),
2300
+ ignore: ["node_modules/**", ".git/**", "dist/**", "SXAI.md"],
2301
+ nodir: true
2302
+ });
2303
+ for (const f of files) {
2304
+ if (!foundDocs.includes(f) && foundDocs.length < 5) foundDocs.push(f);
2305
+ }
2306
+ }
2307
+ if (foundDocs.length > 0) {
2308
+ console.log(theme.dim(` \u{1F4C4} \u68C0\u6D4B\u5230\u6587\u6863: ${foundDocs.join(", ")}`));
2309
+ console.log(theme.dim(` \u8F93\u5165 /read <\u6587\u4EF6\u540D> \u52A0\u8F7D\u5230\u5BF9\u8BDD\uFF0C\u6216 /docs \u67E5\u770B\u5168\u90E8`));
2310
+ }
2311
+ } catch {
2312
+ }
2243
2313
  printSeparator();
2244
2314
  console.log();
2245
2315
  } catch (err) {
@@ -2358,15 +2428,18 @@ async function handleSlashCommand(input, agent) {
2358
2428
  case "/h":
2359
2429
  console.log(`
2360
2430
  \u53EF\u7528\u547D\u4EE4:
2361
- /help \u663E\u793A\u5E2E\u52A9
2362
- /model \u67E5\u770B/\u5207\u6362\u6A21\u578B
2363
- /auto \u5207\u6362\u81EA\u52A8\u6A21\u5F0F\uFF08\u8DF3\u8FC7\u786E\u8BA4\uFF09
2364
- /clear \u6E05\u9664\u5BF9\u8BDD\u5386\u53F2
2365
- /account \u67E5\u770B\u8D26\u6237\u4FE1\u606F
2366
- /status \u67E5\u770B\u9879\u76EE\u4FE1\u606F
2367
- /config \u67E5\u770B\u914D\u7F6E
2368
- /logout \u9000\u51FA\u767B\u5F55
2369
- /exit \u9000\u51FA\u7A0B\u5E8F
2431
+ /read <\u8DEF\u5F84> \u8BFB\u53D6\u6587\u4EF6\u5185\u5BB9\u5582\u7ED9AI\uFF08\u5982 /read \u9700\u6C42\u6587\u6863.md\uFF09
2432
+ /docs \u626B\u63CF\u9879\u76EE\u4E2D\u7684\u6587\u6863\u6587\u4EF6
2433
+ /model \u67E5\u770B/\u5207\u6362\u6A21\u578B
2434
+ /auto \u5207\u6362\u81EA\u52A8\u6A21\u5F0F\uFF08\u8DF3\u8FC7\u786E\u8BA4\uFF09
2435
+ /clear \u6E05\u9664\u5BF9\u8BDD\u5386\u53F2
2436
+ /account \u67E5\u770B\u8D26\u6237\u4FE1\u606F
2437
+ /status \u67E5\u770B\u9879\u76EE\u4FE1\u606F
2438
+ /config \u67E5\u770B\u914D\u7F6E
2439
+ /logout \u9000\u51FA\u767B\u5F55
2440
+ /exit \u9000\u51FA\u7A0B\u5E8F
2441
+
2442
+ \u63D0\u793A: \u53EF\u4EE5\u76F4\u63A5\u8BF4"\u8BF7\u8BFB\u53D6 xxx \u6587\u4EF6"\u8BA9AI\u81EA\u5DF1\u53BB\u8BFB
2370
2443
  `);
2371
2444
  break;
2372
2445
  case "/auto":
@@ -2380,6 +2453,82 @@ async function handleSlashCommand(input, agent) {
2380
2453
  }
2381
2454
  break;
2382
2455
  }
2456
+ case "/read": {
2457
+ const filePath = args.join(" ").trim();
2458
+ if (!filePath) {
2459
+ printError("\u7528\u6CD5: /read <\u6587\u4EF6\u8DEF\u5F84> \u4F8B\u5982: /read \u9700\u6C42\u6587\u6863.md");
2460
+ break;
2461
+ }
2462
+ try {
2463
+ const absPath = path5.resolve(process.cwd(), filePath);
2464
+ const content = await fs5.readFile(absPath, "utf-8");
2465
+ const lines = content.split("\n").length;
2466
+ const sizeKB = (Buffer.byteLength(content, "utf-8") / 1024).toFixed(1);
2467
+ printSuccess(`\u5DF2\u8BFB\u53D6: ${filePath} (${lines} \u884C, ${sizeKB}KB)`);
2468
+ agent.addFileContext(filePath, content);
2469
+ console.log(theme.dim(` \u6587\u4EF6\u5185\u5BB9\u5DF2\u6DFB\u52A0\u5230\u5BF9\u8BDD\u4E0A\u4E0B\u6587\uFF0C\u73B0\u5728\u53EF\u4EE5\u57FA\u4E8E\u5B83\u63D0\u95EE\u6216\u6307\u4EE4\u3002`));
2470
+ console.log(theme.dim(` \u4F8B\u5982: "\u6839\u636E\u8FD9\u4E2A\u9700\u6C42\u6587\u6863\u5F00\u59CB\u5F00\u53D1" \u6216 "\u5206\u6790\u8FD9\u4E2A\u6587\u4EF6\u7684\u95EE\u9898"`));
2471
+ } catch (err) {
2472
+ const msg = err instanceof Error ? err.message : String(err);
2473
+ if (msg.includes("ENOENT")) {
2474
+ printError(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`);
2475
+ } else {
2476
+ printError(`\u8BFB\u53D6\u5931\u8D25: ${msg}`);
2477
+ }
2478
+ }
2479
+ break;
2480
+ }
2481
+ case "/docs": {
2482
+ const docPatterns = [
2483
+ "*.md",
2484
+ "*.txt",
2485
+ "docs/**/*.md",
2486
+ "doc/**/*.md",
2487
+ "\u9700\u6C42*.md",
2488
+ "PRD*",
2489
+ "prd*",
2490
+ "\u8BBE\u8BA1*.md",
2491
+ "DESIGN*",
2492
+ "TODO*",
2493
+ "CHANGELOG*",
2494
+ "ARCHITECTURE*"
2495
+ ];
2496
+ const docFiles = [];
2497
+ for (const pattern of docPatterns) {
2498
+ try {
2499
+ const files = await glob4(pattern, {
2500
+ cwd: process.cwd(),
2501
+ ignore: ["node_modules/**", ".git/**", "dist/**"],
2502
+ nodir: true
2503
+ });
2504
+ for (const f of files) {
2505
+ if (!docFiles.includes(f)) docFiles.push(f);
2506
+ }
2507
+ } catch {
2508
+ }
2509
+ }
2510
+ if (docFiles.length === 0) {
2511
+ printInfo("\u672A\u627E\u5230\u6587\u6863\u6587\u4EF6\u3002");
2512
+ } else {
2513
+ console.log(theme.dim(`
2514
+ \u2500\u2500 \u9879\u76EE\u6587\u6863 \u2500\u2500`));
2515
+ for (const f of docFiles.sort().slice(0, 20)) {
2516
+ try {
2517
+ const stat = await fs5.stat(path5.resolve(process.cwd(), f));
2518
+ const sizeKB = (stat.size / 1024).toFixed(1);
2519
+ console.log(` \u{1F4C4} ${theme.info(f)} ${theme.dim(`(${sizeKB}KB)`)}`);
2520
+ } catch {
2521
+ console.log(` \u{1F4C4} ${theme.info(f)}`);
2522
+ }
2523
+ }
2524
+ if (docFiles.length > 20) {
2525
+ console.log(theme.dim(` ... \u8FD8\u6709 ${docFiles.length - 20} \u4E2A\u6587\u4EF6`));
2526
+ }
2527
+ console.log(theme.dim(`
2528
+ \u4F7F\u7528 /read <\u6587\u4EF6\u540D> \u52A0\u8F7D\u6587\u4EF6\u5230\u5BF9\u8BDD\u4E0A\u4E0B\u6587`));
2529
+ }
2530
+ break;
2531
+ }
2383
2532
  case "/clear":
2384
2533
  agent.clearHistory();
2385
2534
  printSuccess("\u5BF9\u8BDD\u5386\u53F2\u5DF2\u6E05\u9664\u3002");
@@ -2904,8 +3053,8 @@ async function adminCommand() {
2904
3053
  break;
2905
3054
  }
2906
3055
  }
2907
- async function adminFetch(config3, path5, options) {
2908
- const response = await fetch(`${config3.apiBaseUrl}${path5}`, {
3056
+ async function adminFetch(config3, path6, options) {
3057
+ const response = await fetch(`${config3.apiBaseUrl}${path6}`, {
2909
3058
  ...options,
2910
3059
  headers: {
2911
3060
  "Content-Type": "application/json",
@@ -3175,7 +3324,7 @@ function fmtTokens3(n) {
3175
3324
  var program = new Command();
3176
3325
  var config2 = getConfig();
3177
3326
  setLocale(config2.locale);
3178
- program.name("sxai").description(t("description")).version("0.4.4");
3327
+ program.name("sxai").description(t("description")).version("0.5.1");
3179
3328
  program.command("chat", { isDefault: true }).description("\u542F\u52A8\u4EA4\u4E92\u5F0FAI\u5BF9\u8BDD\uFF08\u9ED8\u8BA4\u547D\u4EE4\uFF09").option("-m, --model <model>", "\u6307\u5B9AAI\u6A21\u578B", config2.model).option("-y, --yolo", "\u81EA\u52A8\u6A21\u5F0F\uFF1A\u8DF3\u8FC7\u6240\u6709\u786E\u8BA4\uFF0C\u8BA9AI\u81EA\u4E3B\u6267\u884C").action(async (options) => {
3180
3329
  if (options.model && options.model !== config2.model) {
3181
3330
  setConfig("model", options.model);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shenxiang-ai-cli",
3
- "version": "0.4.4",
3
+ "version": "0.5.1",
4
4
  "description": "沈翔的AI助手 - 终端里的AI全栈开发搭档",
5
5
  "type": "module",
6
6
  "bin": {