Tavotto
AI가 당신의 연구 그림을 그려줍니다
오픈소스 디자인 캔버스, 로컬에서 호스팅, Claude Code·Codex·opencode가 MCP 통해 실시간 변경 감시·수정·코멘트
Open-source design canvas you host on your own machine. Claude Code, Codex and opencode draw on it through MCP; you watch changes land live, edit by hand and review with comments.
디자인TypeScript · JavaScriptGitHub @krittaprot온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기A drawboard for your agent. Draw via MCP or CLI, watch changes live, and edit together. Self-hosted and open source.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Install Node.js 20+, pnpm, and Git, then run:
git clone https://github.com/krittaprot/draft.git
cd draft
pnpm install --frozen-lockfile
pnpm build
node bin/draft.js serve --data "$HOME/.local/share/draft/data"
Keep that terminal running and open http://127.0.0.1:3290.
cat "$HOME/.local/share/draft/data/owner-token" and use it to sign in. Keep this key private.Stop the server with Ctrl+C. Restart it with the same command and data directory to reopen your boards.
New installations store data in ~/.local/share/draft/data. Set DRAFT_DATA or use --data DIR to choose another location. Back up the entire data directory while the server is stopped; it includes designs, revisions, assets and access settings. Keep data outside the source checkout.
Common options:
node bin/draft.js serve --port 3290 --data "$HOME/.local/share/draft/data"
node bin/draft.js --help
The server binds to 127.0.0.1 by default. Local setup needs no domain, Google account or tunnel.
With Xcode Command Line Tools installed, run from the project root:
./scripts/install.sh
This installs ~/Applications/Draft.app and the draft command in ~/.local/bin. Add that directory to your PATH if needed. Open Draft to use a running local server, or let the app start its bundled server while it is open. Choose Draft → Settings… to connect to another server.
The web app can also be installed through Safari's File → Add to Dock, or Share → Add to Home Screen on iPhone.
From the machine hosting Draft:
draft connect claude
draft connect codex
draft connect opencode
Without the Mac installer, substitute node bin/draft.js for draft. Each command creates a named agent key and registers the MCP connection. Restart the agent to load its new tools.
For remote MCP clients, use https://your-host/mcp. Clients with OAuth sign in through the browser and request your approval. For bearer-token clients, create a named key with draft token create my-agent and use it as an Authorization bearer token. A viewer key can inspect designs and take screenshots without editing them.
Use the same drawing tools from a shell or an agent with terminal access. In a second terminal, set a key for your own server:
export DRAFT_TOKEN="$(cat "$HOME/.local/share/draft/data/owner-token")"
# Discover tools and their JSON input schemas.
draft tools
# Create a board. Copy its returned document ID into the next command.
draft call create_document '{"name":"My first board"}'
draft call create_nodes '{"document":"d_REPLACE_ME","nodes":[{"type":"frame","name":"Hello","width":640,"height":400,"fill":"#0D1838","layout":"column","padding":48,"children":[{"type":"text","text":"Hello, Draft","fontSize":40,"color":"#ffffff"}]}]}'
# Capture the board; the result includes a PNG share link.
draft call screenshot '{"document":"d_REPLACE_ME"}'
Use --url https://your-host for a remote server. For larger inputs, use draft call create_nodes --file nodes.json, or pipe JSON into draft call create_nodes --file -. Output is JSON; a failed tool call exits nonzero. Use a named editor key instead of the owner key for ongoing agent work.
Both MCP and CLI expose the same tools:
People get an infinite canvas, layers panel, style inspector, comment pins and live change highlights in the browser or Mac app.
For remote access, use a domain you control and Cloudflare Tunnel. Install cloudflared, sign in with cloudflared tunnel login, then copy deploy/deploy.env.example to deploy/deploy.env and set your hostname, service label and tunnel name.
Run from the project root:
./scripts/deploy.sh
./scripts/install-service.sh
./scripts/setup-tunnel.sh
The scripts publish a release, install the server launch agent and configure the tunnel. The server stays on loopback; the tunnel provides public HTTPS. These launchd scripts are macOS-specific. On Linux, run the Node server under your own process manager and HTTPS reverse proxy, with a persistent data directory.
pnpm install --frozen-lockfile, then ./scripts/deploy.sh.https://your-host/healthz, sign in, and open an existing board.current symlink at its previous release, then restart <your-label>.server with launchctl.~/.local/share/draft; logs live under ~/Library/Logs/Draft.Set DRAFT_NEON_AUTH_URL to your Neon Auth endpoint and add the public URL as a trusted domain. Configure Google sign-in in Neon, then allow members:
draft member add [email protected] --role owner
Set DRAFT_ALLOWED_EMAILS for a fixed email allowlist. With Google enabled, browser token login defaults to local-only; DRAFT_TOKEN_LOGIN accepts on, local, or off. Without Neon, sign in with an access key; explicitly set DRAFT_TOKEN_LOGIN=on if you want remote token sign-in.
Treat owner keys as secrets. MCP and API calls require authentication; signed screenshot links are viewable by anyone who has the link until they expire. Back up your data before updates.
pnpm install --frozen-lockfile
pnpm typecheck
pnpm test
node bin/draft.js serve --port 3299 --data /tmp/draft-dev
./scripts/build-app.sh
Use a scratch data directory for testing. src/shared contains the model and renderer; src/server contains storage, HTTP, MCP and CLI; src/web contains the editor and homepage; mac contains the native wrapper.
See CONTRIBUTING.md, SECURITY.md, and AGENTS.md for contribution and development guidance.
Text measurement uses the real metrics of the face that will be rendered for the 39 bundled families (advance widths, ascender/descender), so the editor, the SVG and the server PNG wrap identically. Families outside the catalogue fall back to Helvetica metrics. Server rendering also uses installed system fonts and any fonts added to <data>/fonts.
src/shared/font-catalog.json lists the families and weights Draft ships (Inter, Geist, Roboto, Open Sans, Lato, Montserrat, Poppins, Work Sans, DM Sans, Manrope, Space Grotesk, Plus Jakarta Sans, Figtree, Public Sans, Outfit, Archivo, Barlow, Rubik, IBM Plex Sans/Mono, Source Sans 3, Source Serif 4, Noto Sans, Oswald, Bebas Neue, Anton, Playfair Display, Merriweather, Lora, Libre Baskerville, EB Garamond, DM Serif Display, Instrument Serif, Fraunces, JetBrains Mono, Fira Code, Roboto Mono, Space Mono, Geist Mono) — 248 faces including the italics Google actually ships.
pnpm fonts downloads the families and regenerates src/shared/font-data.ts plus src/shared/fonts.lock.json (a sha256 of every font file); it is the only thing that writes the metrics. A build verifies that every declared face is on disk and that no binary changed since the metrics were written, and fails otherwise — so a release can never ship a stylesheet, a metric table and a set of font files that disagree. DRAFT_FONTS_OFFLINE=1 downgrades that to a warning for machines without network, and pnpm fonts:check reports missing faces on its own.
build.mjs writes /fonts.css with one @font-face per available face, each URL carrying its content hash, so a stylesheet and the binaries it names are always the same pair (the stylesheet is versioned like every other asset).
Typography rules the editor and the renderer share: canvas text sets font-synthesis: none, so a weight or italic the family does not actually ship is not faked — it falls back to the nearest real face, exactly as resvg renders it. New text nodes default to Inter; existing documents keep the family they were saved with. Agents can call list_fonts, and create_nodes/update_nodes warn when they use an unknown family or an italic the family cannot draw.
The font files are large, so src/web/fonts/<slug>/ is git-ignored while the generated metrics are committed.
Documents carry three authoring features that keep a large design consistent:
manage_variables sets document tokens ({paper:"#faf6ef", space:8}); props reference them as "var:paper", including fill, stroke, text colour, shadow and outline colours, plus radius, gap, padding and strokeWidth. Changing a token re-renders every node that uses it, and export_tokens emits the set as CSS custom properties, DTCG-shaped JSON and a Tailwind @theme block.component:true is a definition. {type:"instance", component:"button"} places it; overrides maps component-local child ids to props ({"cta-label":{text:"Save"}}, "@root" for the frame itself). Instances resolve at render time, so editing the definition — or moving a token — updates every instance; there is no flattening to keep in sync.states:{"hover":{"@root":{fill:"#2a261f"}, "cta-label":{color:"var:paper"}}}: prop patches over the same tree, so a state never duplicates nodes. Instances pick one with state, and screenshot({node, state:"hover"}) previews a definition's state without saving.Supporting tools close the loop: find_nodes queries by property rather than id, update_nodes({where, props}) edits every match in one revision, dry_run validates a batch without writing, create_nodes/update_nodes return warnings for text and children that a clipped frame will cut off, get_node.computed reports alpha colours flattened against their backdrop, and audit_contrast checks text against the colour actually behind it — alpha layers and every gradient stop at its worst — suggests the nearest passing token, groups failures by page so an archive is not mistaken for the live design, and reports text inside a disabled: true control as exempt under WCAG 1.4.3 rather than as a defect.
Painting has outline:{width, offset, color} (a ring outside the box that does not affect layout) and strokeAlign: inner|center|outer (an inner border that does not change a component's width). Text has wrap: auto|nowrap|truncate and maxLines. Measurement walks a per-glyph fallback chain, so symbols like ⌘ are measured with the face that draws them instead of being clipped.
MIT.