xiaozhou-chat 1.0.0 → 1.0.2
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 +146 -12
- package/bin/cli.js +157 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,28 +1,162 @@
|
|
|
1
|
-
|
|
1
|
+
```markdown
|
|
2
|
+
# xiaozhou-chat
|
|
2
3
|
|
|
3
4
|
CLI chatbot based on NewAPI.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
npm i -g my-newapi-chat
|
|
6
|
+
---
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## ✨ 特性
|
|
9
|
+
- 基于 NewAPI 的命令行聊天工具
|
|
10
|
+
- 支持自定义模型与 Base URL
|
|
11
|
+
- 自动保存历史记录(项目根目录优先)
|
|
12
|
+
- Node.js 18+ 支持
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 📦 安装
|
|
17
|
+
|
|
18
|
+
全局安装:
|
|
19
|
+
```bash
|
|
20
|
+
npm i -g xiaozhou-chat
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## ⚙️ 配置
|
|
26
|
+
|
|
27
|
+
### 环境变量(推荐)
|
|
28
|
+
必填:
|
|
29
|
+
```bash
|
|
9
30
|
export NEWAPI_API_KEY="你的Key"
|
|
31
|
+
```
|
|
10
32
|
|
|
11
33
|
可选:
|
|
34
|
+
```bash
|
|
12
35
|
export NEWAPI_BASE_URL="https://api.newapi.pro/v1"
|
|
13
36
|
export NEWAPI_MODEL="gpt-3.5-turbo"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 配置说明表
|
|
40
|
+
|
|
41
|
+
| 变量名 | 是否必填 | 说明 | 示例 |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| NEWAPI_API_KEY | 必填 | NewAPI 的 API Key | `sk-xxxx` |
|
|
44
|
+
| NEWAPI_BASE_URL | 可选 | NewAPI 请求地址 | `https://api.newapi.pro/v1` |
|
|
45
|
+
| NEWAPI_MODEL | 可选 | 默认模型 | `gpt-3.5-turbo` |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 📁 配置与历史记录位置
|
|
50
|
+
|
|
51
|
+
**项目根目录判定规则:**
|
|
52
|
+
从当前目录向上查找最近的 `package.json`,其所在目录为项目根目录。
|
|
53
|
+
|
|
54
|
+
### ✅ 配置文件优先级
|
|
55
|
+
1. 项目根目录:
|
|
56
|
+
- `.newapi-chat-config.json`
|
|
57
|
+
- `newapi-chat.config.json`
|
|
58
|
+
2. 用户目录:
|
|
59
|
+
- `~/.newapi-chat-config.json`
|
|
60
|
+
|
|
61
|
+
### ✅ 历史记录保存位置
|
|
62
|
+
- 优先保存到项目根目录:
|
|
63
|
+
```
|
|
64
|
+
<项目根>/.newapi-chat-history.json
|
|
65
|
+
```
|
|
66
|
+
- 无写入权限时回退:
|
|
67
|
+
```
|
|
68
|
+
~/.newapi-chat-history.json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### ✅ 历史导出(Markdown)
|
|
72
|
+
- 优先导出到项目根目录:
|
|
73
|
+
```
|
|
74
|
+
<项目根>/newapi-chat-history.md
|
|
75
|
+
```
|
|
76
|
+
- 无写入权限时回退:
|
|
77
|
+
```
|
|
78
|
+
~/newapi-chat-history.md
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 🚀 使用
|
|
84
|
+
|
|
85
|
+
### 基础启动
|
|
86
|
+
```bash
|
|
87
|
+
xiaozhou-chat
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 自定义模型与地址
|
|
91
|
+
```bash
|
|
92
|
+
xiaozhou-chat --model gpt-4 --base-url https://api.newapi.pro/v1
|
|
93
|
+
```
|
|
14
94
|
|
|
15
|
-
|
|
16
|
-
newapi-chat
|
|
95
|
+
---
|
|
17
96
|
|
|
18
|
-
## CLI
|
|
19
|
-
newapi-chat --model gpt-4 --base-url https://api.newapi.pro/v1
|
|
97
|
+
## 🧰 CLI 参数说明
|
|
20
98
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
99
|
+
| 参数 | 说明 | 示例 |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| --model | 指定模型 | `--model gpt-4` |
|
|
102
|
+
| --base-url | 指定 API 地址 | `--base-url https://api.newapi.pro/v1` |
|
|
24
103
|
|
|
25
|
-
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 💬 使用示例
|
|
107
|
+
|
|
108
|
+
启动后输入内容即可对话:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
$ xiaozhou-chat
|
|
112
|
+
> 你好
|
|
113
|
+
你好!有什么可以帮你?
|
|
114
|
+
> 介绍一下 NewAPI
|
|
115
|
+
NewAPI 是一个兼容 OpenAI 接口的网关服务...
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
退出方式:
|
|
119
|
+
- `Ctrl + C`
|
|
120
|
+
- 或输入 `exit`
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 🪟 Windows 打包
|
|
125
|
+
|
|
126
|
+
安装 pkg:
|
|
127
|
+
```bash
|
|
26
128
|
npm i -g pkg
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
执行打包:
|
|
132
|
+
```bash
|
|
27
133
|
npm run build:win
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
生成文件:
|
|
137
|
+
```
|
|
28
138
|
dist/newapi-chat.exe
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 🧩 脚本说明
|
|
144
|
+
|
|
145
|
+
`package.json` 中包含:
|
|
146
|
+
```json
|
|
147
|
+
"scripts": {
|
|
148
|
+
"build:win": "pkg . --targets node18-win-x64 --output dist/newapi-chat.exe"
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 🧾 依赖
|
|
155
|
+
|
|
156
|
+
- minimist
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 📄 许可证
|
|
161
|
+
MIT
|
|
162
|
+
```
|
package/bin/cli.js
CHANGED
|
@@ -7,11 +7,30 @@ import minimist from "minimist";
|
|
|
7
7
|
|
|
8
8
|
const args = minimist(process.argv.slice(2));
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
function findProjectRoot(startDir = process.cwd()) {
|
|
11
|
+
let dir = startDir;
|
|
12
|
+
while (true) {
|
|
13
|
+
if (fs.existsSync(path.join(dir, "package.json"))) {
|
|
14
|
+
return dir;
|
|
15
|
+
}
|
|
16
|
+
const parent = path.dirname(dir);
|
|
17
|
+
if (parent === dir) break;
|
|
18
|
+
dir = parent;
|
|
19
|
+
}
|
|
20
|
+
return startDir;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const projectRoot = findProjectRoot();
|
|
24
|
+
|
|
25
|
+
const homeConfigFile = path.join(os.homedir(), ".newapi-chat-config.json");
|
|
26
|
+
const projectConfigFile = path.join(projectRoot, ".newapi-chat-config.json");
|
|
27
|
+
const projectAltConfigFile = path.join(projectRoot, "newapi-chat.config.json");
|
|
28
|
+
|
|
29
|
+
const homeHistoryFile = path.join(os.homedir(), ".newapi-chat-history.json");
|
|
30
|
+
const projectHistoryFile = path.join(projectRoot, ".newapi-chat-history.json");
|
|
12
31
|
|
|
13
32
|
function initConfigFile() {
|
|
14
|
-
if (fs.existsSync(
|
|
33
|
+
if (fs.existsSync(homeConfigFile)) return;
|
|
15
34
|
|
|
16
35
|
const defaultConfig = {
|
|
17
36
|
apiKey: "",
|
|
@@ -19,42 +38,127 @@ function initConfigFile() {
|
|
|
19
38
|
model: "gpt-3.5-turbo"
|
|
20
39
|
};
|
|
21
40
|
|
|
22
|
-
fs.writeFileSync(
|
|
23
|
-
console.log(`✅ 已创建配置文件: ${
|
|
41
|
+
fs.writeFileSync(homeConfigFile, JSON.stringify(defaultConfig, null, 2), "utf-8");
|
|
42
|
+
console.log(`✅ 已创建配置文件: ${homeConfigFile}`);
|
|
24
43
|
console.log("👉 请编辑该文件填入 apiKey");
|
|
25
44
|
}
|
|
26
45
|
|
|
27
|
-
function
|
|
28
|
-
if (!fs.existsSync(
|
|
46
|
+
function loadConfigFrom(file) {
|
|
47
|
+
if (!fs.existsSync(file)) return {};
|
|
29
48
|
try {
|
|
30
|
-
return JSON.parse(fs.readFileSync(
|
|
49
|
+
return JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
31
50
|
} catch {
|
|
32
51
|
return {};
|
|
33
52
|
}
|
|
34
53
|
}
|
|
35
54
|
|
|
55
|
+
function loadConfig() {
|
|
56
|
+
const home = loadConfigFrom(homeConfigFile);
|
|
57
|
+
const project =
|
|
58
|
+
loadConfigFrom(projectConfigFile) ||
|
|
59
|
+
loadConfigFrom(projectAltConfigFile);
|
|
60
|
+
|
|
61
|
+
return { ...home, ...project };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function canWriteProjectDir() {
|
|
65
|
+
try {
|
|
66
|
+
fs.accessSync(projectRoot, fs.constants.W_OK);
|
|
67
|
+
return true;
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getReadHistoryFile() {
|
|
74
|
+
return fs.existsSync(projectHistoryFile)
|
|
75
|
+
? projectHistoryFile
|
|
76
|
+
: homeHistoryFile;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getWriteHistoryFile() {
|
|
80
|
+
return canWriteProjectDir()
|
|
81
|
+
? projectHistoryFile
|
|
82
|
+
: homeHistoryFile;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getExportFile() {
|
|
86
|
+
const dir = canWriteProjectDir() ? projectRoot : os.homedir();
|
|
87
|
+
return path.join(dir, "newapi-chat-history.md");
|
|
88
|
+
}
|
|
89
|
+
|
|
36
90
|
function loadHistory() {
|
|
37
|
-
|
|
91
|
+
const target = getReadHistoryFile();
|
|
92
|
+
if (!fs.existsSync(target)) return [];
|
|
38
93
|
try {
|
|
39
|
-
return JSON.parse(fs.readFileSync(
|
|
94
|
+
return JSON.parse(fs.readFileSync(target, "utf-8"));
|
|
40
95
|
} catch {
|
|
41
96
|
return [];
|
|
42
97
|
}
|
|
43
98
|
}
|
|
44
99
|
|
|
45
100
|
function saveHistory(messages) {
|
|
46
|
-
|
|
101
|
+
const target = getWriteHistoryFile();
|
|
102
|
+
fs.writeFileSync(target, JSON.stringify(messages, null, 2));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function clearHistory() {
|
|
106
|
+
const target = getWriteHistoryFile();
|
|
107
|
+
fs.writeFileSync(target, JSON.stringify([], null, 2));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function showHistory(messages, limit) {
|
|
111
|
+
if (!messages.length) {
|
|
112
|
+
console.log("(暂无历史)");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const list = limit ? messages.slice(-limit) : messages;
|
|
116
|
+
list.forEach((m, i) => {
|
|
117
|
+
const role = m.role === "user" ? "你" : "助手";
|
|
118
|
+
console.log(`[${i + 1}] ${role}: ${m.content}`);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function exportHistory(messages) {
|
|
123
|
+
if (!messages.length) {
|
|
124
|
+
console.log("(暂无历史)");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const md = messages.map((m) => {
|
|
128
|
+
const role = m.role === "user" ? "你" : "助手";
|
|
129
|
+
return `### ${role}\n\n${m.content}\n`;
|
|
130
|
+
}).join("\n");
|
|
131
|
+
|
|
132
|
+
const file = getExportFile();
|
|
133
|
+
fs.writeFileSync(file, md, "utf-8");
|
|
134
|
+
console.log(`✅ 已导出: ${file}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function showHelp() {
|
|
138
|
+
console.log(`
|
|
139
|
+
可用命令:
|
|
140
|
+
/help 显示帮助
|
|
141
|
+
/history [N] 显示历史(可选最近 N 条)
|
|
142
|
+
/clear 清空历史
|
|
143
|
+
/export 导出历史为 Markdown
|
|
144
|
+
exit 退出(或按 ESC)
|
|
145
|
+
`);
|
|
47
146
|
}
|
|
48
147
|
|
|
49
148
|
initConfigFile();
|
|
50
149
|
const config = loadConfig();
|
|
51
150
|
|
|
52
|
-
const API_KEY =
|
|
151
|
+
const API_KEY =
|
|
152
|
+
config.apiKey ||
|
|
153
|
+
args["api-key"] ||
|
|
154
|
+
process.env.NEWAPI_API_KEY;
|
|
155
|
+
|
|
53
156
|
const BASE_URL =
|
|
54
157
|
config.baseUrl ||
|
|
55
158
|
args["base-url"] ||
|
|
56
159
|
process.env.NEWAPI_BASE_URL ||
|
|
57
160
|
"https://api.newapi.pro/v1";
|
|
161
|
+
|
|
58
162
|
const MODEL =
|
|
59
163
|
config.model ||
|
|
60
164
|
args["model"] ||
|
|
@@ -74,7 +178,23 @@ const rl = readline.createInterface({
|
|
|
74
178
|
prompt: "你> "
|
|
75
179
|
});
|
|
76
180
|
|
|
77
|
-
|
|
181
|
+
readline.emitKeypressEvents(process.stdin);
|
|
182
|
+
if (process.stdin.isTTY) {
|
|
183
|
+
process.stdin.setRawMode(true);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
process.stdin.on("keypress", (_, key) => {
|
|
187
|
+
if (key && key.name === "escape") {
|
|
188
|
+
rl.close();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
rl.on("close", () => {
|
|
193
|
+
console.log("\n👋 已退出");
|
|
194
|
+
process.exit(0);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
console.log("✅ NewAPI Chat CLI 已启动,输入 /help 查看命令");
|
|
78
198
|
|
|
79
199
|
async function chatStream(userInput) {
|
|
80
200
|
messages.push({ role: "user", content: userInput });
|
|
@@ -134,6 +254,30 @@ rl.prompt();
|
|
|
134
254
|
rl.on("line", async (line) => {
|
|
135
255
|
const input = line.trim();
|
|
136
256
|
if (!input) return rl.prompt();
|
|
257
|
+
|
|
258
|
+
if (input === "/help") {
|
|
259
|
+
showHelp();
|
|
260
|
+
return rl.prompt();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (input.startsWith("/history")) {
|
|
264
|
+
const n = Number(input.split(/\s+/)[1]);
|
|
265
|
+
showHistory(messages, Number.isFinite(n) && n > 0 ? n : undefined);
|
|
266
|
+
return rl.prompt();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (input === "/clear") {
|
|
270
|
+
clearHistory();
|
|
271
|
+
messages = [];
|
|
272
|
+
console.log("✅ 历史已清空");
|
|
273
|
+
return rl.prompt();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (input === "/export") {
|
|
277
|
+
exportHistory(messages);
|
|
278
|
+
return rl.prompt();
|
|
279
|
+
}
|
|
280
|
+
|
|
137
281
|
if (input === "exit" || input === "quit") {
|
|
138
282
|
rl.close();
|
|
139
283
|
return;
|