ls.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import { z } from "zod"
  2. import { Tool } from "./tool"
  3. import * as path from "path"
  4. import DESCRIPTION from "./ls.txt"
  5. import { Instance } from "../project/instance"
  6. import { Ripgrep } from "../file/ripgrep"
  7. export const IGNORE_PATTERNS = [
  8. "node_modules/",
  9. "__pycache__/",
  10. ".git/",
  11. "dist/",
  12. "build/",
  13. "target/",
  14. "vendor/",
  15. "bin/",
  16. "obj/",
  17. ".idea/",
  18. ".vscode/",
  19. ".zig-cache/",
  20. "zig-out",
  21. ".coverage",
  22. "coverage/",
  23. "vendor/",
  24. "tmp/",
  25. "temp/",
  26. ".cache/",
  27. "cache/",
  28. "logs/",
  29. ".venv/",
  30. "venv/",
  31. "env/",
  32. ]
  33. const LIMIT = 100
  34. export const ListTool = Tool.define("list", {
  35. description: DESCRIPTION,
  36. parameters: z.object({
  37. path: z.string().describe("The absolute path to the directory to list (must be absolute, not relative)").optional(),
  38. ignore: z.array(z.string()).describe("List of glob patterns to ignore").optional(),
  39. }),
  40. async execute(params) {
  41. const searchPath = path.resolve(Instance.directory, params.path || ".")
  42. const ignoreGlobs = IGNORE_PATTERNS.map((p) => `!${p}*`).concat(params.ignore?.map((p) => `!${p}`) || [])
  43. const files = await Ripgrep.files({ cwd: searchPath, glob: ignoreGlobs, limit: LIMIT })
  44. // Build directory structure
  45. const dirs = new Set<string>()
  46. const filesByDir = new Map<string, string[]>()
  47. for (const file of files) {
  48. const dir = path.dirname(file)
  49. const parts = dir === "." ? [] : dir.split("/")
  50. // Add all parent directories
  51. for (let i = 0; i <= parts.length; i++) {
  52. const dirPath = i === 0 ? "." : parts.slice(0, i).join("/")
  53. dirs.add(dirPath)
  54. }
  55. // Add file to its directory
  56. if (!filesByDir.has(dir)) filesByDir.set(dir, [])
  57. filesByDir.get(dir)!.push(path.basename(file))
  58. }
  59. function renderDir(dirPath: string, depth: number): string {
  60. const indent = " ".repeat(depth)
  61. let output = ""
  62. if (depth > 0) {
  63. output += `${indent}${path.basename(dirPath)}/\n`
  64. }
  65. const childIndent = " ".repeat(depth + 1)
  66. const children = Array.from(dirs)
  67. .filter((d) => path.dirname(d) === dirPath && d !== dirPath)
  68. .sort()
  69. // Render subdirectories first
  70. for (const child of children) {
  71. output += renderDir(child, depth + 1)
  72. }
  73. // Render files
  74. const files = filesByDir.get(dirPath) || []
  75. for (const file of files.sort()) {
  76. output += `${childIndent}${file}\n`
  77. }
  78. return output
  79. }
  80. const output = `${searchPath}/\n` + renderDir(".", 0)
  81. return {
  82. title: path.relative(Instance.worktree, searchPath),
  83. metadata: {
  84. count: files.length,
  85. truncated: files.length >= LIMIT,
  86. },
  87. output,
  88. }
  89. },
  90. })