@ghenya/clinn 0.8.8 → 0.9.1

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/Tools/browser.js CHANGED
@@ -35,7 +35,7 @@ let _browserPromise = null;
35
35
  let _browser = null;
36
36
 
37
37
  async function _ensureBrowser() {
38
- if (_browser && _browser.isConnected()) return _browser;
38
+ if (_browser && _browser.connected) return _browser;
39
39
  const pp = _getPuppeteer();
40
40
  if (!pp) throw new Error("puppeteer-core 未安装, 请运行 npm install puppeteer-core");
41
41
  const chromePath = _getChromePath();
@@ -182,26 +182,101 @@ const globTool = {
182
182
 
183
183
  const grepTool = {
184
184
  name: "grep",
185
- description: "正则搜索文件内容(ripgrep风格). 返回 文件路径:行号:内容",
185
+ description: "超快速内容搜索(优先使用ripgrep). 支持上下文行/files_only/count三种输出模式. 比系统grep快5-10x",
186
186
  parameters: {
187
- pattern: { type: "string", required: true, description: "正则表达式" },
187
+ pattern: { type: "string", required: true, description: "正则表达式搜索模式" },
188
188
  dirPath: { type: "string", required: false, description: "搜索目录, 默认当前目录" },
189
- glob: { type: "string", required: false, description: "文件过滤glob, 如 '*.js'" },
189
+ glob: { type: "string", required: false, description: "文件过滤, 如 '*.js' 或 '*.{ts,tsx}'" },
190
190
  headLimit: { type: "number", required: false, description: "结果上限, 默认50" },
191
191
  ignoreCase: { type: "boolean", required: false, description: "忽略大小写, 默认true" },
192
+ context: { type: "number", required: false, description: "上下文行数(前后各N行), 默认0" },
193
+ outputMode: { type: "string", required: false, description: "输出模式: content(默认)/files_only/count" },
192
194
  },
193
- execute: async ({ pattern, dirPath, glob, headLimit, ignoreCase }) => {
194
- const root = dirPath || process.cwd();
195
+ execute: async ({ pattern, dirPath, glob, headLimit, ignoreCase, context, outputMode }) => {
196
+ const root = path.resolve(dirPath || process.cwd());
195
197
  if (!fs.existsSync(root)) return `[不存在] ${root}`;
196
198
  const max = headLimit || 50;
197
199
  const ic = ignoreCase !== false;
200
+ const mode = outputMode || "content";
201
+
202
+ // 优先使用 ripgrep (更快, 支持 --json + 上下文)
203
+ const rgPath = (() => {
204
+ try { return execSync("which rg", { encoding: "utf-8", timeout: 2000 }).trim(); }
205
+ catch (_) { return null; }
206
+ })();
207
+
208
+ if (rgPath) {
209
+ const args = ["--json"];
210
+ if (ic) args.push("-i");
211
+ if (glob) args.push("-g", glob);
212
+ args.push("-m", String(max));
213
+ if (context && context > 0) args.push("-C", String(context));
214
+ args.push("--no-ignore-vcs");
215
+ args.push("-g", "!node_modules");
216
+ args.push("-g", "!.git");
217
+ args.push("-g", "!__pycache__");
218
+ args.push("-g", "!.DS_Store");
219
+
220
+ try {
221
+ const stdout = execSync(
222
+ `rg ${args.map(a => JSON.stringify(a)).join(" ")} ${JSON.stringify(pattern)} ${JSON.stringify(root)}`,
223
+ { encoding: "utf-8", timeout: 30000, maxBuffer: 4 * 1024 * 1024 }
224
+ );
225
+ if (!stdout.trim()) return `[无匹配] "${pattern}" 在 ${root}`;
226
+
227
+ const lines = stdout.trim().split("\n").filter(Boolean);
228
+
229
+ if (mode === "count") {
230
+ const counts = {};
231
+ for (const line of lines) {
232
+ try {
233
+ const obj = JSON.parse(line);
234
+ if (obj.type === "match") counts[obj.data.path.text] = (counts[obj.data.path.text] || 0) + 1;
235
+ } catch (_) {}
236
+ }
237
+ return Object.entries(counts).map(([f, c]) => `${f}: ${c} 处匹配`).join("\n");
238
+ }
239
+
240
+ if (mode === "files_only") {
241
+ const files = new Set();
242
+ for (const line of lines) {
243
+ try {
244
+ const obj = JSON.parse(line);
245
+ if (obj.type === "match" || obj.type === "begin") files.add(obj.data.path.text);
246
+ } catch (_) {}
247
+ }
248
+ return [...files].join("\n");
249
+ }
250
+
251
+ // content 模式
252
+ const results = [];
253
+ for (const line of lines) {
254
+ try {
255
+ const obj = JSON.parse(line);
256
+ if (obj.type === "match") {
257
+ const d = obj.data;
258
+ results.push(`${d.path.text}:${d.line_number}: ${d.lines.text.trimEnd()}`);
259
+ }
260
+ } catch (_) {}
261
+ }
262
+ if (results.length === 0) return `[无匹配] "${pattern}" 在 ${root}`;
263
+ return results.join("\n");
264
+ } catch (e) {
265
+ if (e.status === 1) return `[无匹配] "${pattern}" 在 ${root}`;
266
+ // ripgrep 失败, 回退到系统 grep
267
+ }
268
+ }
269
+
270
+ // 回退: 系统 grep
198
271
  const globFilter = glob ? ` --include="${glob}"` : "";
199
272
  const icFlag = ic ? " -i" : "";
273
+ const ctxFlag = context && context > 0 ? ` -C ${context}` : "";
200
274
  try {
201
- const cmd = `grep -rn${icFlag}${globFilter} -m ${max} "${pattern.replace(/"/g, '\\"')}" "${root}"`;
275
+ const cmd = `grep -rn${icFlag}${ctxFlag}${globFilter} -m ${max} "${pattern.replace(/"/g, '\\"')}" "${root}"`;
202
276
  const output = execSync(cmd, { cwd: root, encoding: "utf-8", timeout: 15000, maxBuffer: 2 * 1024 * 1024 });
203
- const lines = output.trim().split("\n").slice(0, max);
204
- return lines.length > 0 ? lines.join("\n") : `[无匹配] "${pattern}" 在 ${root}`;
277
+ const lines = output.trim().split("\n").slice(0, mode === "count" ? 9999 : max);
278
+ if (lines.length === 0 || (lines.length === 1 && !lines[0])) return `[无匹配] "${pattern}" 在 ${root}`;
279
+ return lines.join("\n");
205
280
  } catch (e) {
206
281
  if (e.stdout) return e.stdout.trim().split("\n").slice(0, max).join("\n");
207
282
  return `[无匹配] "${pattern}" 在 ${root}`;
package/Tools/index.js CHANGED
@@ -241,7 +241,7 @@ function filterToolDeclarations(prompt) {
241
241
  return toFunctionDeclarations();
242
242
  }
243
243
 
244
- const alwaysInclude = ["search_memory", "save_memory", "list_memory", "delete_memory", "search_history", "list_history_files", "browse_page", "browse_page_text", "compress_context", "forget_conversation", "restart_session", "todo_write"];
244
+ const alwaysInclude = ["search_memory", "save_memory", "list_memory", "delete_memory", "mem_rom", "mem_ram", "search_history", "list_history_files", "list_sessions", "view_session", "search_sessions", "browse_page", "browse_page_text", "compress_context", "forget_conversation", "restart_session", "todo_write"];
245
245
  for (const name of alwaysInclude) {
246
246
  if (toolRegistry[name]) matched.add(name);
247
247
  }
@@ -172,7 +172,7 @@ const searchMemoryTool = {
172
172
 
173
173
  const saveMemoryTool = {
174
174
  name: "save_memory",
175
- description: "保存一条关键信息到记忆(每条不超过200字)",
175
+ description: "保存一条关键信息到 ROM(永久记忆, 重启不丢). 等价于 mem_rom",
176
176
  parameters: {
177
177
  content: { type: "string", required: true, description: "记忆内容" },
178
178
  tags: { type: "string", required: false, description: "标签,逗号分隔" },
@@ -180,6 +180,26 @@ const saveMemoryTool = {
180
180
  execute: () => { return "save_memory must be injected"; },
181
181
  };
182
182
 
183
+ const memRomTool = {
184
+ name: "mem_rom",
185
+ description: "保存一条 ROM 记忆(永久保存, 写入磁盘, 重启后仍存在). 用于身份、偏好、配置等跨会话信息",
186
+ parameters: {
187
+ content: { type: "string", required: true, description: "记忆内容, 不超过200字" },
188
+ tags: { type: "string", required: false, description: "标签,逗号分隔, 如 habit,config" },
189
+ },
190
+ execute: () => { return "mem_rom must be injected"; },
191
+ };
192
+
193
+ const memRamTool = {
194
+ name: "mem_ram",
195
+ description: "保存一条 RAM 记忆(仅本次窗口, 关闭即丢失). 用于临时上下文、当前任务状态等",
196
+ parameters: {
197
+ content: { type: "string", required: true, description: "记忆内容, 不超过200字" },
198
+ tags: { type: "string", required: false, description: "标签,逗号分隔" },
199
+ },
200
+ execute: () => { return "mem_ram must be injected"; },
201
+ };
202
+
183
203
  const listMemoryTool = {
184
204
  name: "list_memory",
185
205
  description: "列出所有记忆条目",
@@ -215,6 +235,35 @@ const listHistoryTool = {
215
235
  execute: () => { return "list_history_files must be injected"; },
216
236
  };
217
237
 
238
+ const listSessionsTool = {
239
+ name: "list_sessions",
240
+ description: "列出所有对话 session(会话分组),每次 /new 或重启算一个新 session。可以看到过去的对话主题、时间、轮数。用这个来回答「之前聊过什么」类问题。",
241
+ parameters: {
242
+ limit: { type: "number", required: false, description: "返回数量, 默认20" },
243
+ },
244
+ execute: () => { return "list_sessions must be injected"; },
245
+ };
246
+
247
+ const viewSessionTool = {
248
+ name: "view_session",
249
+ description: "查看某个 session 的完整对话内容,包括用户消息和 AI 回复。用 list_sessions 获取 session_id 后再调用此工具。",
250
+ parameters: {
251
+ session_id: { type: "string", required: true, description: "Session ID (从 list_sessions 获取)" },
252
+ limit: { type: "number", required: false, description: "返回轮数上限, 默认50" },
253
+ },
254
+ execute: () => { return "view_session must be injected"; },
255
+ };
256
+
257
+ const searchSessionsTool = {
258
+ name: "search_sessions",
259
+ description: "在全部历史 session 中全文搜索。比 search_history 更好——会按 session 分组、显示匹配得分和上下文。用这个来回答「你记得关于 X 的哪次对话?」类问题。",
260
+ parameters: {
261
+ query: { type: "string", required: true, description: "搜索关键词,支持多词空格分隔" },
262
+ limit: { type: "number", required: false, description: "返回数量, 默认10" },
263
+ },
264
+ execute: () => { return "search_sessions must be injected"; },
265
+ };
266
+
218
267
  const compressContextTool = {
219
268
  name: "compress_context",
220
269
  description: "压缩当前上下文: 将历史对话摘要存入记忆并清空对话历史",
@@ -268,10 +317,15 @@ module.exports = {
268
317
  set_timer: setTimerTool,
269
318
  search_memory: searchMemoryTool,
270
319
  save_memory: saveMemoryTool,
320
+ mem_rom: memRomTool,
321
+ mem_ram: memRamTool,
271
322
  list_memory: listMemoryTool,
272
323
  delete_memory: deleteMemoryTool,
273
324
  search_history: searchHistoryTool,
274
325
  list_history_files: listHistoryTool,
326
+ list_sessions: listSessionsTool,
327
+ view_session: viewSessionTool,
328
+ search_sessions: searchSessionsTool,
275
329
  compress_context: compressContextTool,
276
330
  agent_self_invoke: agentSelfInvokeTool,
277
331
  save_tool: saveToolTool,
package/config.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "agent": {
3
3
  "name": "Clinn",
4
- "version": "0.8.8",
4
+ "version": "0.9.1",
5
5
  "description": "控制台智能体助手"
6
6
  },
7
7
  "llm": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghenya/clinn",
3
- "version": "0.8.8",
3
+ "version": "0.9.1",
4
4
  "description": "终端原生 AI 编程助手 — DeepSeek 驱动,50+ 工具,对话记忆,虚拟浏览器,Ink 全屏界面",
5
5
  "main": "Src/index.jsx",
6
6
  "bin": {
@@ -11,13 +11,13 @@
11
11
  "Tools/",
12
12
  "Mem/",
13
13
  "Logos/",
14
- "Skills/",
15
14
  "bin/",
16
15
  "config.json",
17
16
  "README.md",
18
17
  "install.cjs",
19
18
  "install.sh",
20
- "install.bat"
19
+ "install.bat",
20
+ "Skills/"
21
21
  ],
22
22
  "scripts": {
23
23
  "postinstall": "node install.cjs",
@@ -52,11 +52,11 @@
52
52
  "node": ">=18.0.0"
53
53
  },
54
54
  "dependencies": {
55
- "puppeteer-core": "^25.1.0",
56
- "ink": "^5.2.0",
57
- "ink-text-input": "^6.0.0",
55
+ "ink": "^7.0.5",
58
56
  "ink-spinner": "^5.0.0",
59
- "react": "^18.3.1",
57
+ "ink-text-input": "^6.0.0",
58
+ "puppeteer-core": "^25.1.0",
59
+ "react": "^19.2.7",
60
60
  "tsx": "^4.0.0"
61
61
  }
62
- }
62
+ }