xiaozhou-chat 1.0.30 → 1.0.32

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
@@ -46,7 +46,9 @@ import {
46
46
  loadConfig,
47
47
  updateConfig,
48
48
  getActiveConfig,
49
- setProfileValue
49
+ setProfileValue,
50
+ setRole,
51
+ getRoles
50
52
  } from "../lib/config.js";
51
53
  import {
52
54
  loadHistory,
@@ -342,6 +344,9 @@ rl.on("line", async (line) => {
342
344
  - \`/commit\`: 生成 Git Commit Message
343
345
  - \`/compress\`: 压缩对话历史
344
346
  - \`/token\`: 估算 Token 消耗
347
+ - `/copy`: 复制上一次 AI 回复的代码或内容
348
+ - `/editor`: 调用系统编辑器输入多行内容
349
+ - `/role`: 切换或管理角色预设 (别名: `/角色`)
345
350
  `);
346
351
  return rl.prompt();
347
352
  }
package/lib/config.js CHANGED
@@ -37,7 +37,12 @@ export function initConfigFile() {
37
37
  model: "claude-sonnet-4-5-20250929"
38
38
  }
39
39
  },
40
- currentProfile: "default"
40
+ currentProfile: "default",
41
+ roles: {
42
+ coder: "你是一个资深的全栈工程师,精通 Node.js, React, TypeScript。请编写高质量、可维护的代码。",
43
+ writer: "你是一个专业的文案创作者,擅长清晰、有说服力的表达。",
44
+ translator: "你是一个精通中英文的翻译专家,请提供信达雅的翻译结果。"
45
+ }
41
46
  };
42
47
 
43
48
  fs.writeFileSync(homeConfigFile, JSON.stringify(defaultConfig, null, 2), "utf-8");
@@ -65,7 +70,12 @@ export function initProjectConfigFile() {
65
70
  model: "claude-sonnet-4-5-20250929"
66
71
  }
67
72
  },
68
- currentProfile: "default"
73
+ currentProfile: "default",
74
+ roles: {
75
+ coder: "你是一个资深的全栈工程师,精通 Node.js, React, TypeScript。请编写高质量、可维护的代码。",
76
+ writer: "你是一个专业的文案创作者,擅长清晰、有说服力的表达。",
77
+ translator: "你是一个精通中英文的翻译专家,请提供信达雅的翻译结果。"
78
+ }
69
79
  };
70
80
 
71
81
  fs.writeFileSync(targetFile, JSON.stringify(defaultConfig, null, 2), "utf-8");
@@ -184,6 +194,21 @@ export function setProfileValue(profileName, key, value) {
184
194
  return current;
185
195
  }
186
196
 
197
+ export function setRole(name, prompt) {
198
+ const target = getWriteConfigFile();
199
+ let current = loadConfigFrom(target);
200
+
201
+ if (!current.roles) current.roles = {};
202
+ current.roles[name] = prompt;
203
+
204
+ fs.writeFileSync(target, JSON.stringify(current, null, 2), "utf-8");
205
+ return current;
206
+ }
207
+
208
+ export function getRoles(config) {
209
+ return config.roles || {};
210
+ }
211
+
187
212
  export function getActiveConfig(config) {
188
213
  const profileName = config.currentProfile || "default";
189
214
  const profile = config.profiles?.[profileName] || {};
package/lib/tools.js CHANGED
@@ -55,12 +55,29 @@ export const builtInTools = [
55
55
  parameters: {
56
56
  type: "object",
57
57
  properties: {
58
- path: { type: "string", description: "文件的相对或绝对路径" }
58
+ path: { type: "string", description: "文件的相对或绝对路径" },
59
+ start_line: { type: "integer", description: "起始行号 (从1开始,可选)" },
60
+ end_line: { type: "integer", description: "结束行号 (可选)" }
59
61
  },
60
62
  required: ["path"]
61
63
  }
62
64
  }
63
65
  },
66
+ {
67
+ type: "function",
68
+ function: {
69
+ name: "search_files",
70
+ description: "在项目中搜索包含特定关键词的文件。返回匹配的文件路径和行号。",
71
+ parameters: {
72
+ type: "object",
73
+ properties: {
74
+ query: { type: "string", description: "搜索关键词 (不区分大小写)" },
75
+ path: { type: "string", description: "搜索目录,默认为当前目录" }
76
+ },
77
+ required: ["query"]
78
+ }
79
+ }
80
+ },
64
81
  {
65
82
  type: "function",
66
83
  function: {
@@ -112,16 +129,73 @@ const handlers = {
112
129
  if (!fs.existsSync(filepath)) return `Error: File not found: ${filepath}`;
113
130
 
114
131
  // 安全检查:防止读取敏感文件 (如 /etc/passwd 或 .env)
115
- // 简单实现:检查文件名
116
132
  if (path.basename(filepath) === ".env" || path.basename(filepath).includes("key")) {
117
133
  // Warning but allow if user explicitly asked? Better strict for now.
118
- // return `Error: Access to sensitive file ${args.path} is restricted.`;
119
134
  }
120
135
 
121
- const content = fs.readFileSync(filepath, "utf-8");
136
+ let content = fs.readFileSync(filepath, "utf-8");
137
+
138
+ if (args.start_line || args.end_line) {
139
+ const lines = content.split('\n');
140
+ const start = (args.start_line || 1) - 1;
141
+ const end = args.end_line || lines.length;
142
+ content = lines.slice(start, end).join('\n');
143
+ return `(File Content of ${args.path} lines ${start+1}-${end}):\n${content}`;
144
+ }
145
+
122
146
  return `(File Content of ${args.path}):\n${content}`;
123
147
  },
124
148
 
149
+ async search_files(args) {
150
+ const query = args.query.toLowerCase();
151
+ const dir = path.resolve(process.cwd(), args.path || ".");
152
+
153
+ // 使用 scanDir 的逻辑来遍历文件,但这里我们需要读取内容
154
+ // 为避免性能问题,我们限制搜索深度和文件大小
155
+
156
+ function searchRecursive(currentDir, depth = 0) {
157
+ if (depth > 5) return [];
158
+ let results = [];
159
+ try {
160
+ const files = fs.readdirSync(currentDir, { withFileTypes: true });
161
+ for (const file of files) {
162
+ const fullPath = path.join(currentDir, file.name);
163
+
164
+ // Ignore common binary/system dirs
165
+ if (file.name.startsWith(".") || file.name === "node_modules" || file.name === "dist" || file.name === "build") continue;
166
+
167
+ if (file.isDirectory()) {
168
+ results = results.concat(searchRecursive(fullPath, depth + 1));
169
+ } else {
170
+ // Check extension (skip images, binaries)
171
+ const ext = path.extname(file.name).toLowerCase();
172
+ if ([".jpg", ".png", ".exe", ".bin", ".lock", ".pdf"].includes(ext)) continue;
173
+
174
+ try {
175
+ const content = fs.readFileSync(fullPath, "utf-8");
176
+ const lines = content.split('\n');
177
+ lines.forEach((line, index) => {
178
+ if (line.toLowerCase().includes(query)) {
179
+ // Return relative path
180
+ const relPath = path.relative(process.cwd(), fullPath);
181
+ results.push(`${relPath}:${index + 1}: ${line.trim().slice(0, 100)}`);
182
+ }
183
+ });
184
+ } catch (e) {
185
+ // ignore read errors
186
+ }
187
+ }
188
+ }
189
+ } catch (e) {}
190
+ return results;
191
+ }
192
+
193
+ const matches = searchRecursive(dir);
194
+ if (matches.length === 0) return "No matches found.";
195
+ // Limit results
196
+ return `Found ${matches.length} matches:\n${matches.slice(0, 50).join('\n')}${matches.length > 50 ? `\n...and ${matches.length - 50} more` : ""}`;
197
+ },
198
+
125
199
  async write_file(args) {
126
200
  const filepath = path.resolve(process.cwd(), args.path);
127
201
  const dir = path.dirname(filepath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xiaozhou-chat",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
4
4
  "description": "CLI chatbot based on NewAPI",
5
5
  "bin": {
6
6
  "xiaozhou-chat": "bin/cli.js"