[drawgent](../README.md) › [Documentation](README.md) # Development ## Building ```bash 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](#releasing-a-version)) | - **macOS**: the build is cross-compiled from Linux with zig and no Apple SDK; drawgent links only `libSystem`, `libiconv` and `libcharset`. 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 ```bash 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--.yml` | a `v*` tag | one per target, in parallel (Linux x86_64 and arm64, macOS arm64 and x86_64): `scripts/release.sh build `, then `publish`. The tarball and its `.sha256` are attached to the tag | ```bash 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: 1. Bump `version` in `Cargo.toml` and `package.json`. 2. Commit. 3. Run `git tag -a v0.2.1 -m v0.2.1`. The tag must be annotated: Tangled attaches artifacts to tag objects. 4. 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=`. Open it in any tab at sidebar size, e.g. with Firefox's Responsive Design Mode (Ctrl+Shift+M). After a change, run `npm run build`, restart drawgent and reload the tab. - **`node scripts/panel-preview.mjs`** sets 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. `--keep` leaves it running and prints the URL. - **The sidebar itself** (`sidebar.*`, `drawgent.js`, `background.js`): go to `about: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 extension` reloads on save in a fresh profile; - `drawgent setup firefox --from-source` installs the current sources. ### Releasing a version 1. Bump `version` in `extension/manifest.json`. Mozilla signs each version only once. 2. Run `make sign-extension` with the addons.mozilla.org API keys in `AMO_JWT_ISSUER` and `AMO_JWT_SECRET`. It signs the extension as unlisted and writes `extension/signed/drawgent-firefox-.xpi`. It also records the package, with its SHA-256, in `extension/updates.json`. 3. Commit and push both. Installed extensions update from `updates.json`, and the next drawgent build embeds the package for `setup 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: ```bash 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](../LICENSE). The third-party work it bundles is under GPL-compatible licenses, listed in full in [THIRD_PARTY_LICENSES.md](../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.