1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
import { z } from "zod";
import { Tool } from "./tool";
import { App } from "../app/app";
import * as path from "path";
const IGNORE_PATTERNS = [
"node_modules/",
"__pycache__/",
".git/",
"dist/",
"build/",
"target/",
"vendor/",
"bin/",
"obj/",
".idea/",
".vscode/",
];
export const ls = Tool.define({
name: "opencode.ls",
description: "List directory contents",
parameters: z.object({
path: z.string().optional(),
ignore: z.array(z.string()).optional(),
}),
async execute(params) {
const app = await App.use();
const searchPath = path.resolve(app.root, params.path || ".");
const glob = new Bun.Glob("**/*");
const files = [];
for await (const file of glob.scan({ cwd: searchPath })) {
if (file.startsWith(".") || IGNORE_PATTERNS.some((p) => file.includes(p)))
continue;
if (params.ignore?.some((pattern) => new Bun.Glob(pattern).match(file)))
continue;
files.push(file);
if (files.length >= 1000) break;
}
// Build directory structure
const dirs = new Set<string>();
const filesByDir = new Map<string, string[]>();
for (const file of files) {
const dir = path.dirname(file);
const parts = dir === "." ? [] : dir.split("/");
// Add all parent directories
for (let i = 0; i <= parts.length; i++) {
const dirPath = i === 0 ? "." : parts.slice(0, i).join("/");
dirs.add(dirPath);
}
// Add file to its directory
if (!filesByDir.has(dir)) filesByDir.set(dir, []);
filesByDir.get(dir)!.push(path.basename(file));
}
function renderDir(dirPath: string, depth: number): string {
const indent = " ".repeat(depth);
let output = "";
if (depth > 0) {
output += `${indent}${path.basename(dirPath)}/\n`;
}
const childIndent = " ".repeat(depth + 1);
const children = Array.from(dirs)
.filter((d) => path.dirname(d) === dirPath && d !== dirPath)
.sort();
// Render subdirectories first
for (const child of children) {
output += renderDir(child, depth + 1);
}
// Render files
const files = filesByDir.get(dirPath) || [];
for (const file of files.sort()) {
output += `${childIndent}${file}\n`;
}
return output;
}
const output = `${searchPath}/\n` + renderDir(".", 0);
return {
metadata: { count: files.length, truncated: files.length >= 1000 },
output,
};
},
});
|