nandi/oripublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

workspace-api.md · 108 lines · 4.8 KBmarkdown Blame HistoryRaw
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday1# Reference: workspace API
2
3> 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).
4
5## GET /api/files
6
7Lists a directory.
8
9| Query parameter | Type | Default | Description |
10| --- | --- | --- | --- |
11| `path` | string | the workspace root | Directory to list. |
12
13Response `200`:
14
15```json
16{ "path": "/work", "entries": [ { "name": "src", "path": "/work/src", "isDir": true, "size": 0 } ] }
17```
18
19Entries are sorted directories first, then files, each group alphabetically.
20
21## GET /api/files/search
22
23Searches files recursively under the workspace root, for the composer's `@` selector.
24
25| Query parameter | Type | Default | Description |
26| --- | --- | --- | --- |
27| `q` | string | `""` | Case-insensitive substring matched against the path relative to the root. Empty returns the first files. |
28| `limit` | integer | 50 | Maximum number of hits; capped at 500. |
29
30Response `200`:
31
32```json
33{ "root": "/work", "files": [ { "name": "main.go", "path": "/work/src/main.go", "relPath": "src/main.go" } ] }
34```
35
36`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`.
37
38## GET /api/file
39
40Reads a text file.
41
42| Query parameter | Type | Default | Description |
43| --- | --- | --- | --- |
44| `path` | string | — | File to read. |
45
46Response `200`: `{ "path": "/work/README.md", "content": "…" }`
47
48## PUT /api/file
49
50Writes a text file, creating parent directories as needed.
51
52| Query parameter | Type | Default | Description |
53| --- | --- | --- | --- |
54| `path` | string | — (required) | File to write. |
55
56Body: `{ "content": "…" }` — response `200`: `{ "path": "…", "status": "saved" }`
57
58## GET /api/raw
59
60Streams a file's bytes as-is — how the preview pane shows images. No size cap, no text requirement; `Range` requests are honoured (`http.ServeContent`).
61
62| Query parameter | Type | Default | Description |
63| --- | --- | --- | --- |
64| `path` | string | — (required) | File to stream. |
65
66Response `200` with the file bytes, `X-Content-Type-Options: nosniff`, and a `Content-Type` chosen in this order:
67
68| Extension | Content-Type |
69| --- | --- |
70| `.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`) |
71| any other known extension | the host's mime table (e.g. `text/markdown; charset=utf-8`) |
72| unknown | sniffed from the first 512 bytes |
73
74## GET /api/skills
75
76Lists the Claude Code skills available to the session, for the composer's `/` selector.
77
78Response `200`:
79
80```json
81{ "skills": [ { "name": "quality", "description": "Audit code quality with qlty.", "path": "/work/.claude/skills/quality/SKILL.md", "source": "project" } ] }
82```
83
84A 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.
85
86## File API errors
87
88All errors are JSON: `{ "error": "message" }`.
89
90| Status | Cause |
91| --- | --- |
92| 400 | Missing `path` on PUT or `GET /api/raw`, invalid JSON body, or reading a directory. |
93| 403 | Filesystem permission denied. |
94| 404 | Path (or the workspace root, for the search) does not exist. |
95| 413 | File larger than 5 MiB (`GET /api/file` only; `/api/raw` has no cap). |
96| 415 | File is not valid UTF-8 text (`GET /api/file` only). |
97
98## GET /ws/terminal
99
100Upgrades 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`.
101
102| Direction | Frame type | Content |
103| --- | --- | --- |
104| server → client | binary | Raw terminal output bytes. |
105| client → server | text | `{"type":"input","data":"…"}` — keystrokes written to the pty. |
106| client → server | text | `{"type":"resize","cols":N,"rows":N}` — viewport change. |
107
108The socket closes with a normal-closure status when the shell exits; closing the socket kills the shell. Malformed text frames are ignored.