@mariozechner/pi-coding-agent 0.10.1 → 0.11.0

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.
@@ -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"]}
@@ -0,0 +1,100 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { existsSync, readdirSync, statSync } from "fs";
3
+ import { homedir } from "os";
4
+ import nodePath from "path";
5
+ /**
6
+ * Expand ~ to home directory
7
+ */
8
+ function expandPath(filePath) {
9
+ if (filePath === "~") {
10
+ return homedir();
11
+ }
12
+ if (filePath.startsWith("~/")) {
13
+ return homedir() + filePath.slice(1);
14
+ }
15
+ return filePath;
16
+ }
17
+ const lsSchema = Type.Object({
18
+ path: Type.Optional(Type.String({ description: "Directory to list (default: current directory)" })),
19
+ limit: Type.Optional(Type.Number({ description: "Maximum number of entries to return (default: 500)" })),
20
+ });
21
+ const DEFAULT_LIMIT = 500;
22
+ export const lsTool = {
23
+ name: "ls",
24
+ label: "ls",
25
+ description: "List directory contents. Returns entries sorted alphabetically, with '/' suffix for directories. Includes dotfiles.",
26
+ parameters: lsSchema,
27
+ execute: async (_toolCallId, { path, limit }, signal) => {
28
+ return new Promise((resolve, reject) => {
29
+ if (signal?.aborted) {
30
+ reject(new Error("Operation aborted"));
31
+ return;
32
+ }
33
+ const onAbort = () => reject(new Error("Operation aborted"));
34
+ signal?.addEventListener("abort", onAbort, { once: true });
35
+ try {
36
+ const dirPath = nodePath.resolve(expandPath(path || "."));
37
+ const effectiveLimit = limit ?? DEFAULT_LIMIT;
38
+ // Check if path exists
39
+ if (!existsSync(dirPath)) {
40
+ reject(new Error(`Path not found: ${dirPath}`));
41
+ return;
42
+ }
43
+ // Check if path is a directory
44
+ const stat = statSync(dirPath);
45
+ if (!stat.isDirectory()) {
46
+ reject(new Error(`Not a directory: ${dirPath}`));
47
+ return;
48
+ }
49
+ // Read directory entries
50
+ let entries;
51
+ try {
52
+ entries = readdirSync(dirPath);
53
+ }
54
+ catch (e) {
55
+ reject(new Error(`Cannot read directory: ${e.message}`));
56
+ return;
57
+ }
58
+ // Sort alphabetically (case-insensitive)
59
+ entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
60
+ // Format entries with directory indicators
61
+ const results = [];
62
+ let truncated = false;
63
+ for (const entry of entries) {
64
+ if (results.length >= effectiveLimit) {
65
+ truncated = true;
66
+ break;
67
+ }
68
+ const fullPath = nodePath.join(dirPath, entry);
69
+ let suffix = "";
70
+ try {
71
+ const entryStat = statSync(fullPath);
72
+ if (entryStat.isDirectory()) {
73
+ suffix = "/";
74
+ }
75
+ }
76
+ catch {
77
+ // Skip entries we can't stat
78
+ continue;
79
+ }
80
+ results.push(entry + suffix);
81
+ }
82
+ signal?.removeEventListener("abort", onAbort);
83
+ let output = results.join("\n");
84
+ if (truncated) {
85
+ const remaining = entries.length - effectiveLimit;
86
+ output += `\n\n(truncated, ${remaining} more entries)`;
87
+ }
88
+ if (results.length === 0) {
89
+ output = "(empty directory)";
90
+ }
91
+ resolve({ content: [{ type: "text", text: output }], details: undefined });
92
+ }
93
+ catch (e) {
94
+ signal?.removeEventListener("abort", onAbort);
95
+ reject(e);
96
+ }
97
+ });
98
+ },
99
+ };
100
+ //# sourceMappingURL=ls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ls.js","sourceRoot":"","sources":["../../src/tools/ls.ts"],"names":[],"mappings":"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","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"]}
@@ -0,0 +1,3 @@
1
+ export declare function getToolPath(tool: "fd" | "rg"): string | null;
2
+ export declare function ensureTool(tool: "fd" | "rg", silent?: boolean): Promise<string | null>;
3
+ //# sourceMappingURL=tools-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools-manager.d.ts","sourceRoot":"","sources":["../src/tools-manager.ts"],"names":[],"mappings":"AAyEA,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAgB5D;AAgGD,wBAAsB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0BnG","sourcesContent":["import chalk from \"chalk\";\nimport { spawnSync } from \"child_process\";\nimport { chmodSync, createWriteStream, existsSync, mkdirSync, renameSync, rmSync } from \"fs\";\nimport { arch, homedir, platform } from \"os\";\nimport { join } from \"path\";\nimport { Readable } from \"stream\";\nimport { finished } from \"stream/promises\";\n\nconst TOOLS_DIR = join(homedir(), \".pi\", \"agent\", \"tools\");\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n\tgetAssetName: (version: string, plat: string, architecture: string) => string | null;\n}\n\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\ttagPrefix: \"v\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tif (architecture === \"arm64\") {\n\t\t\t\t\treturn `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`;\n\t\t\t\t}\n\t\t\t\treturn `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Get the path to a tool (system-wide or in our tools dir)\nexport function getToolPath(tool: \"fd\" | \"rg\"): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Check our tools directory first\n\tconst localPath = join(TOOLS_DIR, config.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"));\n\tif (existsSync(localPath)) {\n\t\treturn localPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tif (commandExists(config.binaryName)) {\n\t\treturn config.binaryName;\n\t}\n\n\treturn null;\n}\n\n// Fetch latest release version from GitHub\nasync function getLatestVersion(repo: string): Promise<string> {\n\tconst response = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {\n\t\theaders: { \"User-Agent\": \"pi-coding-agent\" },\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`GitHub API error: ${response.status}`);\n\t}\n\n\tconst data = (await response.json()) as { tag_name: string };\n\treturn data.tag_name.replace(/^v/, \"\");\n}\n\n// Download a file from URL\nasync function downloadFile(url: string, dest: string): Promise<void> {\n\tconst response = await fetch(url);\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new Error(\"No response body\");\n\t}\n\n\tconst fileStream = createWriteStream(dest);\n\tawait finished(Readable.fromWeb(response.body as any).pipe(fileStream));\n}\n\n// Download and install a tool\nasync function downloadTool(tool: \"fd\" | \"rg\"): Promise<string> {\n\tconst config = TOOLS[tool];\n\tif (!config) throw new Error(`Unknown tool: ${tool}`);\n\n\tconst plat = platform();\n\tconst architecture = arch();\n\n\t// Get latest version\n\tconst version = await getLatestVersion(config.repo);\n\n\t// Get asset name for this platform\n\tconst assetName = config.getAssetName(version, plat, architecture);\n\tif (!assetName) {\n\t\tthrow new Error(`Unsupported platform: ${plat}/${architecture}`);\n\t}\n\n\t// Create tools directory\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\tconst downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;\n\tconst archivePath = join(TOOLS_DIR, assetName);\n\tconst binaryExt = plat === \"win32\" ? \".exe\" : \"\";\n\tconst binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);\n\n\t// Download\n\tawait downloadFile(downloadUrl, archivePath);\n\n\t// Extract\n\tconst extractDir = join(TOOLS_DIR, \"extract_tmp\");\n\tmkdirSync(extractDir, { recursive: true });\n\n\ttry {\n\t\tif (assetName.endsWith(\".tar.gz\")) {\n\t\t\tspawnSync(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir], { stdio: \"pipe\" });\n\t\t} else if (assetName.endsWith(\".zip\")) {\n\t\t\tspawnSync(\"unzip\", [\"-o\", archivePath, \"-d\", extractDir], { stdio: \"pipe\" });\n\t\t}\n\n\t\t// Find the binary in extracted files\n\t\tconst extractedDir = join(extractDir, assetName.replace(/\\.(tar\\.gz|zip)$/, \"\"));\n\t\tconst extractedBinary = join(extractedDir, config.binaryName + binaryExt);\n\n\t\tif (existsSync(extractedBinary)) {\n\t\t\trenameSync(extractedBinary, binaryPath);\n\t\t} else {\n\t\t\tthrow new Error(`Binary not found in archive: ${extractedBinary}`);\n\t\t}\n\n\t\t// Make executable (Unix only)\n\t\tif (plat !== \"win32\") {\n\t\t\tchmodSync(binaryPath, 0o755);\n\t\t}\n\t} finally {\n\t\t// Cleanup\n\t\trmSync(archivePath, { force: true });\n\t\trmSync(extractDir, { recursive: true, force: true });\n\t}\n\n\treturn binaryPath;\n}\n\n// Ensure a tool is available, downloading if necessary\n// Returns the path to the tool, or null if unavailable\nexport async function ensureTool(tool: \"fd\" | \"rg\", silent: boolean = false): Promise<string | null> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Tool not found - download it\n\tif (!silent) {\n\t\tconsole.log(chalk.dim(`${config.name} not found. Downloading...`));\n\t}\n\n\ttry {\n\t\tconst path = await downloadTool(tool);\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.dim(`${config.name} installed to ${path}`));\n\t\t}\n\t\treturn path;\n\t} catch (e) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));\n\t\t}\n\t\treturn null;\n\t}\n}\n"]}