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 HistoryOpen raw

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
# 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`:

```json
{ "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`:

```json
{ "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`:

```json
{ "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.