forked from bots-garden/ori
Reference: workspace API
Neutral, exhaustive description of the endpoints behind the workspace panel and the composer's selectors: the file API, the skills endpoint and the terminal WebSocket. Relative paths resolve against the server's
--cwd; absolute paths are used as-is (access is unrestricted by design — ori targets already-isolated sandboxes).
GET /api/files
Lists a directory.
| Query parameter | Type | Default | Description |
|---|---|---|---|
path |
string | the workspace root | Directory to list. |
Response 200:
{ "path": "/work", "entries": [ { "name": "src", "path": "/work/src", "isDir": true, "size": 0 } ] }
Entries are sorted directories first, then files, each group alphabetically.
GET /api/files/search
Searches files recursively under the workspace root, for the composer's @ selector.
| Query parameter | Type | Default | Description |
|---|---|---|---|
q |
string | "" |
Case-insensitive substring matched against the path relative to the root. Empty returns the first files. |
limit |
integer | 50 | Maximum number of hits; capped at 500. |
Response 200:
{ "root": "/work", "files": [ { "name": "main.go", "path": "/work/src/main.go", "relPath": "src/main.go" } ] }
path is absolute (usable in the other endpoints), relPath uses forward slashes. Hits are ranked: files whose base name contains the query first, then shorter relative paths, then alphabetically. The walk never enters .git or node_modules, skips unreadable entries, and stops after 50 000 directory entries. files is always an array ([] when nothing matches). A missing root is a 404.
GET /api/file
Reads a text file.
| Query parameter | Type | Default | Description |
|---|---|---|---|
path |
string | — | File to read. |
Response 200: { "path": "/work/README.md", "content": "…" }
PUT /api/file
Writes a text file, creating parent directories as needed.
| Query parameter | Type | Default | Description |
|---|---|---|---|
path |
string | — (required) | File to write. |
Body: { "content": "…" } — response 200: { "path": "…", "status": "saved" }
GET /api/raw
Streams a file's bytes as-is — how the preview pane shows images. No size cap, no text requirement; Range requests are honoured (http.ServeContent).
| Query parameter | Type | Default | Description |
|---|---|---|---|
path |
string | — (required) | File to stream. |
Response 200 with the file bytes, X-Content-Type-Options: nosniff, and a Content-Type chosen in this order:
| Extension | Content-Type |
|---|---|
.png .jpg .jpeg .gif .webp .svg .bmp .ico .avif |
pinned image types (image/png, image/jpeg, image/gif, image/webp, image/svg+xml, image/bmp, image/x-icon, image/avif) |
| any other known extension | the host's mime table (e.g. text/markdown; charset=utf-8) |
| unknown | sniffed from the first 512 bytes |
GET /api/skills
Lists the Claude Code skills available to the session, for the composer's / selector.
Response 200:
{ "skills": [ { "name": "quality", "description": "Audit code quality with qlty.", "path": "/work/.claude/skills/quality/SKILL.md", "source": "project" } ] }
A skill is a directory holding a SKILL.md, looked up in <cwd>/.claude/skills/*/ (source: "project") and ~/.claude/skills/*/ (source: "user"). name and description come from the file's YAML frontmatter (key: value, quoted values and > / | block scalars are understood); a missing name falls back to the directory name, a missing description to "". A project skill shadows a user skill of the same name. Results are sorted by name; skills is always an array.
File API errors
All errors are JSON: { "error": "message" }.
| Status | Cause |
|---|---|
| 400 | Missing path on PUT or GET /api/raw, invalid JSON body, or reading a directory. |
| 403 | Filesystem permission denied. |
| 404 | Path (or the workspace root, for the search) does not exist. |
| 413 | File larger than 5 MiB (GET /api/file only; /api/raw has no cap). |
| 415 | File is not valid UTF-8 text (GET /api/file only). |
GET /ws/terminal
Upgrades to a WebSocket and starts one shell (from $SHELL, falling back to bash then sh, with TERM=xterm-256color) inside a pseudo-terminal, in the server's --cwd.
| Direction | Frame type | Content |
|---|---|---|
| server → client | binary | Raw terminal output bytes. |
| client → server | text | {"type":"input","data":"…"} — keystrokes written to the pty. |
| client → server | text | {"type":"resize","cols":N,"rows":N} — viewport change. |
The socket closes with a normal-closure status when the shell exits; closing the socket kills the shell. Malformed text frames are ignored.
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 97 98 99 100 101 102 103 104 105 106 107 108 |
|