shenxiang-ai-cli 0.4.3 → 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.
- package/dist/index.js +152 -20
- 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.
|
|
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}")}`,
|
|
@@ -754,9 +757,6 @@ var marked = new Marked(markedTerminal());
|
|
|
754
757
|
function streamChunk(text) {
|
|
755
758
|
process.stdout.write(text);
|
|
756
759
|
}
|
|
757
|
-
function streamThinking(text) {
|
|
758
|
-
process.stdout.write(theme.dim(text));
|
|
759
|
-
}
|
|
760
760
|
function printColoredDiff(oldContent, newContent, filepath) {
|
|
761
761
|
const oldLines = oldContent.split("\n");
|
|
762
762
|
const newLines = newContent.split("\n");
|
|
@@ -1875,10 +1875,11 @@ var Agent = class {
|
|
|
1875
1875
|
let hasThinkingOutput = false;
|
|
1876
1876
|
let toolSpinnerStarted = false;
|
|
1877
1877
|
let idleTimer = null;
|
|
1878
|
+
let isStreamingThinking = false;
|
|
1878
1879
|
const resetIdleTimer = () => {
|
|
1879
1880
|
if (idleTimer) clearTimeout(idleTimer);
|
|
1880
1881
|
idleTimer = setTimeout(() => {
|
|
1881
|
-
if (!spinner.isSpinning && spinnerStopped) {
|
|
1882
|
+
if (!spinner.isSpinning && spinnerStopped && !isStreamingThinking) {
|
|
1882
1883
|
spinner.text = "AI \u6B63\u5728\u601D\u8003...";
|
|
1883
1884
|
spinner.start();
|
|
1884
1885
|
}
|
|
@@ -1912,21 +1913,30 @@ var Agent = class {
|
|
|
1912
1913
|
}
|
|
1913
1914
|
switch (chunk.type) {
|
|
1914
1915
|
case "thinking":
|
|
1916
|
+
isStreamingThinking = true;
|
|
1915
1917
|
if (!hasThinkingOutput) {
|
|
1916
1918
|
hasThinkingOutput = true;
|
|
1917
|
-
|
|
1919
|
+
process.stdout.write("\n");
|
|
1920
|
+
process.stdout.write(theme.dim(" \u250C\u2500 \u{1F4AD} \u601D\u8003\u4E2D \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
1921
|
+
process.stdout.write(theme.dim(" \u2502 "));
|
|
1918
1922
|
}
|
|
1919
|
-
|
|
1923
|
+
const thinkText = (chunk.content || "").replace(/\n/g, "\n" + theme.dim(" \u2502 "));
|
|
1924
|
+
process.stdout.write(theme.dim(thinkText));
|
|
1920
1925
|
break;
|
|
1921
1926
|
case "text":
|
|
1922
1927
|
if (hasThinkingOutput && !hasTextOutput) {
|
|
1923
|
-
|
|
1928
|
+
isStreamingThinking = false;
|
|
1929
|
+
process.stdout.write("\n" + theme.dim(" \u2514\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") + "\n\n");
|
|
1924
1930
|
}
|
|
1925
1931
|
responseText += chunk.content || "";
|
|
1926
1932
|
hasTextOutput = true;
|
|
1927
1933
|
streamChunk(chunk.content || "");
|
|
1928
1934
|
break;
|
|
1929
1935
|
case "tool_call_start":
|
|
1936
|
+
if (hasThinkingOutput && isStreamingThinking) {
|
|
1937
|
+
isStreamingThinking = false;
|
|
1938
|
+
process.stdout.write("\n" + theme.dim(" \u2514\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") + "\n");
|
|
1939
|
+
}
|
|
1930
1940
|
if (!toolSpinnerStarted) {
|
|
1931
1941
|
if (hasTextOutput) process.stdout.write("\n");
|
|
1932
1942
|
spinner.text = "\u6B63\u5728\u51C6\u5907\u64CD\u4F5C...";
|
|
@@ -2064,6 +2074,30 @@ var Agent = class {
|
|
|
2064
2074
|
printWarning(t("errors.tokenLimit"));
|
|
2065
2075
|
}
|
|
2066
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
|
+
}
|
|
2067
2101
|
/**
|
|
2068
2102
|
* Clear conversation history (keep system prompt)
|
|
2069
2103
|
*/
|
|
@@ -2233,6 +2267,25 @@ async function chatCommand(options = {}) {
|
|
|
2233
2267
|
if (projectCtx?.projectMemory) {
|
|
2234
2268
|
console.log(theme.dim(` \u{1F4CB} \u5DF2\u52A0\u8F7D SXAI.md \u9879\u76EE\u6307\u4EE4`));
|
|
2235
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
|
+
}
|
|
2236
2289
|
printSeparator();
|
|
2237
2290
|
console.log();
|
|
2238
2291
|
} catch (err) {
|
|
@@ -2351,15 +2404,18 @@ async function handleSlashCommand(input, agent) {
|
|
|
2351
2404
|
case "/h":
|
|
2352
2405
|
console.log(`
|
|
2353
2406
|
\u53EF\u7528\u547D\u4EE4:
|
|
2354
|
-
/
|
|
2355
|
-
/
|
|
2356
|
-
/
|
|
2357
|
-
/
|
|
2358
|
-
/
|
|
2359
|
-
/
|
|
2360
|
-
/
|
|
2361
|
-
/
|
|
2362
|
-
/
|
|
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
|
|
2363
2419
|
`);
|
|
2364
2420
|
break;
|
|
2365
2421
|
case "/auto":
|
|
@@ -2373,6 +2429,82 @@ async function handleSlashCommand(input, agent) {
|
|
|
2373
2429
|
}
|
|
2374
2430
|
break;
|
|
2375
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
|
+
}
|
|
2376
2508
|
case "/clear":
|
|
2377
2509
|
agent.clearHistory();
|
|
2378
2510
|
printSuccess("\u5BF9\u8BDD\u5386\u53F2\u5DF2\u6E05\u9664\u3002");
|
|
@@ -2897,8 +3029,8 @@ async function adminCommand() {
|
|
|
2897
3029
|
break;
|
|
2898
3030
|
}
|
|
2899
3031
|
}
|
|
2900
|
-
async function adminFetch(config3,
|
|
2901
|
-
const response = await fetch(`${config3.apiBaseUrl}${
|
|
3032
|
+
async function adminFetch(config3, path6, options) {
|
|
3033
|
+
const response = await fetch(`${config3.apiBaseUrl}${path6}`, {
|
|
2902
3034
|
...options,
|
|
2903
3035
|
headers: {
|
|
2904
3036
|
"Content-Type": "application/json",
|
|
@@ -3168,7 +3300,7 @@ function fmtTokens3(n) {
|
|
|
3168
3300
|
var program = new Command();
|
|
3169
3301
|
var config2 = getConfig();
|
|
3170
3302
|
setLocale(config2.locale);
|
|
3171
|
-
program.name("sxai").description(t("description")).version("0.
|
|
3303
|
+
program.name("sxai").description(t("description")).version("0.5.0");
|
|
3172
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) => {
|
|
3173
3305
|
if (options.model && options.model !== config2.model) {
|
|
3174
3306
|
setConfig("model", options.model);
|