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 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://api.newapi.pro/v1";
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
- "gpt-3.5-turbo";
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.startsWith("/config")) {
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",
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"
@@ -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
+ }