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

110 lines
2.7 KiB
TypeScript
Raw Normal View History

2025-05-31 18:41:00 +00:00
import { z } from "zod"
import { Tool } from "./tool"
import { App } from "../app/app"
import * as path from "path"
2025-06-04 17:12:13 +00:00
import DESCRIPTION from "./ls.txt"
2025-05-26 17:44:59 +00:00
2025-05-30 20:39:45 +00:00
const IGNORE_PATTERNS = [
"node_modules/",
"__pycache__/",
".git/",
"dist/",
"build/",
"target/",
"vendor/",
"bin/",
"obj/",
".idea/",
".vscode/",
2025-05-31 18:41:00 +00:00
]
2025-05-26 17:44:59 +00:00
2025-05-31 21:12:16 +00:00
export const ListTool = Tool.define({
id: "opencode.list",
2025-06-04 17:12:13 +00:00
description: DESCRIPTION,
2025-05-26 17:44:59 +00:00
parameters: z.object({
2025-06-10 00:24:18 +00:00
path: z
.string()
.describe(
"The absolute path to the directory to list (must be absolute, not relative)",
)
.nullable(),
ignore: z
.array(z.string())
.describe("List of glob patterns to ignore")
.nullable(),
2025-05-26 17:44:59 +00:00
}),
async execute(params) {
const app = App.info()
const searchPath = path.resolve(app.path.cwd, params.path || ".")
2025-05-26 17:44:59 +00:00
2025-05-31 18:41:00 +00:00
const glob = new Bun.Glob("**/*")
const files = []
2025-05-26 17:44:59 +00:00
2025-05-30 20:39:45 +00:00
for await (const file of glob.scan({ cwd: searchPath })) {
if (file.startsWith(".") || IGNORE_PATTERNS.some((p) => file.includes(p)))
2025-05-31 18:41:00 +00:00
continue
2025-05-30 20:39:45 +00:00
if (params.ignore?.some((pattern) => new Bun.Glob(pattern).match(file)))
2025-05-31 18:41:00 +00:00
continue
files.push(file)
if (files.length >= 1000) 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 {
2025-06-11 16:58:06 +00:00
metadata: {
count: files.length,
truncated: files.length >= 1000,
title: path.relative(app.path.root, searchPath),
},
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
})