| 15 | include: z.string().optional().describe('File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")'), |
| 16 | }), |
| 17 | async execute(params) { |
| 18 | if (!params.pattern) { |
| 19 | throw new Error("pattern is required") |
| 20 | } |
| 21 | |
| 22 | const searchPath = params.path || Instance.directory |
| 23 | |
| 24 | const rgPath = await Ripgrep.filepath() |
| 25 | const args = ["-nH", "--field-match-separator=|", "--regexp", params.pattern] |
| 26 | if (params.include) { |
| 27 | args.push("--glob", params.include) |
| 28 | } |
| 29 | args.push(searchPath) |
| 30 | |
| 31 | const proc = Bun.spawn([rgPath, ...args], { |
| 32 | stdout: "pipe", |
| 33 | stderr: "pipe", |
| 34 | }) |
| 35 | |
| 36 | const output = await new Response(proc.stdout).text() |
| 37 | const errorOutput = await new Response(proc.stderr).text() |
| 38 | const exitCode = await proc.exited |
| 39 | |
| 40 | if (exitCode === 1) { |
| 41 | return { |
| 42 | title: params.pattern, |
| 43 | metadata: { matches: 0, truncated: false }, |
| 44 | output: "No files found", |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | if (exitCode !== 0) { |
| 49 | throw new Error(`ripgrep failed: ${errorOutput}`) |
| 50 | } |
| 51 | |
| 52 | const lines = output.trim().split("\n") |
| 53 | const matches = [] |
| 54 | |
| 55 | for (const line of lines) { |
| 56 | if (!line) continue |
| 57 | |
| 58 | const [filePath, lineNumStr, ...lineTextParts] = line.split("|") |
| 59 | if (!filePath || !lineNumStr || lineTextParts.length === 0) continue |
| 60 | |
| 61 | const lineNum = parseInt(lineNumStr, 10) |
| 62 | const lineText = lineTextParts.join("|") |
| 63 | |
| 64 | const file = Bun.file(filePath) |
| 65 | const stats = await file.stat().catch(() => null) |
| 66 | if (!stats) continue |
| 67 | |
| 68 | matches.push({ |
| 69 | path: filePath, |
| 70 | modTime: stats.mtime.getTime(), |
| 71 | lineNum, |
| 72 | lineText, |
| 73 | }) |
| 74 | } |