Development #
Building #
npm ci && npm run build # editor, panel and renderer pages -> dist/ (embedded into the binary)
cargo build --release # or: cargo install --path .
For release builds, nix develop -c make … provides the toolchain; rustup plus
cargo-zigbuild also works.
| Command | Output |
|---|---|
make musl / make dist |
static Linux x86_64 binary / tarball (MUSL_TARGET=aarch64-unknown-linux-musl for ARM) |
make darwin / make dist-darwin |
macOS universal binary (Intel and Apple Silicon, macOS 13 or later) / tarball. DARWIN_TARGET=aarch64-apple-darwin (or x86_64-apple-darwin) builds one architecture |
make licenses |
regenerate THIRD_PARTY_LICENSES.md. make dist fails while it's out of date |
make sign-extension |
sign the Firefox extension (see below) |
- macOS: the build is cross-compiled from Linux with zig and no Apple SDK; drawgent links
only
libSystem,libiconvandlibcharset. The arm64 slice is ad-hoc signed by the linker but not notarized. It hasn't been tested on a real Mac yet. - Small machines: use
JOBS=1. One dependency,chromiumoxide_cdp, needs a lot of memory to compile.
Code layout #
src/, in Rust, has one folder per role. Dependencies only point down the list.
| Path | |
|---|---|
main.rs |
CLI parsing and dispatch |
cli/ |
setup.rs; attach.rs (session discovery, picker); daemon.rs (up -d, status, down); firefox.rs (setup firefox); up.rs (up / serve, --diagram); check.rs; remote.rs |
sync/ |
hub.rs (routing, notes, chat log); server.rs (HTTP, WebSocket, REST); room.rs (Excalidraw room client, peers' laser strokes); rooms.rs (rooms joined at runtime) |
agents/ |
mod.rs (queue, workers); acp.rs (ACP driver: new and forked sessions); opencode.rs, codex.rs (live sessions) |
bridge/ |
ACP bridges built into the binary: claude.rs (Claude Code stream-json), codex.rs (codex app-server) |
canvas/ |
scene.rs (store, edits); boards.rs; diagram.rs (.excalidraw files); build.rs (baselines, semantic diff); lint.rs; autolayout.rs; gesture.rs (gestures, delete impact, poll options); history.rs (undo); el.rs, geometry.rs, fractional.rs. It doesn't depend on the renderer: it asks a Layout |
code/ |
mod.rs (workspace files, search, code links); repomap.rs |
render/ |
mod.rs (Chrome over CDP, implements Layout); chrome.rs (find or install Chrome) |
config.rs, mcp.rs, legal.rs |
config and paths; the MCP server (a REST client); license notices |
web/:
- the editor:
main.jsx,chat.jsx,boards.jsx,code.jsx,cards.jsx,build.jsx,gestures.jsx,laser.js,laserpoint.jsx; - the panel-only view:
panel.jsx; - the renderer page:
render.jsx.
extension/: the Firefox extension, in plain JS with no build step. It only uses drawgent's
REST API and panel view.
Tests #
cargo test
The end-to-end suites are in scripts/. Most use fake agents: fake Claude, Codex and ACP CLIs.
They also use a fake excalidraw-room server, so they run offline:
| Script | Covers |
|---|---|
smoke.mjs [url], e2e-browser.mjs [url] |
a chat turn and an AGENT: note against a running drawgent, in a real browser |
claude-bridge-e2e.mjs, codex-bridge-e2e.mjs |
the built-in bridges: streaming, permission prompts, Stop, usage |
efficiency-e2e.mjs |
check_layout, add_graph, cropped screenshots, incremental get_scene, token usage |
gestures-e2e.mjs |
gestures, the poll, make_space, delete with reconnect, undo |
boards-e2e.mjs |
move, copy, split, folder mode, switching boards |
diagram-e2e.mjs |
--diagram files: clean output, changes from git flow back |
code-e2e.mjs, pointing-e2e.mjs |
code links, the Code tab, drawgent check --fix; tree, Ctrl+P, laser on code, basket, code cards, repo map |
room-e2e.mjs |
a fresh excalidraw.com room, both directions (needs the network) |
remote-room-e2e.mjs |
self-hosted rooms: token, peers storage, leaving when alone |
extension-e2e.mjs |
the extension's sidebar: joining at runtime, panel view, laser from the document, WebSocket origin check |
daemon-e2e.mjs |
up -d, status, down, canvas only |
firefox-setup-e2e.mjs |
setup firefox with a fake Firefox: signed package, --from-source, --xpi |
laser-e2e.mjs, build-e2e.mjs |
with a real agent (needs setup claude): a laser zone edited by the agent; Build what I drew |
CI and releases #
CI runs on Tangled's spindle. The workflows are in .tangled/workflows/: microvm
engine, NixOS image, our own flake.nix dev shell. Both call scripts you can run locally:
| Workflow | Trigger | Runs |
|---|---|---|
ci.yml |
push to main, pull requests to main, manual |
scripts/ci.sh: cargo test, then the offline e2e suites, each with a throwaway HOME |
release-<os>-<arch>.yml |
a v* tag |
one per target, in parallel (Linux x86_64 and arm64, macOS arm64 and x86_64): scripts/release.sh build <tag> <target>, then publish. The tarball and its .sha256 are attached to the tag |
nix develop -c scripts/ci.sh # what CI runs (needs Chrome or Chromium)
nix develop -c scripts/ci.sh gestures-e2e # some suites only
Cutting a release:
- Bump
versioninCargo.tomlandpackage.json. - Commit.
- Run
git tag -a v0.2.1 -m v0.2.1. The tag must be annotated: Tangled attaches artifacts to tag objects. - Run
git push origin main v0.2.1.
The release workflows then build and attach the files. A single job must fit the spindle's time limit, which is why each target gets its own workflow. It checks that the tag matches
Cargo.toml and that each file is under Tangled's 50 MiB artifact limit. Re-running it skips
files that are already attached.
Publishing needs the repo secret ATP_APP_PASSWORD: an app password of the repo owner's
account, added under Settings → Secrets. The script finds the account's PDS from its DID
and logs in there.
The browser extension #
Most of what the sidebar shows is drawgent's panel page, not the extension. You can work on it without signing or reinstalling anything:
- The chat panel (
web/panel.jsx,web/chat.jsx,web/chat.css) is served at/?view=panel&board=<board>. Open it in any tab at sidebar size, e.g. with Firefox's Responsive Design Mode (Ctrl+Shift+M). After a change, runnpm run build, restart drawgent and reload the tab. node scripts/panel-preview.mjssets it all up. It starts a drawgent with a fake agent and a fake room whose "user" chats and draws with the laser. It then takes screenshots of the panel at 320, 400 and 560 px (chat, gesture card, poll) and prints the height of each part.--keepleaves it running and prints the URL.- The sidebar itself (
sidebar.*,drawgent.js,background.js): go toabout:debugging#/runtime/this-firefox→ Load Temporary Add-on… →extension/manifest.json. Reload picks up edits, Inspect opens its devtools. Alternatives:npx web-ext run --source-dir extensionreloads on save in a fresh profile;drawgent setup firefox --from-sourceinstalls the current sources.
Releasing a version #
- Bump
versioninextension/manifest.json. Mozilla signs each version only once. - Run
make sign-extensionwith the addons.mozilla.org API keys inAMO_JWT_ISSUERandAMO_JWT_SECRET. It signs the extension as unlisted and writesextension/signed/drawgent-firefox-<version>.xpi. It also records the package, with its SHA-256, inextension/updates.json. - Commit and push both. Installed extensions update from
updates.json, and the next drawgent build embeds the package forsetup firefox.
The extension ID is drawgent-ext@2kmail.net. It never changes.
Regenerating the docs images #
The images in docs/img/ come from a real session. The script runs drawgent with your
configured agent on a throwaway clone of this repository. It drives the UI in headless
Chrome, records the screen, and makes 3×-speed GIFs with ffmpeg:
npm run build && cargo build --release
node scripts/docs-capture.mjs # every scene (uses real tokens)
node scripts/docs-capture.mjs --only laser,code --scene saved-scene.json
The scenes are architecture, note, laser, gestures, code, repomap and panel.
--scene starts from a saved scene, so later scenes can be redone without the long
architecture run. It needs Chrome, ffmpeg and a set-up agent.
License #
drawgent is GPL-3.0-or-later. The third-party work it bundles is under GPL-compatible licenses, listed in full in THIRD_PARTY_LICENSES.md:
- Rust crates and npm packages: MIT, Apache-2.0, ISC, BSD, Zlib, Unicode-3.0, CDLA-Permissive-2.0, MPL-2.0 OR Apache-2.0, CC0, Unlicense and 0BSD;
- Excalidraw's fonts: OFL 1.1 and MIT. Liberation Sans is left out: its license is GPLv2 with a font exception;
- the drawing guide, adapted from excalidraw-mcp (MIT).
After changing dependencies, run make licenses. The agents and Chrome aren't part of
drawgent: they run as separate programs from your own install.