feat: add browsable directory picker for new sessions and project switch

Users had to type an absolute server path on a phone keyboard to pick a
working directory (#49 "Choose project UIX"), and #57 reports that
only the default-drive project is ever discoverable. #52 already added
recents + client.project.list() as flat pickers, but there was still no
way to browse into subdirectories or discover paths the server hadn't
already indexed as a "project" — the only fallback was manual typing.

The opencode server already exposes a scoped filesystem-listing endpoint
(GET /file, handled in file.ts/handlers/file.ts) that resolves relative
to whatever directory the request is scoped to (header or query param) —
no new server endpoint is needed. Add file.list() to the mobile SDK
client and a new DirectoryBrowserSheet that lists subdirectories one
level at a time (via clientForDirectory(dir) + file.list({path: "."})),
supports "up" navigation, and a manual jump-to-path field. Wire it into
both the "new session" modal and the existing DirectorySwitcher, so
recents/manual entry remain available as a fallback alongside browsing.

Residual gap: there's still no "list available drives" API, so Windows
users with projects on D:, E:, etc. still need to type the drive root
once (it's then remembered via recents) — a full fix for #57 would need
a small server-side addition to enumerate mounted volumes.
This commit is contained in:
engineer
2026-07-16 15:48:38 -07:00
parent a5723bf087
commit f0a7c1d868
5 changed files with 436 additions and 9 deletions

View File

@@ -143,6 +143,14 @@ export interface Project {
}
}
export interface FileEntry {
name: string
path: string
absolute: string
type: "file" | "directory"
ignored: boolean
}
export interface Event {
type: string
properties: Record<string, unknown>
@@ -250,6 +258,17 @@ export function createClient(config: ClientConfig) {
current: () => request<Project>(config, "/project/current"),
},
// Server-side filesystem browsing, scoped to this client's directory
// (see ClientConfig.directory / x-opencode-directory header). Use
// clientForDirectory(dir) to get a client rooted at a specific folder,
// then list("." ) to enumerate its immediate children.
file: {
list: (params: { path?: string } = {}) => {
const query = new URLSearchParams({ path: params.path ?? "." })
return request<FileEntry[]>(config, `/file?${query.toString()}`)
},
},
path: {
get: () =>
request<{ home: string; state: string; config: string; worktree: string; directory: string }>(config, "/path"),