xiaozhou-chat 1.0.1 → 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.
Files changed (3) hide show
  1. package/README.md +36 -11
  2. package/bin/cli.js +157 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
-
1
+ ```markdown
2
2
  # xiaozhou-chat
3
3
 
4
4
  CLI chatbot based on NewAPI.
@@ -8,7 +8,7 @@ CLI chatbot based on NewAPI.
8
8
  ## ✨ 特性
9
9
  - 基于 NewAPI 的命令行聊天工具
10
10
  - 支持自定义模型与 Base URL
11
- - 自动保存历史记录
11
+ - 自动保存历史记录(项目根目录优先)
12
12
  - Node.js 18+ 支持
13
13
 
14
14
  ---
@@ -46,6 +46,40 @@ export NEWAPI_MODEL="gpt-3.5-turbo"
46
46
 
47
47
  ---
48
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
+
49
83
  ## 🚀 使用
50
84
 
51
85
  ### 基础启动
@@ -87,15 +121,6 @@ NewAPI 是一个兼容 OpenAI 接口的网关服务...
87
121
 
88
122
  ---
89
123
 
90
- ## 📝 历史记录
91
-
92
- 默认保存在:
93
- ```
94
- ~/.newapi-chat-history.json
95
- ```
96
-
97
- ---
98
-
99
124
  ## 🪟 Windows 打包
100
125
 
101
126
  安装 pkg:
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
- const configFile = path.join(os.homedir(), ".newapi-chat-config.json");
11
- const historyFile = path.join(os.homedir(), ".newapi-chat-history.json");
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(configFile)) return;
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(configFile, JSON.stringify(defaultConfig, null, 2), "utf-8");
23
- console.log(`✅ 已创建配置文件: ${configFile}`);
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 loadConfig() {
28
- if (!fs.existsSync(configFile)) return {};
46
+ function loadConfigFrom(file) {
47
+ if (!fs.existsSync(file)) return {};
29
48
  try {
30
- return JSON.parse(fs.readFileSync(configFile, "utf-8"));
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
- if (!fs.existsSync(historyFile)) return [];
91
+ const target = getReadHistoryFile();
92
+ if (!fs.existsSync(target)) return [];
38
93
  try {
39
- return JSON.parse(fs.readFileSync(historyFile, "utf-8"));
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
- fs.writeFileSync(historyFile, JSON.stringify(messages, null, 2));
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 = config.apiKey || process.env.NEWAPI_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
- console.log("✅ NewAPI Chat CLI 已启动,输入 exit 退出");
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xiaozhou-chat",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "CLI chatbot based on NewAPI",
5
5
  "bin": {
6
6
  "xiaozhou-chat": "bin/cli.js"