145 lines
6.1 KiB
Markdown
145 lines
6.1 KiB
Markdown
---
|
||
tags: maturity/experimental
|
||
references:
|
||
- bin/silverbullet/src/server.rs
|
||
- bin/sb/src/commands/query.rs
|
||
---
|
||
|
||
The Runtime API lets you interact with SilverBullet programmatically over HTTP: evaluate Lua expressions and run scripts from the command line, scripts, or external tools.
|
||
|
||
Requests are evaluated via Chrome DevTools Protocol (CDP) in a headless Chrome instance, which does the actual execution so all results reflect the live client state.
|
||
|
||
> **note** Note
|
||
> The [[CLI]] provides a convenient command-line interface for the Runtime API — evaluate Lua, run scripts, open a REPL, and more, without writing raw HTTP requests.
|
||
|
||
> **note** Note
|
||
> The Runtime API is not available in read-only mode (`SB_READ_ONLY`).
|
||
|
||
# Setup
|
||
The Runtime API is enabled automatically when Chrome or Chromium is detected on your system — no configuration needed.
|
||
|
||
If Chrome isn't auto-detected, set the path explicitly:
|
||
```
|
||
SB_CHROME_PATH=/usr/bin/chromium
|
||
```
|
||
|
||
To explicitly disable the Runtime API, set `SB_RUNTIME_API=0`.
|
||
|
||
# Docker setup
|
||
Use the `-runtime-api` Docker image variant, which includes Chromium:
|
||
```yaml
|
||
services:
|
||
silverbullet:
|
||
image: ghcr.io/silverbulletmd/silverbullet:latest-runtime-api
|
||
environment:
|
||
- SB_USER=me:secret # optional
|
||
- SB_AUTH_TOKEN=mytoken # optional, for API auth
|
||
volumes:
|
||
- myspace:/space
|
||
ports:
|
||
- "3000:3000"
|
||
```
|
||
|
||
The `-runtime-api` image automatically persists the Chrome profile in `/space/.chrome-data`, avoiding re-indexing on container restarts.
|
||
|
||
The base Docker image (`ghcr.io/silverbulletmd/silverbullet`) does **not** include Chromium and is significantly smaller (~64MB vs ~766MB).
|
||
|
||
# Endpoints
|
||
|
||
## Evaluate a Lua expression
|
||
`POST /.runtime/lua`
|
||
|
||
The request body is a raw Lua expression as plain text.
|
||
|
||
```bash
|
||
curl -d '1 + 1' http://localhost:3000/.runtime/lua
|
||
# => {"result":2}
|
||
```
|
||
|
||
```bash
|
||
curl -d 'editor.getCurrentPage()' http://localhost:3000/.runtime/lua
|
||
# => {"result":"index"}
|
||
```
|
||
|
||
## Evaluate a Lua script
|
||
`POST /.runtime/lua_script`
|
||
|
||
The request body is a raw Lua script as plain text. This allows multi-statement scripts with explicit `return` statements.
|
||
|
||
```bash
|
||
curl -d 'local pages = query[[from tags.page limit 3 select table.select(_, "name")]]
|
||
return pages' \
|
||
http://localhost:3000/.runtime/lua_script
|
||
# => {"result":[{"name":"index"},{"name":"Projects"},{"name":"TODO"}]}
|
||
```
|
||
|
||
## Screenshot
|
||
`GET /.runtime/screenshot`
|
||
|
||
Captures the current viewport of the headless Chrome instance as a PNG image.
|
||
|
||
```bash
|
||
curl -o screenshot.png http://localhost:3000/.runtime/screenshot
|
||
```
|
||
|
||
## Console logs
|
||
`GET /.runtime/logs`
|
||
|
||
Returns recent console log entries from the headless browser.
|
||
|
||
| Query parameter | Description |
|
||
|---|---|
|
||
| `limit` | Maximum number of entries to return (default: 100, server retains up to 1000) |
|
||
| `since` | Unix millisecond timestamp — only return entries newer than this |
|
||
|
||
```bash
|
||
curl http://localhost:3000/.runtime/logs?limit=5
|
||
```
|
||
|
||
**Response:** `Content-Type: application/json`
|
||
```json
|
||
{
|
||
"logs": [
|
||
{"level": "log", "text": "[Client] Booting SilverBullet client", "timestamp": 1710000000000},
|
||
{"level": "info", "text": "Service worker disabled.", "timestamp": 1710000000050}
|
||
]
|
||
}
|
||
```
|
||
|
||
Each entry has:
|
||
* `level` — one of `log`, `info`, `warn`, `error`, `debug`
|
||
* `text` — the concatenated console message
|
||
* `timestamp` — unix milliseconds when the entry was captured
|
||
|
||
# Timeout
|
||
The Lua endpoints (`/.runtime/lua` and `/.runtime/lua_script`) support an `X-Timeout` header to control the maximum wait time in seconds (default: 30):
|
||
|
||
```
|
||
curl -H "X-Timeout: 60" \
|
||
-d 'some_long_running_expression()' \
|
||
http://localhost:3000/.runtime/lua
|
||
```
|
||
|
||
# Error handling
|
||
All error responses are JSON with `Content-Type: application/json` and an `error` key. Runtime execution failures also include a stable machine-readable `code`.
|
||
|
||
Status codes used across the Runtime API:
|
||
|
||
* **400** — Empty request body: `{"error": "Request body is required"}`.
|
||
* **500** — Lua/JS execution error (the evaluated code threw, e.g. a Lua error): `{"error": "<error message>", "code": "script_error"}`. The message is the concise client error (e.g. `attempt to call a nil value`); the full stack is available in the runtime console log.
|
||
* **503** — Runtime API not enabled or no headless browser running: `{"error": "Runtime API is not enabled"}` or `{"error": "...", "code": "bridge_unavailable"}`.
|
||
* **504** — Timeout exceeded: `{"error": "...", "code": "timeout"}`.
|
||
|
||
# How it works
|
||
As documented in [[Architecture]], the vast majority of SilverBullet’s power is implemented in the client. However, there are use cases for programmatically accessing your space with all of SilverBullet (client’s) power.
|
||
|
||
When the Runtime API is enabled, the server launches a single, server-wide headless (invisible by default) Chrome process upon the first request to an `/.remote` endpoint. Each space with the runtime API enabled gets its own page (tab) in that shared browser, opened on that space’s first request. A page loads the full SilverBullet client, exactly like a regular browser tab, but without a visible window (with some memory optimizations). The client boots normally: it loads all plugs, Lua code and navigates to the index page.
|
||
|
||
Once ready, the server communicates with the browser directly via Chrome DevTools Protocol (CDP). Because Lua code runs inside a real SilverBullet client, it has access to the full API surface — `editor.*`, `space.*`, queries, and everything else available to in-page scripts and widgets. The results reflect live client state.
|
||
|
||
## Debugging
|
||
Set `SB_CHROME_SHOW=1` to run Chrome with a visible window — useful for watching what the headless client is doing. Set `SB_CHROME_DATA_DIR` to a path to persist the Chrome profile between restarts (avoids re-indexing on each restart).
|
||
|
||
## Resource usage
|
||
Headless Chrome spawns several processes (browser, network, storage, and renderer). With the full SilverBullet client loaded and indexed in a single space, expect roughly **150–200 MB** of total RSS across all Chrome processes. Because the browser is shared, additional spaces cost a renderer each rather than a whole browser. The SilverBullet server itself adds ~30 MB on top of this.
|