xiaozhou-chat 1.0.3 → 1.0.4
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/bin/cli.js +143 -4
- package/package.json +8 -2
- package/update_config.js +19 -0
package/bin/cli.js
CHANGED
|
@@ -140,7 +140,10 @@ function showHelp() {
|
|
|
140
140
|
/help 显示帮助
|
|
141
141
|
/config [set] 查看或修改配置
|
|
142
142
|
/system [prompt] 设置系统提示词
|
|
143
|
+
/scan 扫描当前目录结构到上下文
|
|
143
144
|
/load <file> 加载文件内容到上下文
|
|
145
|
+
/save [index] <path> 保存 AI 回复中的代码块
|
|
146
|
+
/paste 进入多行粘贴模式
|
|
144
147
|
/history [N] 显示历史(可选最近 N 条)
|
|
145
148
|
/clear 清空历史
|
|
146
149
|
/export 导出历史为 Markdown
|
|
@@ -154,19 +157,20 @@ const config = loadConfig();
|
|
|
154
157
|
let API_KEY =
|
|
155
158
|
config.apiKey ||
|
|
156
159
|
args["api-key"] ||
|
|
157
|
-
process.env.NEWAPI_API_KEY
|
|
160
|
+
process.env.NEWAPI_API_KEY ||
|
|
161
|
+
"sk-KUDRAZivzcKUGWnfxAjskgX15uFmNPcKgPvxSWc5pCRwobnK";
|
|
158
162
|
|
|
159
163
|
let BASE_URL =
|
|
160
164
|
config.baseUrl ||
|
|
161
165
|
args["base-url"] ||
|
|
162
166
|
process.env.NEWAPI_BASE_URL ||
|
|
163
|
-
"https://
|
|
167
|
+
"https://paid.tribiosapi.top/v1";
|
|
164
168
|
|
|
165
169
|
let MODEL =
|
|
166
170
|
config.model ||
|
|
167
171
|
args["model"] ||
|
|
168
172
|
process.env.NEWAPI_MODEL ||
|
|
169
|
-
"
|
|
173
|
+
"claude-sonnet-4-5-20250929";
|
|
170
174
|
|
|
171
175
|
let SYSTEM_PROMPT =
|
|
172
176
|
config.systemPrompt ||
|
|
@@ -300,7 +304,28 @@ async function chatStream(userInput) {
|
|
|
300
304
|
|
|
301
305
|
rl.prompt();
|
|
302
306
|
|
|
307
|
+
let inputMode = "chat"; // chat | paste
|
|
308
|
+
let pasteBuffer = [];
|
|
309
|
+
|
|
303
310
|
rl.on("line", async (line) => {
|
|
311
|
+
if (inputMode === "paste") {
|
|
312
|
+
if (line.trim() === "---") {
|
|
313
|
+
inputMode = "chat";
|
|
314
|
+
const content = pasteBuffer.join("\n");
|
|
315
|
+
pasteBuffer = [];
|
|
316
|
+
console.log(`✅ 已接收 ${content.length} 字符,正在发送...`);
|
|
317
|
+
try {
|
|
318
|
+
await chatStream(content);
|
|
319
|
+
} catch (e) {
|
|
320
|
+
console.error("❌", e.message);
|
|
321
|
+
}
|
|
322
|
+
rl.setPrompt("小周> ");
|
|
323
|
+
return rl.prompt();
|
|
324
|
+
}
|
|
325
|
+
pasteBuffer.push(line);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
304
329
|
const input = line.trim();
|
|
305
330
|
if (!input) return rl.prompt();
|
|
306
331
|
|
|
@@ -319,6 +344,47 @@ rl.on("line", async (line) => {
|
|
|
319
344
|
return rl.prompt();
|
|
320
345
|
}
|
|
321
346
|
|
|
347
|
+
if (input === "/scan") {
|
|
348
|
+
console.log("🔍 正在扫描项目结构...");
|
|
349
|
+
const ignore = ["node_modules", ".git", "dist", "coverage", ".DS_Store", ".env"];
|
|
350
|
+
|
|
351
|
+
function scanDir(dir, prefix = "") {
|
|
352
|
+
let output = "";
|
|
353
|
+
try {
|
|
354
|
+
const files = fs.readdirSync(dir, { withFileTypes: true });
|
|
355
|
+
for (const file of files) {
|
|
356
|
+
if (ignore.includes(file.name)) continue;
|
|
357
|
+
if (file.isDirectory()) {
|
|
358
|
+
output += `${prefix}- ${file.name}/\n`;
|
|
359
|
+
output += scanDir(path.join(dir, file.name), `${prefix} `);
|
|
360
|
+
} else {
|
|
361
|
+
output += `${prefix}- ${file.name}\n`;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
} catch (e) {}
|
|
365
|
+
return output;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const structure = scanDir(process.cwd());
|
|
369
|
+
let contextMsg = `(Current Project Structure):\n\`\`\`\n${structure}\n\`\`\``;
|
|
370
|
+
|
|
371
|
+
// 尝试自动读取 package.json
|
|
372
|
+
try {
|
|
373
|
+
if (fs.existsSync("package.json")) {
|
|
374
|
+
const pkg = fs.readFileSync("package.json", "utf-8");
|
|
375
|
+
contextMsg += `\n\n(package.json content):\n\`\`\`json\n${pkg}\n\`\`\``;
|
|
376
|
+
console.log("📦 已自动包含 package.json 内容");
|
|
377
|
+
}
|
|
378
|
+
} catch {}
|
|
379
|
+
|
|
380
|
+
messages.push({ role: "user", content: contextMsg });
|
|
381
|
+
saveHistory(messages);
|
|
382
|
+
|
|
383
|
+
console.log(structure);
|
|
384
|
+
console.log("✅ 项目结构已发送给 AI,请继续提问(如:解释一下这个项目)。");
|
|
385
|
+
return rl.prompt();
|
|
386
|
+
}
|
|
387
|
+
|
|
322
388
|
if (input.startsWith("/load")) {
|
|
323
389
|
const file = input.slice(5).trim();
|
|
324
390
|
if (!file) {
|
|
@@ -338,7 +404,80 @@ rl.on("line", async (line) => {
|
|
|
338
404
|
return rl.prompt();
|
|
339
405
|
}
|
|
340
406
|
|
|
341
|
-
if (input
|
|
407
|
+
if (input === "/paste") {
|
|
408
|
+
inputMode = "paste";
|
|
409
|
+
console.log("📝 进入粘贴模式,请粘贴多行文本。");
|
|
410
|
+
console.log("👉 输入单独一行 '---' (三个减号) 结束并发送。");
|
|
411
|
+
rl.setPrompt("... ");
|
|
412
|
+
return rl.prompt();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (input.startsWith("/save")) {
|
|
416
|
+
const parts = input.split(/\s+/);
|
|
417
|
+
// 用法: /save [index] <filename> 或 /save <filename> (默认最后一个代码块)
|
|
418
|
+
let blockIndex = -1;
|
|
419
|
+
let filename = "";
|
|
420
|
+
|
|
421
|
+
if (parts.length === 2) {
|
|
422
|
+
filename = parts[1];
|
|
423
|
+
} else if (parts.length === 3) {
|
|
424
|
+
blockIndex = parseInt(parts[1]) - 1; // 转换为 0-based
|
|
425
|
+
filename = parts[2];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (!filename) {
|
|
429
|
+
console.log("❌ 用法: /save [index] <filename>");
|
|
430
|
+
return rl.prompt();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// 查找最后一条 AI 消息
|
|
434
|
+
const lastAiMsg = [...messages].reverse().find(m => m.role === "assistant");
|
|
435
|
+
if (!lastAiMsg) {
|
|
436
|
+
console.log("❌ 没有找到 AI 回复历史");
|
|
437
|
+
return rl.prompt();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 提取代码块
|
|
441
|
+
const regex = /```[\w]*\n([\s\S]*?)```/g;
|
|
442
|
+
const blocks = [];
|
|
443
|
+
let match;
|
|
444
|
+
while ((match = regex.exec(lastAiMsg.content)) !== null) {
|
|
445
|
+
blocks.push(match[1]);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (blocks.length === 0) {
|
|
449
|
+
console.log("❌ 上一条回复中没有找到代码块");
|
|
450
|
+
return rl.prompt();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 确定要保存哪个块
|
|
454
|
+
let contentToSave = "";
|
|
455
|
+
if (blockIndex >= 0) {
|
|
456
|
+
if (blockIndex >= blocks.length) {
|
|
457
|
+
console.log(`❌ 索引超出范围 (共 ${blocks.length} 个代码块)`);
|
|
458
|
+
return rl.prompt();
|
|
459
|
+
}
|
|
460
|
+
contentToSave = blocks[blockIndex];
|
|
461
|
+
} else {
|
|
462
|
+
// 默认保存最后一个,或者如果只有一个就保存那个
|
|
463
|
+
contentToSave = blocks[blocks.length - 1];
|
|
464
|
+
if (blocks.length > 1) {
|
|
465
|
+
console.log(`ℹ️ 检测到 ${blocks.length} 个代码块,默认保存最后一个。`);
|
|
466
|
+
console.log("👉 使用 /save 1 <filename> 指定保存第一个。");
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
try {
|
|
471
|
+
const savePath = path.resolve(process.cwd(), filename);
|
|
472
|
+
fs.writeFileSync(savePath, contentToSave, "utf-8");
|
|
473
|
+
console.log(`✅ 已保存到: ${savePath}`);
|
|
474
|
+
} catch (e) {
|
|
475
|
+
console.error(`❌ 保存失败: ${e.message}`);
|
|
476
|
+
}
|
|
477
|
+
return rl.prompt();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (input.startsWith("/config") || input.startsWith("/配置")) {
|
|
342
481
|
const parts = input.split(/\s+/);
|
|
343
482
|
const cmd = parts[1];
|
|
344
483
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xiaozhou-chat",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "CLI chatbot based on NewAPI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"xiaozhou-chat": "bin/cli.js"
|
|
@@ -10,7 +10,13 @@
|
|
|
10
10
|
"node": ">=18"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build:win": "pkg . --targets node18-win-x64 --output dist/newapi-chat.exe"
|
|
13
|
+
"build:win": "pkg . --targets node18-win-x64 --output dist/newapi-chat.exe",
|
|
14
|
+
"build:mac": "pkg . --targets node18-macos-x64 --output dist/xiaozhou-chat",
|
|
15
|
+
"pack:dmg": "mkdir -p release && cp dist/xiaozhou-chat release/ && create-dmg release dist"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"create-dmg": "^6.0.0",
|
|
19
|
+
"pkg": "^5.8.1"
|
|
14
20
|
},
|
|
15
21
|
"dependencies": {
|
|
16
22
|
"minimist": "^1.2.8"
|
package/update_config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const homeConfigFile = path.join(os.homedir(), ".newapi-chat-config.json");
|
|
6
|
+
|
|
7
|
+
const config = {
|
|
8
|
+
apiKey: "sk-KUDRAZivzcKUGWnfxAjskgX15uFmNPcKgPvxSWc5pCRwobnK",
|
|
9
|
+
baseUrl: "https://paid.tribiosapi.top/v1",
|
|
10
|
+
model: "claude-sonnet-4-5-20250929"
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
fs.writeFileSync(homeConfigFile, JSON.stringify(config, null, 2), "utf-8");
|
|
15
|
+
console.log("Successfully updated config file at " + homeConfigFile);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.error("Failed to update config:", e);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|