Skip to content
Agentmart
Submit a tool

tldraw-offline

Operate the user's tldraw offline canvas app, including open .tldraw or .tldr files.

Skill by kwanpham2195

IndexedMIT★ 2
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/kwanpham2195-tldraw-offline

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Operate the user's tldraw offline canvas app, including open .tldraw or .tldr files. Use whenever a task involves inspecting, editing, arranging, connecting, linting, or scripting a tldraw Desktop canvas.

Use cases: design, writing, productivity

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/tldraw-offline

What's inside

Skill name
tldraw-offline
Description
Operate the user's tldraw offline canvas app, including open .tldraw or .tldr files. Use whenever a task involves inspecting, editing, arranging, connecting, linting, or scripting a tldraw Desktop canvas.
License
MIT
Instructions
226 lines in SKILL.md · view on GitHub

Before installing

  • Instructions call the network

Files (1)

  • SKILL.md

SKILL.md (first lines)

<!-- installed-by:tldraw-desktop-agent-skills -->

# tldraw canvas operator

Use this skill for tasks involving open tldraw Desktop files. The desktop app exposes a local HTTP server that can list documents, inspect canvas state, capture screenshots, execute JavaScript against a live editor, and expose live script files for durable behavior.

## Server

The default server is `http://localhost:7236`. If that port is not active, read the `port` from `$HOME/Library/Application Support/tldraw/server.json`.

A clean quit removes `server.json`; the next launch rewrites it. It also records `pid` and `startedAt`, so if the file is present but requests to its `port` fail, treat it as stale (the app quit uncleanly) — the app is not running.

Every request except `GET /` and `/readme` needs the per-launch `token` from that same `server.json`, sent as `-H "authorization: Bearer <token>"`.

**If the server's base URL and bearer token are already in your context** — the app injects them at subagent launch when its agent hook is installed — use those literal values directly, or just call the installed `tq` helper (below). The rest of this section is the fallback for when neither is in hand.

**Each Bash tool call runs in a fresh shell — exported env vars do NOT persist between calls.** A `TLDRAW_TOKEN` you `export` in one call is empty in the next, so the request sends `authorization: Bearer` with no token and 401s. "Export once and reuse" does not work here — re-establish the port and token on every call. Read them together at the top of each call (both stay fixed for the app's lifetime, so re-reading is cheap):

```bash
PORT=$(jq -r .port "$HOME/Library/Application Support/tldraw/server.json"); TOKEN=$(jq -r .token "$HOME/Library/Application Support/tldraw/server.json")
# use as:  http://localhost:$PORT/...   -H "authorization: Bearer $TOKEN"
```

### Helper: `tq`

A ready-made helper ships with this skill at `"$HOME/skills/tldraw-offline/tq"`. Invoke it as `sh "$HOME/skills/tldraw-offline/tq" <METHOD> <path> [body]` — it re-reads the port and token from `server.json` itself on every call, so you never handle the token or the fresh-shell env problem. A body starting with `{` is sent as JSON; anything else as raw `text/plain`:

```bash
sh "$HOME/skills/tldraw-offline/tq" POST /api/search '{"code":"return await api.getDocs()"}'
sh "$HOME/skills/tldraw-offline/tq" POST /api/doc/DOC_ID/exec 'return editor.getCurrentPageShapes().length'
sh "$HOME/skills/tldraw-offline/tq" GET  /api/doc/DOC_ID/script-status
```

If `tq` is missing (an older install), fall back to raw `curl` with the `PORT`/`TOKEN` reads shown above. The raw-`curl` examples below stay in explicit form so each request is visible; translate any to `sh "$HOME/skills/tldraw-offline/tq" <METHOD> <path> [body]`.

```bash
curl -s http://localhost:7236/readme
```

## Core endpoints

- `POST /api/search`: run JavaScript with an `api` object. Use this to discover docs, read shapes and bindings, capture screenshots, and query the editor API reference.
- `POST /api/docs/create`: create a new named `.tldraw` file, open it in a new window, and save it to disk. Use this when the task needs a fresh document rather than an already-open one.
- `POST /api/doc/:id/exec`: run JavaScript with a live tldraw `editor` scoped to one document. Use this for canvas edits.
- `POST /api/doc/:id/script-workspace`: expose a locally owned document's live script paths for direct durable board-script and asset edits.
- `GET /api/doc/:id/script-status`: inspect a locally owned document's watcher state for `script/**` edits and find `errorLogPath`.

The code-taking POST endpoints accept raw JavaScript as the request body (`content-type: text/plain`) or a JSON body `{"code": "..."}`, and wrap the code in an async function so top-level `await` works. Prefer raw bodies for shell use.

## Use this first

Most tasks do not require searching `api.members`. Start with these calls and search the full Editor API only if a snippet fails or you truly need an unknown method. The object is `api`, not `spec`. Each block below is shown as raw `curl` so the request is visible; `sh "$HOME/skills/tldraw-offline/tq" <METHOD> <path> [body]` is the shorter equivalent that handles the port and token for you.

```bash
# Fresh shell per call: re-read port + token first (or use the values already in your context).
PORT=$(jq -r .port "$HOME/Library/Application Support/tldraw/server.json"); TOKEN=$(jq -r .token "$HOME/Library/Application Support/tldraw/server.json")

# Pick the target doc by focused window or filename.
curl -s -X POST http://localhost:$PORT/api/search \
  -H 'content-type: application/json' \
  -H "authorization: Bearer $TOKEN" \
  -d '{"code":"return await api.getDocs({ name: \"NAME\" })"}'

# Read the current page's shapes with ids, bounds, text, and metadata.
curl -s -X POST http://localhost:$PORT/api/search \
  -H 'content-type: application/json' \
  -H "authorization: Bearer $TOKEN" \
  -d '{"code":"const doc = await api.getFocusedDoc(); const page = doc ? await api.getShapes(doc.id) : null; return { doc, shapes: page?.shapes.map(s => ({ id: s.id, type: s.type, x: s.x, y: s.y, props: s.props, meta: s.meta })) ?? [] }"}'

# Read bindings only for connection-dependent behavior.
curl -s -X POST http://localhost:$PORT/api/search \
  -H 'content-type: application/json' \
  -H "authorization: Bearer $TOKEN" \
  -d '{"code":"const doc = await api.getFocusedDoc(); return doc ? await api.getBindings(doc.id) : []"}'
```

Every listed doc has `ownership: 'local' | 'remote' | 'server'`. A remote or server doc's `id` (opaque connection id for remote; the server's board id for server) works with `/exec`, `api.getShapes()`, `api.getBindings()`, and screenshots, but its `documentId`, `filePath`, and `unsavedChanges` are `null`: only the host (or server) owns those facts, and `host` carries the `host:port` it came from. Remote edits sync into the host working copy; server edits sync into the server's durable storage on their own. Do not call `helpers.saveDoc()`, open `/script-workspace`, or claim an archive was saved for a remote or server doc.

A local doc also reports `shared`. True means the board is shared right now and other people are editing it as you work: read before you write, and never clear a page you did not create.

## Scripts on a shared board

Writing or editing a board script for a board that is (or may become) shared changes what the script may do, because **every participant's editor runs the whole bundle** — `config.js` before it mounts, `main.js` after — and nothing elects a writer:

- Registration and rendering (`config.js` utils, view-only reactions) must run on every client, identically. A client that doesn't register a script-defined shape type renders an inert placeholder instead.
- Writes happen once per client unless guarded. Gate them on `ctx.app.board.isHost` — true in the editor that owns the file, false in one that joined — and keep continuous work (`tick` handlers, timers, simulations) behind the same guard. A never-shared board has one editor and it is the host, so the guard is correct offline too.
- Create with stable ids via `helpers.createShapeIfMissing` / `createShapesIfMissing`; never clear-and-redraw a page other people are on.
- Keep per-client state (hover, selection, "which card is flipped for me") in module scope, not in shape props — writing it to the document broadcasts one person's UI to everyone.

Read the `scripts-on-a-shared-board` recipe from `api.recipes` before writing one.

## Creating documents

When the task needs a fresh document (not one of the open canvases), `POST /api/docs/create` with a JSON body `{"name": "..."}` creates `<name>.tldraw`, opens it in a new window, and saves it to disk immediately. The name takes a `.tldraw` extension or none — never legacy `.tldr`, which the app opens but does not create. Optional `"directory"` is an absolute path to an existing folder; the default is the user's Documents folder. It never overwrites — an existing file with that name is a `409`. The response returns the new doc's `id`, `documentId`, `filePath`, `name`, and `windowId` — use that `id` directly with `/api/doc/:id/exec` and the `api.*` reads, no `api.getDocs()` re-discovery needed. Do not create the file yourself with filesystem tools.

```bash
curl -s -X POST http://localhost:$PORT/api/docs/create \
  -H 'content-type: application/json' \
  -H "authorization: Bearer $TOKEN" \
  -d '{"name":"Project Plan"}'
```

## Reference recipes

`api.recipes` (via `/api/search`) is an object keyed by recipe `id`; read one in full with `api.recipes['<id>']`. Query it when a task matches one of the worked recipes:

- `stack-existing-boxes` — Stack existing boxes
- `group-shapes-with-a-box` — Group shapes with a box
- `add-durable-behavior-with-a-board-script` — Add durable behavior with a board script
- `editable-furniture-with-anchored-internals` — Editable furniture with anchored internals
- `scripts-on-a-shared-board` — Scripts on a shared board
- `clickable-card-or-button-ui` — Clickable card or button UI
- `connection-dependent-behavior` — Connection-dependent behavior
- `animation-simulation-loop` — Animation / simulation loop
- `custom-shape-config-js` — Custom shape (config.js)
- `custom-binding-config-js` — Custom binding (config.js)
- `custom-overlay-config-js` — Custom overlay (config.js)

Fetch `/readme` when an endpoint fails or you need API details not covered here.

From kwanpham2195/agent-stuff, MIT.

More from kwanpham2195/agent-stuff

Signals

Stars
2
Forks
0
Open issues
0
Last commit (this folder)
Sep 14, 2026 (1 month ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from kwanpham2195

Cite this page

tldraw-offline skill. Agent Mart. https://agentmart.dev/skills/kwanpham2195-tldraw-offline (last updated 2026-10-24)