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 +6 -1
- package/lib/config.js +27 -2
- package/lib/tools.js +78 -4
- package/package.json +1 -1
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
|
-
|
|
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);
|