cloudaxe-opencode/packages/opencode/src/tool/ls.ts

122 lines
3 KiB
TypeScript
Raw Normal View History

import z from "zod"
2025-05-31 18:41:00 +00:00
import { Tool } from "./tool"
import * as path from "path"
2025-06-04 17:12:13 +00:00
import DESCRIPTION from "./ls.txt"
import { Instance } from "../project/instance"
import { Ripgrep } from "../file/ripgrep"
import { assertExternalDirectory } from "./external-directory"
2025-05-26 17:44:59 +00:00
export const IGNORE_PATTERNS = [
2025-05-30 20:39:45 +00:00
"node_modules/",
"__pycache__/",
".git/",
"dist/",
"build/",
"target/",
"vendor/",
"bin/",
"obj/",
".idea/",
".vscode/",
".zig-cache/",
"zig-out",
".coverage",
"coverage/",
"vendor/",
"tmp/",
"temp/",
".cache/",
"cache/",
"logs/",
".venv/",
"venv/",
"env/",
2025-05-31 18:41:00 +00:00
]
2025-05-26 17:44:59 +00:00
const LIMIT = 100
export const ListTool = Tool.define("list", {
2025-06-04 17:12:13 +00:00
description: DESCRIPTION,
2025-05-26 17:44:59 +00:00
parameters: z.object({
path: z.string().describe("The absolute path to the directory to list (must be absolute, not relative)").optional(),
ignore: z.array(z.string()).describe("List of glob patterns to ignore").optional(),
2025-05-26 17:44:59 +00:00
}),
async execute(params, ctx) {
const searchPath = path.resolve(Instance.directory, params.path || ".")
await assertExternalDirectory(ctx, searchPath, { kind: "directory" })
2025-05-26 17:44:59 +00:00
await ctx.ask({
permission: "list",
patterns: [searchPath],
always: ["*"],
metadata: {
path: searchPath,
},
})
const ignoreGlobs = IGNORE_PATTERNS.map((p) => `!${p}*`).concat(params.ignore?.map((p) => `!${p}`) || [])
const files = []
for await (const file of Ripgrep.files({ cwd: searchPath, glob: ignoreGlobs })) {
files.push(file)
if (files.length >= LIMIT) break
}
2025-05-26 17:44:59 +00:00
2025-05-30 20:39:45 +00:00
// Build directory structure
2025-05-31 18:41:00 +00:00
const dirs = new Set<string>()
const filesByDir = new Map<string, string[]>()
2025-05-26 17:44:59 +00:00
2025-05-30 20:39:45 +00:00
for (const file of files) {
2025-05-31 18:41:00 +00:00
const dir = path.dirname(file)
const parts = dir === "." ? [] : dir.split("/")
2025-05-26 17:44:59 +00:00
2025-05-30 20:39:45 +00:00
// Add all parent directories
for (let i = 0; i <= parts.length; i++) {
2025-05-31 18:41:00 +00:00
const dirPath = i === 0 ? "." : parts.slice(0, i).join("/")
dirs.add(dirPath)
2025-05-26 17:44:59 +00:00
}
2025-05-30 20:39:45 +00:00
// Add file to its directory
2025-05-31 18:41:00 +00:00
if (!filesByDir.has(dir)) filesByDir.set(dir, [])
filesByDir.get(dir)!.push(path.basename(file))
2025-05-26 17:44:59 +00:00
}
2025-05-30 20:39:45 +00:00
function renderDir(dirPath: string, depth: number): string {
2025-05-31 18:41:00 +00:00
const indent = " ".repeat(depth)
let output = ""
2025-05-26 17:44:59 +00:00
2025-05-30 20:39:45 +00:00
if (depth > 0) {
2025-05-31 18:41:00 +00:00
output += `${indent}${path.basename(dirPath)}/\n`
2025-05-26 17:44:59 +00:00
}
2025-05-31 18:41:00 +00:00
const childIndent = " ".repeat(depth + 1)
2025-05-30 20:39:45 +00:00
const children = Array.from(dirs)
.filter((d) => path.dirname(d) === dirPath && d !== dirPath)
2025-05-31 18:41:00 +00:00
.sort()
2025-05-26 17:44:59 +00:00
2025-05-30 20:39:45 +00:00
// Render subdirectories first
for (const child of children) {
2025-05-31 18:41:00 +00:00
output += renderDir(child, depth + 1)
2025-05-30 20:39:45 +00:00
}
2025-05-26 17:44:59 +00:00
2025-05-30 20:39:45 +00:00
// Render files
2025-05-31 18:41:00 +00:00
const files = filesByDir.get(dirPath) || []
2025-05-30 20:39:45 +00:00
for (const file of files.sort()) {
2025-05-31 18:41:00 +00:00
output += `${childIndent}${file}\n`
2025-05-26 17:44:59 +00:00
}
2025-05-31 18:41:00 +00:00
return output
2025-05-26 17:44:59 +00:00
}
2025-05-31 18:41:00 +00:00
const output = `${searchPath}/\n` + renderDir(".", 0)
2025-05-26 17:44:59 +00:00
2025-05-30 20:39:45 +00:00
return {
title: path.relative(Instance.worktree, searchPath),
2025-06-11 16:58:06 +00:00
metadata: {
count: files.length,
truncated: files.length >= LIMIT,
2025-06-11 16:58:06 +00:00
},
2025-05-30 20:39:45 +00:00
output,
2025-05-31 18:41:00 +00:00
}
2025-05-30 20:39:45 +00:00
},
2025-05-31 18:41:00 +00:00
})