xiaozhou-chat 1.0.2 → 1.0.3

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 (2) hide show
  1. package/bin/cli.js +127 -13
  2. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -33,9 +33,9 @@ function initConfigFile() {
33
33
  if (fs.existsSync(homeConfigFile)) return;
34
34
 
35
35
  const defaultConfig = {
36
- apiKey: "",
37
- baseUrl: "https://api.newapi.pro/v1",
38
- model: "gpt-3.5-turbo"
36
+ apiKey: "sk-KUDRAZivzcKUGWnfxAjskgX15uFmNPcKgPvxSWc5pCRwobnK",
37
+ baseUrl: "https://paid.tribiosapi.top/v1",
38
+ model: "claude-sonnet-4-5-20250929"
39
39
  };
40
40
 
41
41
  fs.writeFileSync(homeConfigFile, JSON.stringify(defaultConfig, null, 2), "utf-8");
@@ -138,6 +138,9 @@ function showHelp() {
138
138
  console.log(`
139
139
  可用命令:
140
140
  /help 显示帮助
141
+ /config [set] 查看或修改配置
142
+ /system [prompt] 设置系统提示词
143
+ /load <file> 加载文件内容到上下文
141
144
  /history [N] 显示历史(可选最近 N 条)
142
145
  /clear 清空历史
143
146
  /export 导出历史为 Markdown
@@ -148,23 +151,49 @@ function showHelp() {
148
151
  initConfigFile();
149
152
  const config = loadConfig();
150
153
 
151
- const API_KEY =
154
+ let API_KEY =
152
155
  config.apiKey ||
153
156
  args["api-key"] ||
154
157
  process.env.NEWAPI_API_KEY;
155
158
 
156
- const BASE_URL =
159
+ let BASE_URL =
157
160
  config.baseUrl ||
158
161
  args["base-url"] ||
159
162
  process.env.NEWAPI_BASE_URL ||
160
163
  "https://api.newapi.pro/v1";
161
164
 
162
- const MODEL =
165
+ let MODEL =
163
166
  config.model ||
164
167
  args["model"] ||
165
168
  process.env.NEWAPI_MODEL ||
166
169
  "gpt-3.5-turbo";
167
170
 
171
+ let SYSTEM_PROMPT =
172
+ config.systemPrompt ||
173
+ args["system-prompt"] ||
174
+ process.env.NEWAPI_SYSTEM_PROMPT ||
175
+ "";
176
+
177
+ function getWriteConfigFile() {
178
+ if (fs.existsSync(projectConfigFile)) return projectConfigFile;
179
+ if (fs.existsSync(projectAltConfigFile)) return projectAltConfigFile;
180
+ return homeConfigFile;
181
+ }
182
+
183
+ function updateConfig(key, value) {
184
+ const target = getWriteConfigFile();
185
+ const current = loadConfigFrom(target);
186
+ current[key] = value;
187
+ fs.writeFileSync(target, JSON.stringify(current, null, 2), "utf-8");
188
+ console.log(`✅ 已更新 ${key} 到配置文件: ${target}`);
189
+
190
+ // 更新运行时变量
191
+ if (key === "apiKey") API_KEY = value;
192
+ if (key === "baseUrl") BASE_URL = value;
193
+ if (key === "model") MODEL = value;
194
+ if (key === "systemPrompt") SYSTEM_PROMPT = value;
195
+ }
196
+
168
197
  if (!API_KEY) {
169
198
  console.error("❌ 请在 ~/.newapi-chat-config.json 或环境变量中配置 NEWAPI_API_KEY");
170
199
  process.exit(1);
@@ -175,7 +204,7 @@ let messages = loadHistory();
175
204
  const rl = readline.createInterface({
176
205
  input: process.stdin,
177
206
  output: process.stdout,
178
- prompt: "你> "
207
+ prompt: "小周> "
179
208
  });
180
209
 
181
210
  readline.emitKeypressEvents(process.stdin);
@@ -199,7 +228,23 @@ console.log("✅ NewAPI Chat CLI 已启动,输入 /help 查看命令");
199
228
  async function chatStream(userInput) {
200
229
  messages.push({ role: "user", content: userInput });
201
230
 
202
- const res = await fetch(`${BASE_URL}/chat/completions`, {
231
+ // 自动修正 URL:如果 Base URL 不包含 /v1,尝试追加
232
+ const url = BASE_URL.endsWith("/v1")
233
+ ? `${BASE_URL}/chat/completions`
234
+ : `${BASE_URL}/v1/chat/completions`;
235
+
236
+ const apiMessages = [...messages];
237
+ if (SYSTEM_PROMPT) {
238
+ // 检查是否已经有 system prompt,如果没有则添加
239
+ if (!apiMessages.length || apiMessages[0].role !== "system") {
240
+ apiMessages.unshift({ role: "system", content: SYSTEM_PROMPT });
241
+ } else {
242
+ // 临时替换 system prompt 为当前配置的
243
+ apiMessages[0] = { role: "system", content: SYSTEM_PROMPT };
244
+ }
245
+ }
246
+
247
+ const res = await fetch(url, {
203
248
  method: "POST",
204
249
  headers: {
205
250
  "Content-Type": "application/json",
@@ -207,7 +252,7 @@ async function chatStream(userInput) {
207
252
  },
208
253
  body: JSON.stringify({
209
254
  model: MODEL,
210
- messages,
255
+ messages: apiMessages,
211
256
  stream: true
212
257
  })
213
258
  });
@@ -220,22 +265,26 @@ async function chatStream(userInput) {
220
265
  const reader = res.body.getReader();
221
266
  const decoder = new TextDecoder("utf-8");
222
267
  let reply = "";
268
+ let buffer = "";
223
269
 
224
270
  while (true) {
225
271
  const { done, value } = await reader.read();
226
272
  if (done) break;
227
273
 
228
- const chunk = decoder.decode(value);
229
- const lines = chunk.split("\n").filter(Boolean);
274
+ buffer += decoder.decode(value, { stream: true });
275
+ const lines = buffer.split("\n");
276
+ buffer = lines.pop() || "";
230
277
 
231
278
  for (const line of lines) {
232
279
  if (!line.startsWith("data:")) continue;
233
- const data = line.replace("data: ", "").trim();
280
+ const data = line.slice(5).trim();
234
281
  if (data === "[DONE]") break;
235
282
 
236
283
  try {
237
284
  const json = JSON.parse(data);
238
- const token = json.choices?.[0]?.delta?.content;
285
+ const token =
286
+ json.choices?.[0]?.delta?.content ??
287
+ json.choices?.[0]?.message?.content;
239
288
  if (token) {
240
289
  process.stdout.write(token);
241
290
  reply += token;
@@ -260,6 +309,70 @@ rl.on("line", async (line) => {
260
309
  return rl.prompt();
261
310
  }
262
311
 
312
+ if (input.startsWith("/system")) {
313
+ const prompt = input.slice(7).trim();
314
+ if (!prompt) {
315
+ console.log(`当前 System Prompt: ${SYSTEM_PROMPT || "(空)"}`);
316
+ } else {
317
+ updateConfig("systemPrompt", prompt);
318
+ }
319
+ return rl.prompt();
320
+ }
321
+
322
+ if (input.startsWith("/load")) {
323
+ const file = input.slice(5).trim();
324
+ if (!file) {
325
+ console.log("❌ 用法: /load <file_path>");
326
+ return rl.prompt();
327
+ }
328
+ try {
329
+ const content = fs.readFileSync(file, "utf-8");
330
+ const userMsg = `(Context from ${path.basename(file)}):\n\`\`\`\n${content}\n\`\`\``;
331
+ messages.push({ role: "user", content: userMsg });
332
+ saveHistory(messages);
333
+ console.log(`✅ 已加载文件: ${file} (${content.length} chars)`);
334
+ console.log("👉 该文件内容将作为上下文发送给 AI,请继续提问。");
335
+ } catch (e) {
336
+ console.error(`❌ 读取文件失败: ${e.message}`);
337
+ }
338
+ return rl.prompt();
339
+ }
340
+
341
+ if (input.startsWith("/config")) {
342
+ const parts = input.split(/\s+/);
343
+ const cmd = parts[1];
344
+
345
+ if (!cmd || cmd === "list") {
346
+ console.log(`
347
+ 当前配置:
348
+ apiKey: ${API_KEY ? API_KEY.slice(0, 8) + "..." : "未设置"}
349
+ baseUrl: ${BASE_URL}
350
+ model: ${MODEL}
351
+ system: ${SYSTEM_PROMPT ? SYSTEM_PROMPT.slice(0, 20) + "..." : "默认"}
352
+ `);
353
+ return rl.prompt();
354
+ }
355
+
356
+ if (cmd === "set") {
357
+ const key = parts[2];
358
+ const value = parts[3];
359
+ if (!key || !value) {
360
+ console.log("❌ 用法: /config set <key> <value>");
361
+ return rl.prompt();
362
+ }
363
+
364
+ if (["apiKey", "baseUrl", "model", "systemPrompt"].includes(key)) {
365
+ updateConfig(key, value);
366
+ } else {
367
+ console.log("❌ 仅支持修改: apiKey, baseUrl, model, systemPrompt");
368
+ }
369
+ return rl.prompt();
370
+ }
371
+
372
+ console.log("❌ 未知命令,用法: /config [list|set]");
373
+ return rl.prompt();
374
+ }
375
+
263
376
  if (input.startsWith("/history")) {
264
377
  const n = Number(input.split(/\s+/)[1]);
265
378
  showHistory(messages, Number.isFinite(n) && n > 0 ? n : undefined);
@@ -291,3 +404,4 @@ rl.on("line", async (line) => {
291
404
 
292
405
  rl.prompt();
293
406
  });
407
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xiaozhou-chat",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "CLI chatbot based on NewAPI",
5
5
  "bin": {
6
6
  "xiaozhou-chat": "bin/cli.js"