xiaozhou-chat 1.0.26 → 1.0.28
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/README.md +72 -53
- package/bin/cli.js +58 -21
- package/lib/chat.js +38 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,37 +1,48 @@
|
|
|
1
1
|
# xiaozhou-chat
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
**A powerful CLI chatbot based on NewAPI, designed for developers.**
|
|
8
|
+
基于 NewAPI 的高级命令行聊天工具,专为开发者打造。支持项目上下文感知、多环境配置、MCP 协议及 AI 自动化工具。
|
|
5
9
|
|
|
6
10
|
---
|
|
7
11
|
|
|
8
|
-
## ✨
|
|
12
|
+
## ✨ 核心特性 (Features)
|
|
13
|
+
|
|
14
|
+
### 🤖 智能交互与开发
|
|
15
|
+
- **AI 编程助手**:内置文件读写 (`read_file`, `write_file`) 和命令执行 (`run_command`),可让 AI 帮你重构代码、运行测试。
|
|
16
|
+
- **项目感知**:通过 `/scan` 自动扫描目录结构,智能读取 `package.json` 等关键文件,让 AI 瞬间理解项目全貌。
|
|
17
|
+
- **智能提交**:`/commit` 自动分析暂存区 (`git diff --cached`) 代码变更,生成符合 Conventional Commits 规范的提交信息。
|
|
18
|
+
- **上下文压缩**:`/compress` 利用 AI 对长对话进行深度摘要,保留关键技术细节与决策,大幅节省 Token。
|
|
19
|
+
|
|
20
|
+
### ⚙️ 灵活配置与管理
|
|
21
|
+
- **多环境切换**:支持 Profiles (`/profile`),一键在不同 API Key、模型或 BaseURL 之间切换(如:开发环境/生产环境)。
|
|
22
|
+
- **MCP 协议支持**:完整集成 [Model Context Protocol](https://modelcontextprotocol.io/),可连接外部 MCP Server 扩展能力(如连接数据库、Notion 等)。
|
|
23
|
+
- **配置隔离**:支持项目级 (`./.newapi-chat-config.json`) 和全局级 (`~/.newapi-chat-config.json`) 配置文件,互不干扰。
|
|
9
24
|
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
- **Markdown 渲染**:终端内完美渲染 Markdown(代码高亮、表格、粗体等)。
|
|
18
|
-
- **Esc 快捷键**:按 `Esc` 清除当前输入,空行时按 `Esc` 退出程序。
|
|
19
|
-
- **⚙️ 灵活配置**:支持全局配置及项目级配置文件 (`.newapi-chat-config.json`),可随项目隔离环境。
|
|
20
|
-
- **🔌 MCP 支持**:集成 Model Context Protocol,可连接外部 MCP Server 扩展能力。
|
|
25
|
+
### 🛡️ 稳健与体验
|
|
26
|
+
- **环境自适应**:自动检测并清洗冲突的环境变量(如代理设置),内置指数退避重试机制,确保网络请求稳定。
|
|
27
|
+
- **极致体验**:
|
|
28
|
+
- **无缝打字**:防并发锁设计,解决 AI 生成时的光标跳动问题。
|
|
29
|
+
- **Markdown 渲染**:终端内完美渲染代码高亮、表格、粗体等。
|
|
30
|
+
- **快捷键支持**:`Ctrl+C` 优雅中断生成,`Esc` 快速清空输入。
|
|
31
|
+
- **安全写文件**:覆盖文件前自动创建 `.bak` 备份。
|
|
21
32
|
|
|
22
33
|
---
|
|
23
34
|
|
|
24
|
-
## 📦
|
|
35
|
+
## 📦 安装 (Installation)
|
|
25
36
|
|
|
26
37
|
### 方式一:在当前项目中使用 (推荐)
|
|
27
|
-
|
|
38
|
+
无需全局安装,直接集成到你的项目中:
|
|
28
39
|
|
|
29
40
|
1. **安装依赖**
|
|
30
41
|
```bash
|
|
31
42
|
npm install xiaozhou-chat
|
|
32
43
|
```
|
|
33
44
|
|
|
34
|
-
2. **初始化配置**
|
|
45
|
+
2. **初始化配置**
|
|
35
46
|
```bash
|
|
36
47
|
npx xiaozhou-chat init
|
|
37
48
|
```
|
|
@@ -42,7 +53,7 @@ CLI chatbot based on NewAPI with advanced features like Project Context, Multi-P
|
|
|
42
53
|
```
|
|
43
54
|
|
|
44
55
|
### 方式二:全局安装
|
|
45
|
-
|
|
56
|
+
如果你希望在任何目录下随时呼出 AI 助手:
|
|
46
57
|
|
|
47
58
|
```bash
|
|
48
59
|
npm i -g xiaozhou-chat
|
|
@@ -51,72 +62,80 @@ xiaozhou-chat
|
|
|
51
62
|
|
|
52
63
|
---
|
|
53
64
|
|
|
54
|
-
## 💬
|
|
65
|
+
## 💬 命令指南 (Commands)
|
|
55
66
|
|
|
56
|
-
在对话中输入 `/`
|
|
67
|
+
在对话中输入 `/` 开头的命令即可触发功能。
|
|
57
68
|
|
|
58
|
-
###
|
|
59
|
-
| 命令 |
|
|
69
|
+
### � 效率工具
|
|
70
|
+
| 命令 | 别名 | 功能说明 |
|
|
60
71
|
|---|---|---|
|
|
61
|
-
| `/
|
|
62
|
-
| `/
|
|
63
|
-
| `/
|
|
64
|
-
| `/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
| `/commit` | - | **AI 自动提交**:分析暂存区代码 -> 生成 Commit Message -> 确认提交 |
|
|
73
|
+
| `/compress` | - | **智能压缩**:AI 总结当前对话历史,释放 Token 空间 |
|
|
74
|
+
| `/scan` | `/当前项目结构` | 扫描并加载项目结构到上下文 |
|
|
75
|
+
| `/load <file>` | - | 加载指定文件内容(支持大文件行号选择) |
|
|
76
|
+
| `/paste` | - | 进入多行粘贴模式(输入 `---` 结束) |
|
|
77
|
+
|
|
78
|
+
### � 配置与管理
|
|
79
|
+
| 命令 | 别名 | 功能说明 |
|
|
68
80
|
|---|---|---|
|
|
69
|
-
| `/
|
|
70
|
-
| `/
|
|
71
|
-
| `/
|
|
72
|
-
| `/
|
|
81
|
+
| `/config` | `/配置` | 查看或修改配置 (如 `/config apiKey sk-xxx`) |
|
|
82
|
+
| `/profile` | `/切换模型` | 切换配置环境 (如 `/profile use gpt4`) |
|
|
83
|
+
| `/init` | `/初始化配置` | 在当前目录生成配置文件 |
|
|
84
|
+
| `/system` | - | 设置系统提示词 (System Prompt) |
|
|
85
|
+
| `/mcp` | - | 查看已连接的 MCP 服务器状态 |
|
|
73
86
|
|
|
74
|
-
###
|
|
87
|
+
### 📊 其他
|
|
75
88
|
| 命令 | 说明 |
|
|
76
89
|
|---|---|
|
|
77
|
-
| `/
|
|
78
|
-
| `/
|
|
79
|
-
| `/
|
|
80
|
-
| `/
|
|
81
|
-
| `/clear` | 清空当前对话历史 (保留 System Prompt) |
|
|
82
|
-
| `/history` | 查看历史记录 (支持 Markdown 渲染) |
|
|
83
|
-
| `/export` | 导出对话记录为 Markdown |
|
|
90
|
+
| `/token` | 查看当前 Token 消耗估算 |
|
|
91
|
+
| `/clear` | 清空对话历史 |
|
|
92
|
+
| `/history` | 查看最近的对话记录 |
|
|
93
|
+
| `/help` | 查看帮助菜单 |
|
|
84
94
|
|
|
85
95
|
---
|
|
86
96
|
|
|
87
97
|
## ⚙️ 配置文件 (Configuration)
|
|
88
98
|
|
|
89
|
-
|
|
90
|
-
1. **项目级配置**:`./.newapi-chat-config.json` (推荐,由 `init` 生成)
|
|
91
|
-
2. **用户级配置**:`~/.newapi-chat-config.json`
|
|
99
|
+
配置文件优先级:**项目级** > **全局级**。
|
|
92
100
|
|
|
93
|
-
|
|
101
|
+
**`.newapi-chat-config.json` 示例:**
|
|
94
102
|
```json
|
|
95
103
|
{
|
|
96
|
-
"apiKey": "sk-
|
|
104
|
+
"apiKey": "sk-your-api-key",
|
|
97
105
|
"baseUrl": "https://api.newapi.pro/v1",
|
|
98
106
|
"model": "claude-3-5-sonnet",
|
|
107
|
+
"systemPrompt": "你是一个资深的全栈工程师...",
|
|
99
108
|
"profiles": {
|
|
100
109
|
"default": { "model": "claude-3-5-sonnet" },
|
|
101
|
-
"gpt4": { "model": "gpt-4", "apiKey": "sk-special..." }
|
|
110
|
+
"gpt4": { "model": "gpt-4", "apiKey": "sk-special-key..." },
|
|
111
|
+
"local": { "baseUrl": "http://localhost:11434/v1", "model": "llama3" }
|
|
112
|
+
},
|
|
113
|
+
"mcpServers": {
|
|
114
|
+
"filesystem": {
|
|
115
|
+
"command": "npx",
|
|
116
|
+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
|
|
117
|
+
}
|
|
102
118
|
}
|
|
103
119
|
}
|
|
104
120
|
```
|
|
105
121
|
|
|
106
|
-
>
|
|
122
|
+
> **安全提示**:请务必将 `.newapi-chat-config.json` 添加到 `.gitignore` 中,防止密钥泄露。
|
|
107
123
|
|
|
108
124
|
---
|
|
109
125
|
|
|
110
126
|
## 🛠️ 本地开发 (Development)
|
|
111
127
|
|
|
112
|
-
### 依赖安装
|
|
113
128
|
```bash
|
|
129
|
+
# 安装依赖
|
|
114
130
|
npm install
|
|
115
|
-
```
|
|
116
131
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
132
|
+
# 链接到全局进行测试
|
|
133
|
+
npm link
|
|
134
|
+
xiaozhou-chat
|
|
135
|
+
|
|
136
|
+
# 构建可执行文件
|
|
137
|
+
npm run build:mac # or build:win
|
|
138
|
+
```
|
|
120
139
|
|
|
121
140
|
---
|
|
122
141
|
|
package/bin/cli.js
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
import fs from "node:fs";
|
|
38
38
|
import path from "node:path";
|
|
39
39
|
import readline from "node:readline";
|
|
40
|
+
import { execSync } from "node:child_process";
|
|
40
41
|
import minimist from "minimist";
|
|
41
42
|
import { MCPClient } from "../lib/mcp-lite.js";
|
|
42
43
|
import {
|
|
@@ -54,7 +55,8 @@ import {
|
|
|
54
55
|
exportHistory
|
|
55
56
|
} from "../lib/history.js";
|
|
56
57
|
import {
|
|
57
|
-
chatStream
|
|
58
|
+
chatStream,
|
|
59
|
+
generateCompletion
|
|
58
60
|
} from "../lib/chat.js";
|
|
59
61
|
import {
|
|
60
62
|
builtInTools,
|
|
@@ -431,36 +433,71 @@ rl.on("line", async (line) => {
|
|
|
431
433
|
console.log("⚠️ 历史记录太短,无需压缩。");
|
|
432
434
|
return rl.prompt();
|
|
433
435
|
}
|
|
434
|
-
|
|
435
|
-
// To simplify: we construct a temporary context just for summary
|
|
436
|
+
|
|
436
437
|
const toCompress = messages.slice(0, -2);
|
|
437
438
|
const recent = messages.slice(-2);
|
|
438
439
|
|
|
439
440
|
const summaryPrompt = `
|
|
440
441
|
请总结以下对话的主要内容,提取关键信息、代码片段和决策。
|
|
441
442
|
摘要应简洁明了,以便作为后续对话的上下文。
|
|
443
|
+
保留所有重要的技术细节。
|
|
442
444
|
|
|
443
445
|
对话内容:
|
|
444
446
|
${JSON.stringify(toCompress)}
|
|
445
447
|
`;
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
448
|
+
try {
|
|
449
|
+
const summary = await generateCompletion(activeConfig, [{role: "user", content: summaryPrompt}]);
|
|
450
|
+
messages = [
|
|
451
|
+
{ role: "system", content: `Previous conversation summary:\n${summary}` },
|
|
452
|
+
...recent
|
|
453
|
+
];
|
|
454
|
+
saveHistory(messages);
|
|
455
|
+
console.log("✅ 历史记录已压缩");
|
|
456
|
+
console.log("摘要预览:", summary.slice(0, 100).replace(/\n/g, ' ') + "...");
|
|
457
|
+
} catch (e) {
|
|
458
|
+
console.error("❌ 压缩失败:", e.message);
|
|
459
|
+
}
|
|
460
|
+
return rl.prompt();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (input === "/commit") {
|
|
464
|
+
try {
|
|
465
|
+
let diff;
|
|
466
|
+
try {
|
|
467
|
+
diff = execSync("git diff --cached", { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
468
|
+
} catch (e) {
|
|
469
|
+
console.log("❌ 获取 git diff 失败,请确认当前目录是 git 仓库");
|
|
470
|
+
return rl.prompt();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (!diff) {
|
|
474
|
+
console.log("⚠️ 暂存区为空,请先执行 'git add <file>'");
|
|
475
|
+
return rl.prompt();
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
console.log("🤖 正在分析 Diff 并生成 Commit Message...");
|
|
479
|
+
const prompt = `
|
|
480
|
+
你是一个资深的开发者。请根据以下的 Git Diff 内容,生成一个符合 Conventional Commits 规范的 Commit Message。
|
|
481
|
+
只返回 Commit Message 本身,不要包含 markdown 代码块或其他解释。
|
|
482
|
+
|
|
483
|
+
Diff 内容:
|
|
484
|
+
${diff.slice(0, 8000)}
|
|
485
|
+
`;
|
|
486
|
+
const msg = await generateCompletion(activeConfig, [{role: "user", content: prompt}]);
|
|
487
|
+
console.log("\n----- 建议的 Commit Message -----");
|
|
488
|
+
console.log(`\x1b[32m${msg.trim()}\x1b[0m`);
|
|
489
|
+
console.log("-----------------------------------");
|
|
490
|
+
|
|
491
|
+
const ans = await askQuestion("提交吗? (y/n) ");
|
|
492
|
+
if (ans.trim().toLowerCase() === 'y') {
|
|
493
|
+
execSync(`git commit -m "${msg.trim().replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
|
|
494
|
+
console.log("✅ 提交成功");
|
|
495
|
+
} else {
|
|
496
|
+
console.log("🚫 已取消");
|
|
497
|
+
}
|
|
498
|
+
} catch (e) {
|
|
499
|
+
console.error("❌ Commit 生成失败:", e.message);
|
|
500
|
+
}
|
|
464
501
|
return rl.prompt();
|
|
465
502
|
}
|
|
466
503
|
|
package/lib/chat.js
CHANGED
|
@@ -105,6 +105,44 @@ export async function requestWithRetry(url, options, maxRetries = 3) {
|
|
|
105
105
|
throw lastError;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
export async function generateCompletion(config, messages, options = {}) {
|
|
109
|
+
const {
|
|
110
|
+
model = config.model,
|
|
111
|
+
max_tokens = 4096,
|
|
112
|
+
jsonMode = false
|
|
113
|
+
} = options;
|
|
114
|
+
|
|
115
|
+
const requestUrl = `${config.baseUrl}${config.baseUrl.endsWith('/') ? '' : '/'}chat/completions`;
|
|
116
|
+
|
|
117
|
+
// 自动修正 v1
|
|
118
|
+
const finalUrl = (requestUrl.includes("/v1/") || requestUrl.endsWith("/v1"))
|
|
119
|
+
? requestUrl
|
|
120
|
+
: requestUrl.replace("/chat/completions", "/v1/chat/completions");
|
|
121
|
+
|
|
122
|
+
const body = {
|
|
123
|
+
model,
|
|
124
|
+
messages,
|
|
125
|
+
stream: false,
|
|
126
|
+
max_tokens
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (jsonMode) {
|
|
130
|
+
body.response_format = { type: "json_object" };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const res = await requestWithRetry(finalUrl, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: {
|
|
136
|
+
"Content-Type": "application/json",
|
|
137
|
+
Authorization: `Bearer ${config.apiKey}`
|
|
138
|
+
},
|
|
139
|
+
body: JSON.stringify(body)
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const data = await res.json();
|
|
143
|
+
return data.choices?.[0]?.message?.content || "";
|
|
144
|
+
}
|
|
145
|
+
|
|
108
146
|
export async function chatStream(context, userInput = null, options = {}) {
|
|
109
147
|
const {
|
|
110
148
|
messages,
|