Sketch a UI and watch it materialize into a working, single-file web app in a live
preview. Draw a rough layout (or drop in a screenshot), add a note or two, and
Conjure uses the Claude Code CLI to build — and then incrementally edit — a real,
self-contained app.html in the pane next to your canvas.
Why Conjure? → See BACKSTORY.md for the hackathon origin story and what we learned.
Sketch a UI on paper, on a canvas, or right on top of your running app — Conjure builds it into a real, working web app in seconds.
About this project — Conjure was built as a group hackathon project for the University of Pittsburgh TLI program. It's a team effort; the commit history reflects contributions from multiple members. Shared here for demo, learning, and reference. It runs entirely on your own machine against your own Claude account.
Conjure was built in a weekend by a student team in the Pitt TLI program — trading the canvas, the runtime prompt, and the deploy back and forth until "sketch → live app" worked end to end. The full story is in BACKSTORY.md.
Part of the team mid-build — laptops, cold coffee, and a lot of iterating on the live preview.
Two input modes, both of which can attach voice notes (Web Speech API, Chrome; degrades gracefully):
- Canvas — draw with mouse/touch. A 4-color marker palette (black / red / blue / green) + eraser + clear, plus a text instruction box. Hit Build it or let it auto-commit ~2.5s after your last stroke. Colors carry meaning (a red annotation reads as an instruction). Phone-friendly.
- Add image — drop a screenshot, logo, or mockup onto the markup (the
Imagebutton). It's composited into what you send, so Conjure builds from your reference.
The right pane is an iframe of the current project's generated app, hot-reloaded over a
WebSocket. Export downloads the current app; timestamped copies are kept per project in
history/.
Two panes: starters and markup tools on the left, your live app hot-reloading on the right. Turn on Markup, draw your edits onto the app, hit Build.
-
Node.js 18+
-
The Claude Code CLI, authenticated. Conjure shells out to
claudeto do the actual building, so you need it installed and logged in with your Anthropic account (Claude Pro/Max subscription or API credits):npm install -g @anthropic-ai/claude-code claude # first run walks you through loginVerify it works on its own before running Conjure:
claude -p "say hi".
git clone https://github.com/vafc21/conjure.git
cd conjure
npm install --omit=dev
node server.jsThen open http://localhost:8091. That's it — locally the passphrase gate is disabled by default, so you go straight to the canvas. Draw something and hit Build.
Auth note. If the CLI login above worked, Conjure inherits it automatically — no token needed. If you'd rather use an explicit token (e.g. on a headless server), run
claude setup-tokenand export it before starting:export CLAUDE_CODE_OAUTH_TOKEN=... # from `claude setup-token` node server.jsWithout a working
claudelogin or token, the UI loads but builds will fail.
PORT=3000 node server.js # then open http://localhost:3000Conjure reads a plain conjure/.env at startup (no dotenv dependency). Copy the
template and edit as needed — none of it is required for local use:
cp .env.example .env| Variable | Default | Purpose |
|---|---|---|
PORT |
8091 |
HTTP port. |
CONJURE_MODEL |
claude-sonnet-5 |
Model the runtime passes to claude -p. |
CONJURE_PASSPHRASE |
(unset) | If set, gates every route behind a passphrase. Leave unset for open local use. |
CONJURE_COOKIE_SECRET |
(random) | HMAC secret for the auth cookie (required in production when gated). |
CONJURE_ALLOW_OPEN |
0 |
Dev-only escape hatch to run without a passphrase even when NODE_ENV=production. |
CONJURE_MAX_CONCURRENT |
2 |
Global build semaphore size. |
CONJURE_AUTH_RATE_MAX / CONJURE_AUTH_RATE_WINDOW_MS |
5 / 900000 |
/auth rate limit (per IP). |
CONJURE_UPDATE_RATE_MAX / CONJURE_UPDATE_RATE_WINDOW_MS |
10 / 3600000 |
/update rate limit (per IP). |
Local vs. exposed: with no NODE_ENV=production and no CONJURE_PASSPHRASE, the
gate auto-disables (open access) — perfect for localhost. If you ever put Conjure on a
public URL, set NODE_ENV=production, a CONJURE_PASSPHRASE, and a random
CONJURE_COOKIE_SECRET, so only people with the phrase can drive your Claude account.
- Projects — named, isolated canvases under
workspace/projects/<slug>/(app.html,history/,frames/). Create / rename / delete (delete = move toworkspace/.trash, never hard-removed). WS events and/appare scoped per project. - In-place edits — after the first build the runtime reads
app.htmland edits it in place, emitting only changed lines, so iterative tweaks are fast. - Live terminal — an optional dark panel streams the Claude Code run (thinking /
text / tool calls / results) via
--output-format stream-json. Off by default; the toggle persists in localStorage. - Clarifying questions — if a sketch is critically ambiguous the model may ask instead of guessing; the UI shows the question plus a crop of the region it's asking about, and your answer auto-resubmits the same sketch.
- Concurrency — a global 2-slot semaphore with per-project latest-wins queues, so simultaneous users can't overload the machine.
- Mobile flow — a first-run tutorial walks new users through markup/build; after a build the app goes fullscreen with a slim back-arrow bar. Touch targets ≥ 44px.
- Passphrase gate — optional signed httpOnly cookie (30 days) protecting every route and the WebSocket upgrade (see Configuration).
- Presenter mode — append
?present=1to auto-open the build terminal;?starter=todo(etc.) kicks off a starter after load. ⌘/Ctrl+Enter triggers Build.
The server invokes the Claude Code CLI non-interactively to edit each project's
app.html from the newest sketch + notes. The runtime prompt bakes in
docs/design-tells.md (avoid the AI-slop look) and
docs/web-factors.md (real front-end craft), so generated apps
lean clean and professional rather than the usual purple-gradient-glassmorphism default.
When a passphrase is set, all routes are gated by the auth cookie; locally they're open.
GET /— the Conjure UI (unauthenticated requests get the gate page when gated).POST /auth—passphrase=…form; sets the auth cookie on success.GET /projects·POST /projects{name}·POST /projects/:slug/rename{name}·DELETE /projects/:slug(→ trash).GET /projects/:slug/history·GET /projects/:slug/history/:file·POST /projects/:slug/restore{file}·POST /projects/:slug/cancel.GET /app?project=slug— the generated app (no-store, for the preview iframe).GET /app.html?project=slug— download the generated app.GET /frames/:slug/:file— a submitted sketch frame (for the question crop view).POST /update—{ project, image: <base64 png|null>, notes: string[] }. Per project only the newest pending request is kept (latest-wins stale-drop). Rate-limited.GET /health·GET /health?deep=1— status JSON;deep=1probes the Claude runtime.WS ?project=slug— server pushesreload,status,term, andquestionevents.
- Builds fail / "runtime not healthy" — run
claude -p "hi"yourself. If that fails, fix your CLI login (claude) or setCLAUDE_CODE_OAUTH_TOKEN.GET /health?deep=1probes the runtime. - Port already in use — start with
PORT=3000 node server.js. - Stuck at a passphrase page — you have
CONJURE_PASSPHRASEset (orNODE_ENV=production). Unset it for open local use.
Conjure is a single Node process; anything that runs Node works. The repo includes
example unit files — conjure.service (systemd) and
cloudflared.service (a Cloudflare Tunnel for a public URL) —
from the author's Raspberry Pi setup. If you expose it publicly, set a passphrase
(see Configuration): the server drives your Claude account, so open public access = an
open door to your usage.
No formal license yet — this is a shared University of Pittsburgh TLI hackathon project. It's published for demo, learning, and reference. If you want to reuse it beyond that, open an issue and ask the team.


