shenxiang-ai-cli 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +138 -13
  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.0")}`,
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}")}`,
@@ -2071,6 +2074,30 @@ var Agent = class {
2071
2074
  printWarning(t("errors.tokenLimit"));
2072
2075
  }
2073
2076
  }
2077
+ /**
2078
+ * Add file content to conversation context (for /read command)
2079
+ */
2080
+ addFileContext(filePath, content) {
2081
+ const maxChars = 5e4;
2082
+ const truncated = content.length > maxChars ? content.substring(0, maxChars) + `
2083
+
2084
+ ... [\u6587\u4EF6\u88AB\u622A\u65AD\uFF0C\u5171 ${content.length} \u5B57\u7B26\uFF0C\u53EA\u663E\u793A\u524D ${maxChars} \u5B57\u7B26]` : content;
2085
+ this.messages.push({
2086
+ role: "user",
2087
+ content: `[\u7528\u6237\u52A0\u8F7D\u4E86\u6587\u4EF6: ${filePath}]
2088
+
2089
+ \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
2090
+
2091
+ ---
2092
+ ${truncated}
2093
+ ---`
2094
+ });
2095
+ this.messages.push({
2096
+ role: "assistant",
2097
+ 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`
2098
+ });
2099
+ this.trimMessages();
2100
+ }
2074
2101
  /**
2075
2102
  * Clear conversation history (keep system prompt)
2076
2103
  */
@@ -2240,6 +2267,25 @@ async function chatCommand(options = {}) {
2240
2267
  if (projectCtx?.projectMemory) {
2241
2268
  console.log(theme.dim(` \u{1F4CB} \u5DF2\u52A0\u8F7D SXAI.md \u9879\u76EE\u6307\u4EE4`));
2242
2269
  }
2270
+ try {
2271
+ const docHintPatterns = ["*.md", "\u9700\u6C42*.md", "PRD*", "prd*", "\u8BBE\u8BA1*.md", "docs/*.md"];
2272
+ const foundDocs = [];
2273
+ for (const p of docHintPatterns) {
2274
+ const files = await glob4(p, {
2275
+ cwd: process.cwd(),
2276
+ ignore: ["node_modules/**", ".git/**", "dist/**", "SXAI.md"],
2277
+ nodir: true
2278
+ });
2279
+ for (const f of files) {
2280
+ if (!foundDocs.includes(f) && foundDocs.length < 5) foundDocs.push(f);
2281
+ }
2282
+ }
2283
+ if (foundDocs.length > 0) {
2284
+ console.log(theme.dim(` \u{1F4C4} \u68C0\u6D4B\u5230\u6587\u6863: ${foundDocs.join(", ")}`));
2285
+ console.log(theme.dim(` \u8F93\u5165 /read <\u6587\u4EF6\u540D> \u52A0\u8F7D\u5230\u5BF9\u8BDD\uFF0C\u6216 /docs \u67E5\u770B\u5168\u90E8`));
2286
+ }
2287
+ } catch {
2288
+ }
2243
2289
  printSeparator();
2244
2290
  console.log();
2245
2291
  } catch (err) {
@@ -2358,15 +2404,18 @@ async function handleSlashCommand(input, agent) {
2358
2404
  case "/h":
2359
2405
  console.log(`
2360
2406
  \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
2407
+ /read <\u8DEF\u5F84> \u8BFB\u53D6\u6587\u4EF6\u5185\u5BB9\u5582\u7ED9AI\uFF08\u5982 /read \u9700\u6C42\u6587\u6863.md\uFF09
2408
+ /docs \u626B\u63CF\u9879\u76EE\u4E2D\u7684\u6587\u6863\u6587\u4EF6
2409
+ /model \u67E5\u770B/\u5207\u6362\u6A21\u578B
2410
+ /auto \u5207\u6362\u81EA\u52A8\u6A21\u5F0F\uFF08\u8DF3\u8FC7\u786E\u8BA4\uFF09
2411
+ /clear \u6E05\u9664\u5BF9\u8BDD\u5386\u53F2
2412
+ /account \u67E5\u770B\u8D26\u6237\u4FE1\u606F
2413
+ /status \u67E5\u770B\u9879\u76EE\u4FE1\u606F
2414
+ /config \u67E5\u770B\u914D\u7F6E
2415
+ /logout \u9000\u51FA\u767B\u5F55
2416
+ /exit \u9000\u51FA\u7A0B\u5E8F
2417
+
2418
+ \u63D0\u793A: \u53EF\u4EE5\u76F4\u63A5\u8BF4"\u8BF7\u8BFB\u53D6 xxx \u6587\u4EF6"\u8BA9AI\u81EA\u5DF1\u53BB\u8BFB
2370
2419
  `);
2371
2420
  break;
2372
2421
  case "/auto":
@@ -2380,6 +2429,82 @@ async function handleSlashCommand(input, agent) {
2380
2429
  }
2381
2430
  break;
2382
2431
  }
2432
+ case "/read": {
2433
+ const filePath = args.join(" ").trim();
2434
+ if (!filePath) {
2435
+ printError("\u7528\u6CD5: /read <\u6587\u4EF6\u8DEF\u5F84> \u4F8B\u5982: /read \u9700\u6C42\u6587\u6863.md");
2436
+ break;
2437
+ }
2438
+ try {
2439
+ const absPath = path5.resolve(process.cwd(), filePath);
2440
+ const content = await fs5.readFile(absPath, "utf-8");
2441
+ const lines = content.split("\n").length;
2442
+ const sizeKB = (Buffer.byteLength(content, "utf-8") / 1024).toFixed(1);
2443
+ printSuccess(`\u5DF2\u8BFB\u53D6: ${filePath} (${lines} \u884C, ${sizeKB}KB)`);
2444
+ agent.addFileContext(filePath, content);
2445
+ 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`));
2446
+ 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"`));
2447
+ } catch (err) {
2448
+ const msg = err instanceof Error ? err.message : String(err);
2449
+ if (msg.includes("ENOENT")) {
2450
+ printError(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`);
2451
+ } else {
2452
+ printError(`\u8BFB\u53D6\u5931\u8D25: ${msg}`);
2453
+ }
2454
+ }
2455
+ break;
2456
+ }
2457
+ case "/docs": {
2458
+ const docPatterns = [
2459
+ "*.md",
2460
+ "*.txt",
2461
+ "docs/**/*.md",
2462
+ "doc/**/*.md",
2463
+ "\u9700\u6C42*.md",
2464
+ "PRD*",
2465
+ "prd*",
2466
+ "\u8BBE\u8BA1*.md",
2467
+ "DESIGN*",
2468
+ "TODO*",
2469
+ "CHANGELOG*",
2470
+ "ARCHITECTURE*"
2471
+ ];
2472
+ const docFiles = [];
2473
+ for (const pattern of docPatterns) {
2474
+ try {
2475
+ const files = await glob4(pattern, {
2476
+ cwd: process.cwd(),
2477
+ ignore: ["node_modules/**", ".git/**", "dist/**"],
2478
+ nodir: true
2479
+ });
2480
+ for (const f of files) {
2481
+ if (!docFiles.includes(f)) docFiles.push(f);
2482
+ }
2483
+ } catch {
2484
+ }
2485
+ }
2486
+ if (docFiles.length === 0) {
2487
+ printInfo("\u672A\u627E\u5230\u6587\u6863\u6587\u4EF6\u3002");
2488
+ } else {
2489
+ console.log(theme.dim(`
2490
+ \u2500\u2500 \u9879\u76EE\u6587\u6863 \u2500\u2500`));
2491
+ for (const f of docFiles.sort().slice(0, 20)) {
2492
+ try {
2493
+ const stat = await fs5.stat(path5.resolve(process.cwd(), f));
2494
+ const sizeKB = (stat.size / 1024).toFixed(1);
2495
+ console.log(` \u{1F4C4} ${theme.info(f)} ${theme.dim(`(${sizeKB}KB)`)}`);
2496
+ } catch {
2497
+ console.log(` \u{1F4C4} ${theme.info(f)}`);
2498
+ }
2499
+ }
2500
+ if (docFiles.length > 20) {
2501
+ console.log(theme.dim(` ... \u8FD8\u6709 ${docFiles.length - 20} \u4E2A\u6587\u4EF6`));
2502
+ }
2503
+ console.log(theme.dim(`
2504
+ \u4F7F\u7528 /read <\u6587\u4EF6\u540D> \u52A0\u8F7D\u6587\u4EF6\u5230\u5BF9\u8BDD\u4E0A\u4E0B\u6587`));
2505
+ }
2506
+ break;
2507
+ }
2383
2508
  case "/clear":
2384
2509
  agent.clearHistory();
2385
2510
  printSuccess("\u5BF9\u8BDD\u5386\u53F2\u5DF2\u6E05\u9664\u3002");
@@ -2904,8 +3029,8 @@ async function adminCommand() {
2904
3029
  break;
2905
3030
  }
2906
3031
  }
2907
- async function adminFetch(config3, path5, options) {
2908
- const response = await fetch(`${config3.apiBaseUrl}${path5}`, {
3032
+ async function adminFetch(config3, path6, options) {
3033
+ const response = await fetch(`${config3.apiBaseUrl}${path6}`, {
2909
3034
  ...options,
2910
3035
  headers: {
2911
3036
  "Content-Type": "application/json",
@@ -3175,7 +3300,7 @@ function fmtTokens3(n) {
3175
3300
  var program = new Command();
3176
3301
  var config2 = getConfig();
3177
3302
  setLocale(config2.locale);
3178
- program.name("sxai").description(t("description")).version("0.4.4");
3303
+ program.name("sxai").description(t("description")).version("0.5.0");
3179
3304
  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
3305
  if (options.model && options.model !== config2.model) {
3181
3306
  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.0",
4
4
  "description": "沈翔的AI助手 - 终端里的AI全栈开发搭档",
5
5
  "type": "module",
6
6
  "bin": {