@mariozechner/pi-coding-agent 0.10.2 → 0.11.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +52 -2
  3. package/dist/export-html.d.ts +5 -0
  4. package/dist/export-html.d.ts.map +1 -1
  5. package/dist/export-html.js +662 -1
  6. package/dist/export-html.js.map +1 -1
  7. package/dist/main.d.ts.map +1 -1
  8. package/dist/main.js +116 -19
  9. package/dist/main.js.map +1 -1
  10. package/dist/tools/find.d.ts +9 -0
  11. package/dist/tools/find.d.ts.map +1 -0
  12. package/dist/tools/find.js +143 -0
  13. package/dist/tools/find.js.map +1 -0
  14. package/dist/tools/grep.d.ts +13 -0
  15. package/dist/tools/grep.d.ts.map +1 -0
  16. package/dist/tools/grep.js +207 -0
  17. package/dist/tools/grep.js.map +1 -0
  18. package/dist/tools/index.d.ts +42 -0
  19. package/dist/tools/index.d.ts.map +1 -1
  20. package/dist/tools/index.js +17 -0
  21. package/dist/tools/index.js.map +1 -1
  22. package/dist/tools/ls.d.ts +8 -0
  23. package/dist/tools/ls.d.ts.map +1 -0
  24. package/dist/tools/ls.js +100 -0
  25. package/dist/tools/ls.js.map +1 -0
  26. package/dist/tools-manager.d.ts +3 -0
  27. package/dist/tools-manager.d.ts.map +1 -0
  28. package/dist/tools-manager.js +186 -0
  29. package/dist/tools-manager.js.map +1 -0
  30. package/dist/tui/footer.d.ts +12 -0
  31. package/dist/tui/footer.d.ts.map +1 -1
  32. package/dist/tui/footer.js +42 -1
  33. package/dist/tui/footer.js.map +1 -1
  34. package/dist/tui/tool-execution.d.ts.map +1 -1
  35. package/dist/tui/tool-execution.js +77 -0
  36. package/dist/tui/tool-execution.js.map +1 -1
  37. package/dist/tui/tui-renderer.d.ts +1 -1
  38. package/dist/tui/tui-renderer.d.ts.map +1 -1
  39. package/dist/tui/tui-renderer.js +8 -2
  40. package/dist/tui/tui-renderer.js.map +1 -1
  41. package/package.json +4 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../src/tools/find.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAsBrD,QAAA,MAAM,UAAU;;;;EAMd,CAAC;AAIH,eAAO,MAAM,QAAQ,EAAE,SAAS,CAAC,OAAO,UAAU,CAwIjD,CAAC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawnSync } from \"child_process\";\nimport { existsSync } from \"fs\";\nimport { globSync } from \"glob\";\nimport { homedir } from \"os\";\nimport path from \"path\";\nimport { ensureTool } from \"../tools-manager.js\";\n\n/**\n * Expand ~ to home directory\n */\nfunction expandPath(filePath: string): string {\n\tif (filePath === \"~\") {\n\t\treturn homedir();\n\t}\n\tif (filePath.startsWith(\"~/\")) {\n\t\treturn homedir() + filePath.slice(1);\n\t}\n\treturn filePath;\n}\n\nconst findSchema = Type.Object({\n\tpattern: Type.String({\n\t\tdescription: \"Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'\",\n\t}),\n\tpath: Type.Optional(Type.String({ description: \"Directory to search in (default: current directory)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results (default: 1000)\" })),\n});\n\nconst DEFAULT_LIMIT = 1000;\n\nexport const findTool: AgentTool<typeof findSchema> = {\n\tname: \"find\",\n\tlabel: \"find\",\n\tdescription:\n\t\t\"Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects .gitignore.\",\n\tparameters: findSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{ pattern, path: searchDir, limit }: { pattern: string; path?: string; limit?: number },\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (signal?.aborted) {\n\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst onAbort = () => reject(new Error(\"Operation aborted\"));\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\t// Ensure fd is available\n\t\t\t\t\tconst fdPath = await ensureTool(\"fd\", true);\n\t\t\t\t\tif (!fdPath) {\n\t\t\t\t\t\treject(new Error(\"fd is not available and could not be downloaded\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst searchPath = path.resolve(expandPath(searchDir || \".\"));\n\t\t\t\t\tconst effectiveLimit = limit ?? DEFAULT_LIMIT;\n\n\t\t\t\t\t// Build fd arguments\n\t\t\t\t\tconst args: string[] = [\n\t\t\t\t\t\t\"--glob\", // Use glob pattern\n\t\t\t\t\t\t\"--color=never\", // No ANSI colors\n\t\t\t\t\t\t\"--hidden\", // Search hidden files (but still respect .gitignore)\n\t\t\t\t\t\t\"--max-results\",\n\t\t\t\t\t\tString(effectiveLimit),\n\t\t\t\t\t];\n\n\t\t\t\t\t// Include .gitignore files (root + nested) so fd respects them even outside git repos\n\t\t\t\t\tconst gitignoreFiles = new Set<string>();\n\t\t\t\t\tconst rootGitignore = path.join(searchPath, \".gitignore\");\n\t\t\t\t\tif (existsSync(rootGitignore)) {\n\t\t\t\t\t\tgitignoreFiles.add(rootGitignore);\n\t\t\t\t\t}\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst nestedGitignores = globSync(\"**/.gitignore\", {\n\t\t\t\t\t\t\tcwd: searchPath,\n\t\t\t\t\t\t\tdot: true,\n\t\t\t\t\t\t\tabsolute: true,\n\t\t\t\t\t\t\tignore: [\"**/node_modules/**\", \"**/.git/**\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfor (const file of nestedGitignores) {\n\t\t\t\t\t\t\tgitignoreFiles.add(file);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Ignore glob errors\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const gitignorePath of gitignoreFiles) {\n\t\t\t\t\t\targs.push(\"--ignore-file\", gitignorePath);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Pattern and path\n\t\t\t\t\targs.push(pattern, searchPath);\n\n\t\t\t\t\t// Run fd\n\t\t\t\t\tconst result = spawnSync(fdPath, args, {\n\t\t\t\t\t\tencoding: \"utf-8\",\n\t\t\t\t\t\tmaxBuffer: 10 * 1024 * 1024, // 10MB\n\t\t\t\t\t});\n\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\n\t\t\t\t\tif (result.error) {\n\t\t\t\t\t\treject(new Error(`Failed to run fd: ${result.error.message}`));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet output = result.stdout?.trim() || \"\";\n\n\t\t\t\t\tif (result.status !== 0) {\n\t\t\t\t\t\tconst errorMsg = result.stderr?.trim() || `fd exited with code ${result.status}`;\n\t\t\t\t\t\t// fd returns non-zero for some errors but may still have partial output\n\t\t\t\t\t\tif (!output) {\n\t\t\t\t\t\t\treject(new Error(errorMsg));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!output) {\n\t\t\t\t\t\toutput = \"No files found matching pattern\";\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\t\tconst relativized: string[] = [];\n\n\t\t\t\t\t\tfor (const rawLine of lines) {\n\t\t\t\t\t\t\tconst line = rawLine.replace(/\\r$/, \"\").trim();\n\t\t\t\t\t\t\tif (!line) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst hadTrailingSlash = line.endsWith(\"/\") || line.endsWith(\"\\\\\");\n\t\t\t\t\t\t\tlet relativePath = line;\n\t\t\t\t\t\t\tif (line.startsWith(searchPath)) {\n\t\t\t\t\t\t\t\trelativePath = line.slice(searchPath.length + 1); // +1 for the /\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\trelativePath = path.relative(searchPath, line);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (hadTrailingSlash && !relativePath.endsWith(\"/\")) {\n\t\t\t\t\t\t\t\trelativePath += \"/\";\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\trelativized.push(relativePath);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\toutput = relativized.join(\"\\n\");\n\n\t\t\t\t\t\tconst count = relativized.length;\n\t\t\t\t\t\tif (count >= effectiveLimit) {\n\t\t\t\t\t\t\toutput += `\\n\\n(truncated, ${effectiveLimit} results shown)`;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: output }], details: undefined });\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\treject(e);\n\t\t\t\t}\n\t\t\t})();\n\t\t});\n\t},\n};\n"]}
@@ -0,0 +1,143 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { spawnSync } from "child_process";
3
+ import { existsSync } from "fs";
4
+ import { globSync } from "glob";
5
+ import { homedir } from "os";
6
+ import path from "path";
7
+ import { ensureTool } from "../tools-manager.js";
8
+ /**
9
+ * Expand ~ to home directory
10
+ */
11
+ function expandPath(filePath) {
12
+ if (filePath === "~") {
13
+ return homedir();
14
+ }
15
+ if (filePath.startsWith("~/")) {
16
+ return homedir() + filePath.slice(1);
17
+ }
18
+ return filePath;
19
+ }
20
+ const findSchema = Type.Object({
21
+ pattern: Type.String({
22
+ description: "Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'",
23
+ }),
24
+ path: Type.Optional(Type.String({ description: "Directory to search in (default: current directory)" })),
25
+ limit: Type.Optional(Type.Number({ description: "Maximum number of results (default: 1000)" })),
26
+ });
27
+ const DEFAULT_LIMIT = 1000;
28
+ export const findTool = {
29
+ name: "find",
30
+ label: "find",
31
+ description: "Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects .gitignore.",
32
+ parameters: findSchema,
33
+ execute: async (_toolCallId, { pattern, path: searchDir, limit }, signal) => {
34
+ return new Promise((resolve, reject) => {
35
+ if (signal?.aborted) {
36
+ reject(new Error("Operation aborted"));
37
+ return;
38
+ }
39
+ const onAbort = () => reject(new Error("Operation aborted"));
40
+ signal?.addEventListener("abort", onAbort, { once: true });
41
+ (async () => {
42
+ try {
43
+ // Ensure fd is available
44
+ const fdPath = await ensureTool("fd", true);
45
+ if (!fdPath) {
46
+ reject(new Error("fd is not available and could not be downloaded"));
47
+ return;
48
+ }
49
+ const searchPath = path.resolve(expandPath(searchDir || "."));
50
+ const effectiveLimit = limit ?? DEFAULT_LIMIT;
51
+ // Build fd arguments
52
+ const args = [
53
+ "--glob", // Use glob pattern
54
+ "--color=never", // No ANSI colors
55
+ "--hidden", // Search hidden files (but still respect .gitignore)
56
+ "--max-results",
57
+ String(effectiveLimit),
58
+ ];
59
+ // Include .gitignore files (root + nested) so fd respects them even outside git repos
60
+ const gitignoreFiles = new Set();
61
+ const rootGitignore = path.join(searchPath, ".gitignore");
62
+ if (existsSync(rootGitignore)) {
63
+ gitignoreFiles.add(rootGitignore);
64
+ }
65
+ try {
66
+ const nestedGitignores = globSync("**/.gitignore", {
67
+ cwd: searchPath,
68
+ dot: true,
69
+ absolute: true,
70
+ ignore: ["**/node_modules/**", "**/.git/**"],
71
+ });
72
+ for (const file of nestedGitignores) {
73
+ gitignoreFiles.add(file);
74
+ }
75
+ }
76
+ catch {
77
+ // Ignore glob errors
78
+ }
79
+ for (const gitignorePath of gitignoreFiles) {
80
+ args.push("--ignore-file", gitignorePath);
81
+ }
82
+ // Pattern and path
83
+ args.push(pattern, searchPath);
84
+ // Run fd
85
+ const result = spawnSync(fdPath, args, {
86
+ encoding: "utf-8",
87
+ maxBuffer: 10 * 1024 * 1024, // 10MB
88
+ });
89
+ signal?.removeEventListener("abort", onAbort);
90
+ if (result.error) {
91
+ reject(new Error(`Failed to run fd: ${result.error.message}`));
92
+ return;
93
+ }
94
+ let output = result.stdout?.trim() || "";
95
+ if (result.status !== 0) {
96
+ const errorMsg = result.stderr?.trim() || `fd exited with code ${result.status}`;
97
+ // fd returns non-zero for some errors but may still have partial output
98
+ if (!output) {
99
+ reject(new Error(errorMsg));
100
+ return;
101
+ }
102
+ }
103
+ if (!output) {
104
+ output = "No files found matching pattern";
105
+ }
106
+ else {
107
+ const lines = output.split("\n");
108
+ const relativized = [];
109
+ for (const rawLine of lines) {
110
+ const line = rawLine.replace(/\r$/, "").trim();
111
+ if (!line) {
112
+ continue;
113
+ }
114
+ const hadTrailingSlash = line.endsWith("/") || line.endsWith("\\");
115
+ let relativePath = line;
116
+ if (line.startsWith(searchPath)) {
117
+ relativePath = line.slice(searchPath.length + 1); // +1 for the /
118
+ }
119
+ else {
120
+ relativePath = path.relative(searchPath, line);
121
+ }
122
+ if (hadTrailingSlash && !relativePath.endsWith("/")) {
123
+ relativePath += "/";
124
+ }
125
+ relativized.push(relativePath);
126
+ }
127
+ output = relativized.join("\n");
128
+ const count = relativized.length;
129
+ if (count >= effectiveLimit) {
130
+ output += `\n\n(truncated, ${effectiveLimit} results shown)`;
131
+ }
132
+ }
133
+ resolve({ content: [{ type: "text", text: output }], details: undefined });
134
+ }
135
+ catch (e) {
136
+ signal?.removeEventListener("abort", onAbort);
137
+ reject(e);
138
+ }
139
+ })();
140
+ });
141
+ },
142
+ };
143
+ //# sourceMappingURL=find.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find.js","sourceRoot":"","sources":["../../src/tools/find.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD;;GAEG;AACH,SAAS,UAAU,CAAC,QAAgB,EAAU;IAC7C,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;QACtB,OAAO,OAAO,EAAE,CAAC;IAClB,CAAC;IACD,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,OAAO,OAAO,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,QAAQ,CAAC;AAAA,CAChB;AAED,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;IAC9B,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC;QACpB,WAAW,EAAE,8EAA8E;KAC3F,CAAC;IACF,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,qDAAqD,EAAE,CAAC,CAAC;IACxG,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,2CAA2C,EAAE,CAAC,CAAC;CAC/F,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,IAAI,CAAC;AAE3B,MAAM,CAAC,MAAM,QAAQ,GAAiC;IACrD,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,MAAM;IACb,WAAW,EACV,sHAAsH;IACvH,UAAU,EAAE,UAAU;IACtB,OAAO,EAAE,KAAK,EACb,WAAmB,EACnB,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAsD,EACvF,MAAoB,EACnB,EAAE,CAAC;QACJ,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;YACvC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACrB,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;gBACvC,OAAO;YACR,CAAC;YAED,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;YAC7D,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAE3D,CAAC,KAAK,IAAI,EAAE,CAAC;gBACZ,IAAI,CAAC;oBACJ,yBAAyB;oBACzB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;oBAC5C,IAAI,CAAC,MAAM,EAAE,CAAC;wBACb,MAAM,CAAC,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC,CAAC;wBACrE,OAAO;oBACR,CAAC;oBAED,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,IAAI,GAAG,CAAC,CAAC,CAAC;oBAC9D,MAAM,cAAc,GAAG,KAAK,IAAI,aAAa,CAAC;oBAE9C,qBAAqB;oBACrB,MAAM,IAAI,GAAa;wBACtB,QAAQ,EAAE,mBAAmB;wBAC7B,eAAe,EAAE,iBAAiB;wBAClC,UAAU,EAAE,qDAAqD;wBACjE,eAAe;wBACf,MAAM,CAAC,cAAc,CAAC;qBACtB,CAAC;oBAEF,sFAAsF;oBACtF,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;oBACzC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;oBAC1D,IAAI,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;wBAC/B,cAAc,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;oBACnC,CAAC;oBAED,IAAI,CAAC;wBACJ,MAAM,gBAAgB,GAAG,QAAQ,CAAC,eAAe,EAAE;4BAClD,GAAG,EAAE,UAAU;4BACf,GAAG,EAAE,IAAI;4BACT,QAAQ,EAAE,IAAI;4BACd,MAAM,EAAE,CAAC,oBAAoB,EAAE,YAAY,CAAC;yBAC5C,CAAC,CAAC;wBACH,KAAK,MAAM,IAAI,IAAI,gBAAgB,EAAE,CAAC;4BACrC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;wBAC1B,CAAC;oBACF,CAAC;oBAAC,MAAM,CAAC;wBACR,qBAAqB;oBACtB,CAAC;oBAED,KAAK,MAAM,aAAa,IAAI,cAAc,EAAE,CAAC;wBAC5C,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;oBAC3C,CAAC;oBAED,mBAAmB;oBACnB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;oBAE/B,SAAS;oBACT,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE;wBACtC,QAAQ,EAAE,OAAO;wBACjB,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO;qBACpC,CAAC,CAAC;oBAEH,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;oBAE9C,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;wBAClB,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;wBAC/D,OAAO;oBACR,CAAC;oBAED,IAAI,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;oBAEzC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBACzB,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,uBAAuB,MAAM,CAAC,MAAM,EAAE,CAAC;wBACjF,wEAAwE;wBACxE,IAAI,CAAC,MAAM,EAAE,CAAC;4BACb,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;4BAC5B,OAAO;wBACR,CAAC;oBACF,CAAC;oBAED,IAAI,CAAC,MAAM,EAAE,CAAC;wBACb,MAAM,GAAG,iCAAiC,CAAC;oBAC5C,CAAC;yBAAM,CAAC;wBACP,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;wBACjC,MAAM,WAAW,GAAa,EAAE,CAAC;wBAEjC,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;4BAC7B,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;4BAC/C,IAAI,CAAC,IAAI,EAAE,CAAC;gCACX,SAAS;4BACV,CAAC;4BAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;4BACnE,IAAI,YAAY,GAAG,IAAI,CAAC;4BACxB,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gCACjC,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe;4BAClE,CAAC;iCAAM,CAAC;gCACP,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;4BAChD,CAAC;4BAED,IAAI,gBAAgB,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gCACrD,YAAY,IAAI,GAAG,CAAC;4BACrB,CAAC;4BAED,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;wBAChC,CAAC;wBAED,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;wBAEhC,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC;wBACjC,IAAI,KAAK,IAAI,cAAc,EAAE,CAAC;4BAC7B,MAAM,IAAI,mBAAmB,cAAc,iBAAiB,CAAC;wBAC9D,CAAC;oBACF,CAAC;oBAED,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;gBAC5E,CAAC;gBAAC,OAAO,CAAM,EAAE,CAAC;oBACjB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;oBAC9C,MAAM,CAAC,CAAC,CAAC,CAAC;gBACX,CAAC;YAAA,CACD,CAAC,EAAE,CAAC;QAAA,CACL,CAAC,CAAC;IAAA,CACH;CACD,CAAC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawnSync } from \"child_process\";\nimport { existsSync } from \"fs\";\nimport { globSync } from \"glob\";\nimport { homedir } from \"os\";\nimport path from \"path\";\nimport { ensureTool } from \"../tools-manager.js\";\n\n/**\n * Expand ~ to home directory\n */\nfunction expandPath(filePath: string): string {\n\tif (filePath === \"~\") {\n\t\treturn homedir();\n\t}\n\tif (filePath.startsWith(\"~/\")) {\n\t\treturn homedir() + filePath.slice(1);\n\t}\n\treturn filePath;\n}\n\nconst findSchema = Type.Object({\n\tpattern: Type.String({\n\t\tdescription: \"Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'\",\n\t}),\n\tpath: Type.Optional(Type.String({ description: \"Directory to search in (default: current directory)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results (default: 1000)\" })),\n});\n\nconst DEFAULT_LIMIT = 1000;\n\nexport const findTool: AgentTool<typeof findSchema> = {\n\tname: \"find\",\n\tlabel: \"find\",\n\tdescription:\n\t\t\"Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects .gitignore.\",\n\tparameters: findSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{ pattern, path: searchDir, limit }: { pattern: string; path?: string; limit?: number },\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (signal?.aborted) {\n\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst onAbort = () => reject(new Error(\"Operation aborted\"));\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\t// Ensure fd is available\n\t\t\t\t\tconst fdPath = await ensureTool(\"fd\", true);\n\t\t\t\t\tif (!fdPath) {\n\t\t\t\t\t\treject(new Error(\"fd is not available and could not be downloaded\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst searchPath = path.resolve(expandPath(searchDir || \".\"));\n\t\t\t\t\tconst effectiveLimit = limit ?? DEFAULT_LIMIT;\n\n\t\t\t\t\t// Build fd arguments\n\t\t\t\t\tconst args: string[] = [\n\t\t\t\t\t\t\"--glob\", // Use glob pattern\n\t\t\t\t\t\t\"--color=never\", // No ANSI colors\n\t\t\t\t\t\t\"--hidden\", // Search hidden files (but still respect .gitignore)\n\t\t\t\t\t\t\"--max-results\",\n\t\t\t\t\t\tString(effectiveLimit),\n\t\t\t\t\t];\n\n\t\t\t\t\t// Include .gitignore files (root + nested) so fd respects them even outside git repos\n\t\t\t\t\tconst gitignoreFiles = new Set<string>();\n\t\t\t\t\tconst rootGitignore = path.join(searchPath, \".gitignore\");\n\t\t\t\t\tif (existsSync(rootGitignore)) {\n\t\t\t\t\t\tgitignoreFiles.add(rootGitignore);\n\t\t\t\t\t}\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst nestedGitignores = globSync(\"**/.gitignore\", {\n\t\t\t\t\t\t\tcwd: searchPath,\n\t\t\t\t\t\t\tdot: true,\n\t\t\t\t\t\t\tabsolute: true,\n\t\t\t\t\t\t\tignore: [\"**/node_modules/**\", \"**/.git/**\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfor (const file of nestedGitignores) {\n\t\t\t\t\t\t\tgitignoreFiles.add(file);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Ignore glob errors\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const gitignorePath of gitignoreFiles) {\n\t\t\t\t\t\targs.push(\"--ignore-file\", gitignorePath);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Pattern and path\n\t\t\t\t\targs.push(pattern, searchPath);\n\n\t\t\t\t\t// Run fd\n\t\t\t\t\tconst result = spawnSync(fdPath, args, {\n\t\t\t\t\t\tencoding: \"utf-8\",\n\t\t\t\t\t\tmaxBuffer: 10 * 1024 * 1024, // 10MB\n\t\t\t\t\t});\n\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\n\t\t\t\t\tif (result.error) {\n\t\t\t\t\t\treject(new Error(`Failed to run fd: ${result.error.message}`));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet output = result.stdout?.trim() || \"\";\n\n\t\t\t\t\tif (result.status !== 0) {\n\t\t\t\t\t\tconst errorMsg = result.stderr?.trim() || `fd exited with code ${result.status}`;\n\t\t\t\t\t\t// fd returns non-zero for some errors but may still have partial output\n\t\t\t\t\t\tif (!output) {\n\t\t\t\t\t\t\treject(new Error(errorMsg));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!output) {\n\t\t\t\t\t\toutput = \"No files found matching pattern\";\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\t\tconst relativized: string[] = [];\n\n\t\t\t\t\t\tfor (const rawLine of lines) {\n\t\t\t\t\t\t\tconst line = rawLine.replace(/\\r$/, \"\").trim();\n\t\t\t\t\t\t\tif (!line) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst hadTrailingSlash = line.endsWith(\"/\") || line.endsWith(\"\\\\\");\n\t\t\t\t\t\t\tlet relativePath = line;\n\t\t\t\t\t\t\tif (line.startsWith(searchPath)) {\n\t\t\t\t\t\t\t\trelativePath = line.slice(searchPath.length + 1); // +1 for the /\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\trelativePath = path.relative(searchPath, line);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (hadTrailingSlash && !relativePath.endsWith(\"/\")) {\n\t\t\t\t\t\t\t\trelativePath += \"/\";\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\trelativized.push(relativePath);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\toutput = relativized.join(\"\\n\");\n\n\t\t\t\t\t\tconst count = relativized.length;\n\t\t\t\t\t\tif (count >= effectiveLimit) {\n\t\t\t\t\t\t\toutput += `\\n\\n(truncated, ${effectiveLimit} results shown)`;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: output }], details: undefined });\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\treject(e);\n\t\t\t\t}\n\t\t\t})();\n\t\t});\n\t},\n};\n"]}
@@ -0,0 +1,13 @@
1
+ import type { AgentTool } from "@mariozechner/pi-ai";
2
+ declare const grepSchema: import("@sinclair/typebox").TObject<{
3
+ pattern: import("@sinclair/typebox").TString;
4
+ path: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
5
+ glob: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
6
+ ignoreCase: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
7
+ literal: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
8
+ context: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
9
+ limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
10
+ }>;
11
+ export declare const grepTool: AgentTool<typeof grepSchema>;
12
+ export {};
13
+ //# sourceMappingURL=grep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../src/tools/grep.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAqBrD,QAAA,MAAM,UAAU;;;;;;;;EAYd,CAAC;AAIH,eAAO,MAAM,QAAQ,EAAE,SAAS,CAAC,OAAO,UAAU,CAoOjD,CAAC","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\nimport { readFileSync, type Stats, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport path from \"path\";\nimport { ensureTool } from \"../tools-manager.js\";\n\n/**\n * Expand ~ to home directory\n */\nfunction expandPath(filePath: string): string {\n\tif (filePath === \"~\") {\n\t\treturn homedir();\n\t}\n\tif (filePath.startsWith(\"~/\")) {\n\t\treturn homedir() + filePath.slice(1);\n\t}\n\treturn filePath;\n}\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nconst DEFAULT_LIMIT = 100;\n\nexport const grepTool: AgentTool<typeof grepSchema> = {\n\tname: \"grep\",\n\tlabel: \"grep\",\n\tdescription:\n\t\t\"Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore.\",\n\tparameters: grepSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{\n\t\t\tpattern,\n\t\t\tpath: searchDir,\n\t\t\tglob,\n\t\t\tignoreCase,\n\t\t\tliteral,\n\t\t\tcontext,\n\t\t\tlimit,\n\t\t}: {\n\t\t\tpattern: string;\n\t\t\tpath?: string;\n\t\t\tglob?: string;\n\t\t\tignoreCase?: boolean;\n\t\t\tliteral?: boolean;\n\t\t\tcontext?: number;\n\t\t\tlimit?: number;\n\t\t},\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (signal?.aborted) {\n\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet settled = false;\n\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\tif (!settled) {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tfn();\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst rgPath = await ensureTool(\"rg\", true);\n\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\tsettle(() => reject(new Error(\"ripgrep (rg) is not available and could not be downloaded\")));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst searchPath = path.resolve(expandPath(searchDir || \".\"));\n\t\t\t\t\tlet searchStat: Stats;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tsearchStat = statSync(searchPath);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst isDirectory = searchStat.isDirectory();\n\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\n\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t};\n\n\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\tconst getFileLines = (filePath: string): string[] => {\n\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst content = readFileSync(filePath, \"utf-8\");\n\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn lines;\n\t\t\t\t\t};\n\n\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\n\t\t\t\t\tif (ignoreCase) {\n\t\t\t\t\t\targs.push(\"--ignore-case\");\n\t\t\t\t\t}\n\n\t\t\t\t\tif (literal) {\n\t\t\t\t\t\targs.push(\"--fixed-strings\");\n\t\t\t\t\t}\n\n\t\t\t\t\tif (glob) {\n\t\t\t\t\t\targs.push(\"--glob\", glob);\n\t\t\t\t\t}\n\n\t\t\t\t\targs.push(pattern, searchPath);\n\n\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\tlet truncated = false;\n\t\t\t\t\tlet aborted = false;\n\t\t\t\t\tlet killedDueToLimit = false;\n\t\t\t\t\tconst outputLines: string[] = [];\n\n\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\trl.close();\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t};\n\n\t\t\t\t\tconst stopChild = (dueToLimit: boolean = false) => {\n\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\tstopChild();\n\t\t\t\t\t};\n\n\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t});\n\n\t\t\t\t\tconst formatBlock = (filePath: string, lineNumber: number) => {\n\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\tconst lines = getFileLines(filePath);\n\t\t\t\t\t\tif (!lines.length) {\n\t\t\t\t\t\t\treturn [`${relativePath}:${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\n\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\n\t\t\t\t\t\t\tif (isMatchLine) {\n\t\t\t\t\t\t\t\tblock.push(`${relativePath}:${current}: ${sanitized}`);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tblock.push(`${relativePath}-${current}- ${sanitized}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn block;\n\t\t\t\t\t};\n\n\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\n\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\") {\n\t\t\t\t\t\t\t\toutputLines.push(...formatBlock(filePath, lineNumber));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\ttruncated = true;\n\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t});\n\n\t\t\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\t\t\tcleanup();\n\n\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\tconst errorMsg = stderr.trim() || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (matchCount === 0) {\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet output = outputLines.join(\"\\n\");\n\t\t\t\t\t\tif (truncated) {\n\t\t\t\t\t\t\toutput += `\\n\\n(truncated, limit of ${effectiveLimit} matches reached)`;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tsettle(() => resolve({ content: [{ type: \"text\", text: output }], details: undefined }));\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t}\n\t\t\t})();\n\t\t});\n\t},\n};\n"]}
@@ -0,0 +1,207 @@
1
+ import { createInterface } from "node:readline";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { spawn } from "child_process";
4
+ import { readFileSync, statSync } from "fs";
5
+ import { homedir } from "os";
6
+ import path from "path";
7
+ import { ensureTool } from "../tools-manager.js";
8
+ /**
9
+ * Expand ~ to home directory
10
+ */
11
+ function expandPath(filePath) {
12
+ if (filePath === "~") {
13
+ return homedir();
14
+ }
15
+ if (filePath.startsWith("~/")) {
16
+ return homedir() + filePath.slice(1);
17
+ }
18
+ return filePath;
19
+ }
20
+ const grepSchema = Type.Object({
21
+ pattern: Type.String({ description: "Search pattern (regex or literal string)" }),
22
+ path: Type.Optional(Type.String({ description: "Directory or file to search (default: current directory)" })),
23
+ glob: Type.Optional(Type.String({ description: "Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'" })),
24
+ ignoreCase: Type.Optional(Type.Boolean({ description: "Case-insensitive search (default: false)" })),
25
+ literal: Type.Optional(Type.Boolean({ description: "Treat pattern as literal string instead of regex (default: false)" })),
26
+ context: Type.Optional(Type.Number({ description: "Number of lines to show before and after each match (default: 0)" })),
27
+ limit: Type.Optional(Type.Number({ description: "Maximum number of matches to return (default: 100)" })),
28
+ });
29
+ const DEFAULT_LIMIT = 100;
30
+ export const grepTool = {
31
+ name: "grep",
32
+ label: "grep",
33
+ description: "Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore.",
34
+ parameters: grepSchema,
35
+ execute: async (_toolCallId, { pattern, path: searchDir, glob, ignoreCase, literal, context, limit, }, signal) => {
36
+ return new Promise((resolve, reject) => {
37
+ if (signal?.aborted) {
38
+ reject(new Error("Operation aborted"));
39
+ return;
40
+ }
41
+ let settled = false;
42
+ const settle = (fn) => {
43
+ if (!settled) {
44
+ settled = true;
45
+ fn();
46
+ }
47
+ };
48
+ (async () => {
49
+ try {
50
+ const rgPath = await ensureTool("rg", true);
51
+ if (!rgPath) {
52
+ settle(() => reject(new Error("ripgrep (rg) is not available and could not be downloaded")));
53
+ return;
54
+ }
55
+ const searchPath = path.resolve(expandPath(searchDir || "."));
56
+ let searchStat;
57
+ try {
58
+ searchStat = statSync(searchPath);
59
+ }
60
+ catch (err) {
61
+ settle(() => reject(new Error(`Path not found: ${searchPath}`)));
62
+ return;
63
+ }
64
+ const isDirectory = searchStat.isDirectory();
65
+ const contextValue = context && context > 0 ? context : 0;
66
+ const effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);
67
+ const formatPath = (filePath) => {
68
+ if (isDirectory) {
69
+ const relative = path.relative(searchPath, filePath);
70
+ if (relative && !relative.startsWith("..")) {
71
+ return relative.replace(/\\/g, "/");
72
+ }
73
+ }
74
+ return path.basename(filePath);
75
+ };
76
+ const fileCache = new Map();
77
+ const getFileLines = (filePath) => {
78
+ let lines = fileCache.get(filePath);
79
+ if (!lines) {
80
+ try {
81
+ const content = readFileSync(filePath, "utf-8");
82
+ lines = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
83
+ }
84
+ catch {
85
+ lines = [];
86
+ }
87
+ fileCache.set(filePath, lines);
88
+ }
89
+ return lines;
90
+ };
91
+ const args = ["--json", "--line-number", "--color=never", "--hidden"];
92
+ if (ignoreCase) {
93
+ args.push("--ignore-case");
94
+ }
95
+ if (literal) {
96
+ args.push("--fixed-strings");
97
+ }
98
+ if (glob) {
99
+ args.push("--glob", glob);
100
+ }
101
+ args.push(pattern, searchPath);
102
+ const child = spawn(rgPath, args, { stdio: ["ignore", "pipe", "pipe"] });
103
+ const rl = createInterface({ input: child.stdout });
104
+ let stderr = "";
105
+ let matchCount = 0;
106
+ let truncated = false;
107
+ let aborted = false;
108
+ let killedDueToLimit = false;
109
+ const outputLines = [];
110
+ const cleanup = () => {
111
+ rl.close();
112
+ signal?.removeEventListener("abort", onAbort);
113
+ };
114
+ const stopChild = (dueToLimit = false) => {
115
+ if (!child.killed) {
116
+ killedDueToLimit = dueToLimit;
117
+ child.kill();
118
+ }
119
+ };
120
+ const onAbort = () => {
121
+ aborted = true;
122
+ stopChild();
123
+ };
124
+ signal?.addEventListener("abort", onAbort, { once: true });
125
+ child.stderr?.on("data", (chunk) => {
126
+ stderr += chunk.toString();
127
+ });
128
+ const formatBlock = (filePath, lineNumber) => {
129
+ const relativePath = formatPath(filePath);
130
+ const lines = getFileLines(filePath);
131
+ if (!lines.length) {
132
+ return [`${relativePath}:${lineNumber}: (unable to read file)`];
133
+ }
134
+ const block = [];
135
+ const start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;
136
+ const end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;
137
+ for (let current = start; current <= end; current++) {
138
+ const lineText = lines[current - 1] ?? "";
139
+ const sanitized = lineText.replace(/\r/g, "");
140
+ const isMatchLine = current === lineNumber;
141
+ if (isMatchLine) {
142
+ block.push(`${relativePath}:${current}: ${sanitized}`);
143
+ }
144
+ else {
145
+ block.push(`${relativePath}-${current}- ${sanitized}`);
146
+ }
147
+ }
148
+ return block;
149
+ };
150
+ rl.on("line", (line) => {
151
+ if (!line.trim() || matchCount >= effectiveLimit) {
152
+ return;
153
+ }
154
+ let event;
155
+ try {
156
+ event = JSON.parse(line);
157
+ }
158
+ catch {
159
+ return;
160
+ }
161
+ if (event.type === "match") {
162
+ matchCount++;
163
+ const filePath = event.data?.path?.text;
164
+ const lineNumber = event.data?.line_number;
165
+ if (filePath && typeof lineNumber === "number") {
166
+ outputLines.push(...formatBlock(filePath, lineNumber));
167
+ }
168
+ if (matchCount >= effectiveLimit) {
169
+ truncated = true;
170
+ stopChild(true);
171
+ }
172
+ }
173
+ });
174
+ child.on("error", (error) => {
175
+ cleanup();
176
+ settle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));
177
+ });
178
+ child.on("close", (code) => {
179
+ cleanup();
180
+ if (aborted) {
181
+ settle(() => reject(new Error("Operation aborted")));
182
+ return;
183
+ }
184
+ if (!killedDueToLimit && code !== 0 && code !== 1) {
185
+ const errorMsg = stderr.trim() || `ripgrep exited with code ${code}`;
186
+ settle(() => reject(new Error(errorMsg)));
187
+ return;
188
+ }
189
+ if (matchCount === 0) {
190
+ settle(() => resolve({ content: [{ type: "text", text: "No matches found" }], details: undefined }));
191
+ return;
192
+ }
193
+ let output = outputLines.join("\n");
194
+ if (truncated) {
195
+ output += `\n\n(truncated, limit of ${effectiveLimit} matches reached)`;
196
+ }
197
+ settle(() => resolve({ content: [{ type: "text", text: output }], details: undefined }));
198
+ });
199
+ }
200
+ catch (err) {
201
+ settle(() => reject(err));
202
+ }
203
+ })();
204
+ });
205
+ },
206
+ };
207
+ //# sourceMappingURL=grep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grep.js","sourceRoot":"","sources":["../../src/tools/grep.ts"],"names":[],"mappings":"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","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\nimport { readFileSync, type Stats, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport path from \"path\";\nimport { ensureTool } from \"../tools-manager.js\";\n\n/**\n * Expand ~ to home directory\n */\nfunction expandPath(filePath: string): string {\n\tif (filePath === \"~\") {\n\t\treturn homedir();\n\t}\n\tif (filePath.startsWith(\"~/\")) {\n\t\treturn homedir() + filePath.slice(1);\n\t}\n\treturn filePath;\n}\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nconst DEFAULT_LIMIT = 100;\n\nexport const grepTool: AgentTool<typeof grepSchema> = {\n\tname: \"grep\",\n\tlabel: \"grep\",\n\tdescription:\n\t\t\"Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore.\",\n\tparameters: grepSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{\n\t\t\tpattern,\n\t\t\tpath: searchDir,\n\t\t\tglob,\n\t\t\tignoreCase,\n\t\t\tliteral,\n\t\t\tcontext,\n\t\t\tlimit,\n\t\t}: {\n\t\t\tpattern: string;\n\t\t\tpath?: string;\n\t\t\tglob?: string;\n\t\t\tignoreCase?: boolean;\n\t\t\tliteral?: boolean;\n\t\t\tcontext?: number;\n\t\t\tlimit?: number;\n\t\t},\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (signal?.aborted) {\n\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet settled = false;\n\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\tif (!settled) {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tfn();\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst rgPath = await ensureTool(\"rg\", true);\n\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\tsettle(() => reject(new Error(\"ripgrep (rg) is not available and could not be downloaded\")));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst searchPath = path.resolve(expandPath(searchDir || \".\"));\n\t\t\t\t\tlet searchStat: Stats;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tsearchStat = statSync(searchPath);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst isDirectory = searchStat.isDirectory();\n\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\n\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t};\n\n\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\tconst getFileLines = (filePath: string): string[] => {\n\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst content = readFileSync(filePath, \"utf-8\");\n\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn lines;\n\t\t\t\t\t};\n\n\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\n\t\t\t\t\tif (ignoreCase) {\n\t\t\t\t\t\targs.push(\"--ignore-case\");\n\t\t\t\t\t}\n\n\t\t\t\t\tif (literal) {\n\t\t\t\t\t\targs.push(\"--fixed-strings\");\n\t\t\t\t\t}\n\n\t\t\t\t\tif (glob) {\n\t\t\t\t\t\targs.push(\"--glob\", glob);\n\t\t\t\t\t}\n\n\t\t\t\t\targs.push(pattern, searchPath);\n\n\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\tlet truncated = false;\n\t\t\t\t\tlet aborted = false;\n\t\t\t\t\tlet killedDueToLimit = false;\n\t\t\t\t\tconst outputLines: string[] = [];\n\n\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\trl.close();\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t};\n\n\t\t\t\t\tconst stopChild = (dueToLimit: boolean = false) => {\n\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\tstopChild();\n\t\t\t\t\t};\n\n\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t});\n\n\t\t\t\t\tconst formatBlock = (filePath: string, lineNumber: number) => {\n\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\tconst lines = getFileLines(filePath);\n\t\t\t\t\t\tif (!lines.length) {\n\t\t\t\t\t\t\treturn [`${relativePath}:${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\n\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\n\t\t\t\t\t\t\tif (isMatchLine) {\n\t\t\t\t\t\t\t\tblock.push(`${relativePath}:${current}: ${sanitized}`);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tblock.push(`${relativePath}-${current}- ${sanitized}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn block;\n\t\t\t\t\t};\n\n\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\n\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\") {\n\t\t\t\t\t\t\t\toutputLines.push(...formatBlock(filePath, lineNumber));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\ttruncated = true;\n\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t});\n\n\t\t\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\t\t\tcleanup();\n\n\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\tconst errorMsg = stderr.trim() || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (matchCount === 0) {\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet output = outputLines.join(\"\\n\");\n\t\t\t\t\t\tif (truncated) {\n\t\t\t\t\t\t\toutput += `\\n\\n(truncated, limit of ${effectiveLimit} matches reached)`;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tsettle(() => resolve({ content: [{ type: \"text\", text: output }], details: undefined }));\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t}\n\t\t\t})();\n\t\t});\n\t},\n};\n"]}
@@ -1,5 +1,8 @@
1
1
  export { bashTool } from "./bash.js";
2
2
  export { editTool } from "./edit.js";
3
+ export { findTool } from "./find.js";
4
+ export { grepTool } from "./grep.js";
5
+ export { lsTool } from "./ls.js";
3
6
  export { readTool } from "./read.js";
4
7
  export { writeTool } from "./write.js";
5
8
  export declare const codingTools: (import("../../../ai/dist/index.js").AgentTool<import("@sinclair/typebox").TObject<{
@@ -17,4 +20,43 @@ export declare const codingTools: (import("../../../ai/dist/index.js").AgentTool
17
20
  path: import("@sinclair/typebox").TString;
18
21
  content: import("@sinclair/typebox").TString;
19
22
  }>, any>)[];
23
+ export declare const allTools: {
24
+ read: import("../../../ai/dist/index.js").AgentTool<import("@sinclair/typebox").TObject<{
25
+ path: import("@sinclair/typebox").TString;
26
+ offset: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
27
+ limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
28
+ }>, any>;
29
+ bash: import("../../../ai/dist/index.js").AgentTool<import("@sinclair/typebox").TObject<{
30
+ command: import("@sinclair/typebox").TString;
31
+ timeout: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
32
+ }>, any>;
33
+ edit: import("../../../ai/dist/index.js").AgentTool<import("@sinclair/typebox").TObject<{
34
+ path: import("@sinclair/typebox").TString;
35
+ oldText: import("@sinclair/typebox").TString;
36
+ newText: import("@sinclair/typebox").TString;
37
+ }>, any>;
38
+ write: import("../../../ai/dist/index.js").AgentTool<import("@sinclair/typebox").TObject<{
39
+ path: import("@sinclair/typebox").TString;
40
+ content: import("@sinclair/typebox").TString;
41
+ }>, any>;
42
+ grep: import("../../../ai/dist/index.js").AgentTool<import("@sinclair/typebox").TObject<{
43
+ pattern: import("@sinclair/typebox").TString;
44
+ path: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
45
+ glob: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
46
+ ignoreCase: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
47
+ literal: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
48
+ context: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
49
+ limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
50
+ }>, any>;
51
+ find: import("../../../ai/dist/index.js").AgentTool<import("@sinclair/typebox").TObject<{
52
+ pattern: import("@sinclair/typebox").TString;
53
+ path: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
54
+ limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
55
+ }>, any>;
56
+ ls: import("../../../ai/dist/index.js").AgentTool<import("@sinclair/typebox").TObject<{
57
+ path: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
58
+ limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
59
+ }>, any>;
60
+ };
61
+ export type ToolName = keyof typeof allTools;
20
62
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAOvC,eAAO,MAAM,WAAW;;;;;;;;;;;;;;WAA4C,CAAC","sourcesContent":["export { bashTool } from \"./bash.js\";\nexport { editTool } from \"./edit.js\";\nexport { readTool } from \"./read.js\";\nexport { writeTool } from \"./write.js\";\n\nimport { bashTool } from \"./bash.js\";\nimport { editTool } from \"./edit.js\";\nimport { readTool } from \"./read.js\";\nimport { writeTool } from \"./write.js\";\n\nexport const codingTools = [readTool, bashTool, editTool, writeTool];\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAWvC,eAAO,MAAM,WAAW;;;;;;;;;;;;;;WAA4C,CAAC;AAGrE,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAQpB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,QAAQ,CAAC","sourcesContent":["export { bashTool } from \"./bash.js\";\nexport { editTool } from \"./edit.js\";\nexport { findTool } from \"./find.js\";\nexport { grepTool } from \"./grep.js\";\nexport { lsTool } from \"./ls.js\";\nexport { readTool } from \"./read.js\";\nexport { writeTool } from \"./write.js\";\n\nimport { bashTool } from \"./bash.js\";\nimport { editTool } from \"./edit.js\";\nimport { findTool } from \"./find.js\";\nimport { grepTool } from \"./grep.js\";\nimport { lsTool } from \"./ls.js\";\nimport { readTool } from \"./read.js\";\nimport { writeTool } from \"./write.js\";\n\n// Default tools for full access mode\nexport const codingTools = [readTool, bashTool, editTool, writeTool];\n\n// All available tools (including read-only exploration tools)\nexport const allTools = {\n\tread: readTool,\n\tbash: bashTool,\n\tedit: editTool,\n\twrite: writeTool,\n\tgrep: grepTool,\n\tfind: findTool,\n\tls: lsTool,\n};\n\nexport type ToolName = keyof typeof allTools;\n"]}
@@ -1,10 +1,27 @@
1
1
  export { bashTool } from "./bash.js";
2
2
  export { editTool } from "./edit.js";
3
+ export { findTool } from "./find.js";
4
+ export { grepTool } from "./grep.js";
5
+ export { lsTool } from "./ls.js";
3
6
  export { readTool } from "./read.js";
4
7
  export { writeTool } from "./write.js";
5
8
  import { bashTool } from "./bash.js";
6
9
  import { editTool } from "./edit.js";
10
+ import { findTool } from "./find.js";
11
+ import { grepTool } from "./grep.js";
12
+ import { lsTool } from "./ls.js";
7
13
  import { readTool } from "./read.js";
8
14
  import { writeTool } from "./write.js";
15
+ // Default tools for full access mode
9
16
  export const codingTools = [readTool, bashTool, editTool, writeTool];
17
+ // All available tools (including read-only exploration tools)
18
+ export const allTools = {
19
+ read: readTool,
20
+ bash: bashTool,
21
+ edit: editTool,
22
+ write: writeTool,
23
+ grep: grepTool,
24
+ find: findTool,
25
+ ls: lsTool,
26
+ };
10
27
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC","sourcesContent":["export { bashTool } from \"./bash.js\";\nexport { editTool } from \"./edit.js\";\nexport { readTool } from \"./read.js\";\nexport { writeTool } from \"./write.js\";\n\nimport { bashTool } from \"./bash.js\";\nimport { editTool } from \"./edit.js\";\nimport { readTool } from \"./read.js\";\nimport { writeTool } from \"./write.js\";\n\nexport const codingTools = [readTool, bashTool, editTool, writeTool];\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,qCAAqC;AACrC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAErE,8DAA8D;AAC9D,MAAM,CAAC,MAAM,QAAQ,GAAG;IACvB,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,QAAQ;IACd,EAAE,EAAE,MAAM;CACV,CAAC","sourcesContent":["export { bashTool } from \"./bash.js\";\nexport { editTool } from \"./edit.js\";\nexport { findTool } from \"./find.js\";\nexport { grepTool } from \"./grep.js\";\nexport { lsTool } from \"./ls.js\";\nexport { readTool } from \"./read.js\";\nexport { writeTool } from \"./write.js\";\n\nimport { bashTool } from \"./bash.js\";\nimport { editTool } from \"./edit.js\";\nimport { findTool } from \"./find.js\";\nimport { grepTool } from \"./grep.js\";\nimport { lsTool } from \"./ls.js\";\nimport { readTool } from \"./read.js\";\nimport { writeTool } from \"./write.js\";\n\n// Default tools for full access mode\nexport const codingTools = [readTool, bashTool, editTool, writeTool];\n\n// All available tools (including read-only exploration tools)\nexport const allTools = {\n\tread: readTool,\n\tbash: bashTool,\n\tedit: editTool,\n\twrite: writeTool,\n\tgrep: grepTool,\n\tfind: findTool,\n\tls: lsTool,\n};\n\nexport type ToolName = keyof typeof allTools;\n"]}
@@ -0,0 +1,8 @@
1
+ import type { AgentTool } from "@mariozechner/pi-ai";
2
+ declare const lsSchema: import("@sinclair/typebox").TObject<{
3
+ path: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
4
+ limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
5
+ }>;
6
+ export declare const lsTool: AgentTool<typeof lsSchema>;
7
+ export {};
8
+ //# sourceMappingURL=ls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ls.d.ts","sourceRoot":"","sources":["../../src/tools/ls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAmBrD,QAAA,MAAM,QAAQ;;;EAGZ,CAAC;AAIH,eAAO,MAAM,MAAM,EAAE,SAAS,CAAC,OAAO,QAAQ,CAyF7C,CAAC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { existsSync, readdirSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport nodePath from \"path\";\n\n/**\n * Expand ~ to home directory\n */\nfunction expandPath(filePath: string): string {\n\tif (filePath === \"~\") {\n\t\treturn homedir();\n\t}\n\tif (filePath.startsWith(\"~/\")) {\n\t\treturn homedir() + filePath.slice(1);\n\t}\n\treturn filePath;\n}\n\nconst lsSchema = Type.Object({\n\tpath: Type.Optional(Type.String({ description: \"Directory to list (default: current directory)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of entries to return (default: 500)\" })),\n});\n\nconst DEFAULT_LIMIT = 500;\n\nexport const lsTool: AgentTool<typeof lsSchema> = {\n\tname: \"ls\",\n\tlabel: \"ls\",\n\tdescription:\n\t\t\"List directory contents. Returns entries sorted alphabetically, with '/' suffix for directories. Includes dotfiles.\",\n\tparameters: lsSchema,\n\texecute: async (_toolCallId: string, { path, limit }: { path?: string; limit?: number }, signal?: AbortSignal) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (signal?.aborted) {\n\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst onAbort = () => reject(new Error(\"Operation aborted\"));\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\ttry {\n\t\t\t\tconst dirPath = nodePath.resolve(expandPath(path || \".\"));\n\t\t\t\tconst effectiveLimit = limit ?? DEFAULT_LIMIT;\n\n\t\t\t\t// Check if path exists\n\t\t\t\tif (!existsSync(dirPath)) {\n\t\t\t\t\treject(new Error(`Path not found: ${dirPath}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Check if path is a directory\n\t\t\t\tconst stat = statSync(dirPath);\n\t\t\t\tif (!stat.isDirectory()) {\n\t\t\t\t\treject(new Error(`Not a directory: ${dirPath}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Read directory entries\n\t\t\t\tlet entries: string[];\n\t\t\t\ttry {\n\t\t\t\t\tentries = readdirSync(dirPath);\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\treject(new Error(`Cannot read directory: ${e.message}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Sort alphabetically (case-insensitive)\n\t\t\t\tentries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));\n\n\t\t\t\t// Format entries with directory indicators\n\t\t\t\tconst results: string[] = [];\n\t\t\t\tlet truncated = false;\n\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tif (results.length >= effectiveLimit) {\n\t\t\t\t\t\ttruncated = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst fullPath = nodePath.join(dirPath, entry);\n\t\t\t\t\tlet suffix = \"\";\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst entryStat = statSync(fullPath);\n\t\t\t\t\t\tif (entryStat.isDirectory()) {\n\t\t\t\t\t\t\tsuffix = \"/\";\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Skip entries we can't stat\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tresults.push(entry + suffix);\n\t\t\t\t}\n\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\n\t\t\t\tlet output = results.join(\"\\n\");\n\t\t\t\tif (truncated) {\n\t\t\t\t\tconst remaining = entries.length - effectiveLimit;\n\t\t\t\t\toutput += `\\n\\n(truncated, ${remaining} more entries)`;\n\t\t\t\t}\n\t\t\t\tif (results.length === 0) {\n\t\t\t\t\toutput = \"(empty directory)\";\n\t\t\t\t}\n\n\t\t\t\tresolve({ content: [{ type: \"text\", text: output }], details: undefined });\n\t\t\t} catch (e: any) {\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\treject(e);\n\t\t\t}\n\t\t});\n\t},\n};\n"]}