diff --git a/packages/opencode/src/cli/cmd/debug/file.ts b/packages/opencode/src/cli/cmd/debug/file.ts index f773dbd9..74fb1936 100644 --- a/packages/opencode/src/cli/cmd/debug/file.ts +++ b/packages/opencode/src/cli/cmd/debug/file.ts @@ -29,8 +29,25 @@ const FileStatusCommand = cmd({ }, }) +const FileListCommand = cmd({ + command: "list ", + builder: (yargs) => + yargs.positional("path", { + type: "string", + demandOption: true, + description: "File path to list", + }), + async handler(args) { + await bootstrap({ cwd: process.cwd() }, async () => { + const files = await File.list(args.path) + console.log(JSON.stringify(files, null, 2)) + }) + }, +}) + export const FileCommand = cmd({ command: "file", - builder: (yargs) => yargs.command(FileReadCommand).command(FileStatusCommand).demandCommand(), + builder: (yargs) => + yargs.command(FileReadCommand).command(FileStatusCommand).command(FileListCommand).demandCommand(), async handler() {}, }) diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index b99f35e1..7613e920 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -24,6 +24,17 @@ export namespace File { export type Info = z.infer + export const Node = z + .object({ + name: z.string(), + path: z.string(), + type: z.enum(["file", "directory"]), + }) + .openapi({ + ref: "FileNode", + }) + export type Node = z.infer + export const Event = { Edited: Bus.event( "file.edited", @@ -120,4 +131,27 @@ export namespace File { } return { type: "raw", content } } + + export async function list(dir?: string) { + const ignore = [".git", ".DS_Store"] + const app = App.info() + const resolved = dir ? path.join(app.path.cwd, dir) : app.path.cwd + const nodes: Node[] = [] + for (const entry of await fs.promises.readdir(resolved, { withFileTypes: true })) { + if (ignore.includes(entry.name)) continue + const fullPath = path.join(resolved, entry.name) + const relativePath = path.relative(app.path.cwd, fullPath) + nodes.push({ + name: entry.name, + path: relativePath, + type: entry.isDirectory() ? "directory" : "file", + }) + } + return nodes.sort((a, b) => { + if (a.type !== b.type) { + return a.type === "directory" ? -1 : 1 + } + return a.name.localeCompare(b.name) + }) + } } diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 631c9ab4..27d4aa92 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -937,6 +937,34 @@ export namespace Server { ) .get( "/file", + describeRoute({ + description: "List files and directories", + operationId: "file.list", + responses: { + 200: { + description: "Files and directories", + content: { + "application/json": { + schema: resolver(File.Node.array()), + }, + }, + }, + }, + }), + zValidator( + "query", + z.object({ + path: z.string(), + }), + ), + async (c) => { + const path = c.req.valid("query").path + const content = await File.list(path) + return c.json(content) + }, + ) + .get( + "/file/content", describeRoute({ description: "Read a file", operationId: "file.read", @@ -965,10 +993,6 @@ export namespace Server { async (c) => { const path = c.req.valid("query").path const content = await File.read(path) - log.info("read file", { - path, - content: content.content, - }) return c.json(content) }, ) diff --git a/packages/sdk/js/src/gen/sdk.gen.ts b/packages/sdk/js/src/gen/sdk.gen.ts index f900c24f..a50f5e14 100644 --- a/packages/sdk/js/src/gen/sdk.gen.ts +++ b/packages/sdk/js/src/gen/sdk.gen.ts @@ -59,6 +59,8 @@ import type { FindFilesResponses, FindSymbolsData, FindSymbolsResponses, + FileListData, + FileListResponses, FileReadData, FileReadResponses, FileStatusData, @@ -457,12 +459,22 @@ class Find extends _HeyApiClient { } class File extends _HeyApiClient { + /** + * List files and directories + */ + public list(options: Options) { + return (options.client ?? this._client).get({ + url: "/file", + ...options, + }) + } + /** * Read a file */ public read(options: Options) { return (options.client ?? this._client).get({ - url: "/file", + url: "/file/content", ...options, }) } diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 1c4fc5b3..5043f968 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -1138,6 +1138,12 @@ export type Symbol = { } } +export type FileNode = { + name: string + path: string + type: "file" | "directory" +} + export type File = { path: string added: number @@ -1804,7 +1810,7 @@ export type FindSymbolsResponses = { export type FindSymbolsResponse = FindSymbolsResponses[keyof FindSymbolsResponses] -export type FileReadData = { +export type FileListData = { body?: never path?: never query: { @@ -1813,6 +1819,24 @@ export type FileReadData = { url: "/file" } +export type FileListResponses = { + /** + * Files and directories + */ + 200: Array +} + +export type FileListResponse = FileListResponses[keyof FileListResponses] + +export type FileReadData = { + body?: never + path?: never + query: { + path: string + } + url: "/file/content" +} + export type FileReadResponses = { /** * File content diff --git a/packages/sdk/stainless/stainless.yml b/packages/sdk/stainless/stainless.yml index 3dd34a41..f829e228 100644 --- a/packages/sdk/stainless/stainless.yml +++ b/packages/sdk/stainless/stainless.yml @@ -48,7 +48,6 @@ resources: app: models: app: App - logLevel: LogLevel provider: Provider model: Model agent: Agent @@ -61,7 +60,6 @@ resources: find: models: - match: Match symbol: Symbol methods: text: get /find @@ -71,8 +69,11 @@ resources: file: models: file: File + fileNode: FileNode + methods: - read: get /file + list: get /file + read: get /file/content status: get /file/status config: