glob.ts 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import z from "zod"
  2. import path from "path"
  3. import { Tool } from "./tool"
  4. import DESCRIPTION from "./glob.txt"
  5. import { Ripgrep } from "../file/ripgrep"
  6. import { Instance } from "../project/instance"
  7. import { assertExternalDirectory } from "./external-directory"
  8. export const GlobTool = Tool.define("glob", {
  9. description: DESCRIPTION,
  10. parameters: z.object({
  11. pattern: z.string().describe("The glob pattern to match files against"),
  12. path: z
  13. .string()
  14. .optional()
  15. .describe(
  16. `The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.`,
  17. ),
  18. }),
  19. async execute(params, ctx) {
  20. await ctx.ask({
  21. permission: "glob",
  22. patterns: [params.pattern],
  23. always: ["*"],
  24. metadata: {
  25. pattern: params.pattern,
  26. path: params.path,
  27. },
  28. })
  29. let search = params.path ?? Instance.directory
  30. search = path.isAbsolute(search) ? search : path.resolve(Instance.directory, search)
  31. await assertExternalDirectory(ctx, search, { kind: "directory" })
  32. const limit = 100
  33. const files = []
  34. let truncated = false
  35. for await (const file of Ripgrep.files({
  36. cwd: search,
  37. glob: [params.pattern],
  38. })) {
  39. if (files.length >= limit) {
  40. truncated = true
  41. break
  42. }
  43. const full = path.resolve(search, file)
  44. const stats = await Bun.file(full)
  45. .stat()
  46. .then((x) => x.mtime.getTime())
  47. .catch(() => 0)
  48. files.push({
  49. path: full,
  50. mtime: stats,
  51. })
  52. }
  53. files.sort((a, b) => b.mtime - a.mtime)
  54. const output = []
  55. if (files.length === 0) output.push("No files found")
  56. if (files.length > 0) {
  57. output.push(...files.map((f) => f.path))
  58. if (truncated) {
  59. output.push("")
  60. output.push("(Results are truncated. Consider using a more specific path or pattern.)")
  61. }
  62. }
  63. return {
  64. title: path.relative(Instance.worktree, search),
  65. metadata: {
  66. count: files.length,
  67. truncated,
  68. },
  69. output: output.join("\n"),
  70. }
  71. },
  72. })