* Stabilize synced main for AI handoff, drag nesting, and Electron dev (#104) * docs(readme): update cover screenshot * fix: stabilize electron dev sync and codex env passthrough * Preserve nested frame behavior during drag reparenting Reparenting across containers used raw local coordinates and root-only clipping assumptions, which made nodes jump visually and caused dragged frames to lose clip/corner semantics after nesting. This adapts the drag-reparent fix to the current upstream store architecture, keeps frame/shape nodes from auto-detaching on canvas drags, and promotes formerly root-only frame clipping to explicit clipContent when nested. Constraint: Latest upstream workspace checkout is incomplete locally (missing workspaces/deps), so full upstream verification could not be rerun in this environment Rejected: Keep using raw local x/y during parent changes | fails for auto-layout/padding-rendered positions Rejected: Make all nested frames clip unconditionally | would change non-clipping containers Confidence: medium Scope-risk: moderate Reversibility: clean Directive: Preserve visual-position conversion through rendered coordinates when parent changes; local coordinates alone are insufficient once layout participates Not-tested: Fresh full workspace typecheck/test/build on latest upstream checkout (blocked by missing workspace/dependency setup in this local clone) * Keep AI codegen requests bounded while exporting asset bundles The AI codegen pipeline needed two stability fixes: exported design images had to flow through chunk/assembly prompts as reusable asset hints, and oversized chat payloads needed a local guard before hitting provider limits. This commit wires asset extraction into the planning pipeline, threads exported asset paths into prompt assembly, and rejects obviously overlarge chat requests with an actionable client-side error. Constraint: This branch is split out from a larger local fix stack, so only codegen/prompt/context files are included here Constraint: Provider request limits are approximate locally, so the payload guard must be conservative rather than exact Rejected: Inline base64 assets directly into prompts | explodes request size and repeats the same payload per chunk Rejected: Let provider errors handle oversized payloads | too slow and opaque for users Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep asset references flowing as stable ./assets paths and enforce payload limits before fetch to avoid silent request bloat Tested: bun x tsc -p apps/web/tsconfig.json --noEmit; cd apps/web && bun --bun vitest run src/services/ai/__tests__/context-optimizer.test.ts src/services/ai/__tests__/codegen-assets.test.ts src/services/ai/__tests__/structure-bundle.test.ts; bun run build Not-tested: Manual end-to-end AI generation with live providers * Explain sanitized design views instead of leaving AI to guess The sanitized structure bundle already stabilized asset paths, but it still exposed low-level image/layout/component fields that models had to interpret on their own. This change adds explicit consumer-view enrichment for fills, layout, text, variables, themes, and component semantics, carries original image size through the Figma import path, and augments sanitized bundles with summary/highlight guidance for downstream AI consumers. Constraint: This branch is intentionally stacked on the asset-bundle PR because it extends the sanitized/codegen asset pipeline rather than replacing it Constraint: Figma import data is not always complete, so original image size must be preserved when present and inferred only as a fallback downstream Rejected: Keep sanitized.json as a pure field-level dump | still leaves AI to misread transforms, layout, and component relationships Rejected: Put all explain text directly in asset extraction helpers | mixes resource stabilization with semantic enrichment responsibilities Confidence: high Scope-risk: moderate Reversibility: clean Directive: Treat consumer-view enrichment as a distinct layer on top of stable asset extraction; future AI-facing semantics should land there instead of leaking into unrelated pipeline code Tested: bun x tsc -p apps/web/tsconfig.json --noEmit; cd apps/web && bun --bun vitest run src/services/ai/__tests__/consumer-view-enrichment.test.ts src/services/ai/__tests__/codegen-assets.test.ts src/services/ai/__tests__/structure-bundle.test.ts ../../packages/pen-figma/src/figma-fill-mapper.test.ts; bun run build Not-tested: Manual prompt-to-code generation quality with live provider responses * Restore code-panel bundle exports for AI handoff flows The code generation backend still produced asset manifests and AI structure bundles, but the code panel UI no longer exposed those export paths after later sync work. This commit reconnects the panel to bundle export actions, restores ZIP download behavior when generated code includes exported assets, and locks the affordances with focused panel tests. Constraint: Other local fixes are still in progress in the working tree, so this commit is intentionally limited to the code-panel export surface Rejected: Rebuild export support in a separate panel | users expect the export actions to remain where generation results are shown Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep code-panel UI aligned with codegen asset/bundle backends whenever generation result shape changes Tested: cd apps/web && bun --bun vitest run src/components/panels/code-panel.test.tsx src/services/ai/__tests__/codegen-assets.test.ts src/services/ai/__tests__/structure-bundle.test.ts; bun run build Not-tested: Manual click-through of AI Bundle and Download ZIP in the desktop/web UI * Unblock electron dev startup in the incomplete local workspace The local workspace was failing before the app could even start: the skills plugin hard-required js-yaml from a node_modules layout that was not present, Vite dev under Bun hit Nitro NodeResponse incompatibilities, and the web tsconfig was missing path mappings for local packages. This commit removes the unnecessary js-yaml dependency from the skills loader, runs Vite under Node for dev startup, hardens readiness probing with socket checks, and points TypeScript/Vite at the in-repo package sources. Constraint: The current local clone has incomplete hoisted/workspace installation state, so dev startup must not depend on root package links being perfectly present Constraint: Bun + Nitro dev currently mis-handle NodeResponse in this environment, so the safest startup path is Node-hosted Vite Rejected: Keep js-yaml and require everyone to fix local hoisting first | still leaves electron:dev broken in the current environment Rejected: Continue running Vite dev through Bun | reproduces the NodeResponse/Parse Error failure on /api and /editor requests Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep the dev launcher biased toward resilient local startup, even when the workspace install shape is imperfect Tested: bun -e import('./packages/pen-ai-skills/vite-plugin-skills.ts').then(() => console.log('SKILL_PLUGIN_IMPORT_OK')); bun electron:dev verified Vite ready, MCP/Electron compiled, Electron launched, MCP sync log emitted Not-tested: Long-running interactive desktop session after startup * fix(figma): preserve cropped image fill transforms The synced branch started exporting original image dimensions but dropped the existing crop transform semantics from the shared image-fill type and both Figma mappers. That broke the new regression test and stripped metadata that AI consumer-view/bundle code already relies on. Constraint: keep app and package Figma mappers in lockstep Rejected: loosen the new regression test | would hide a real metadata regression Confidence: high Scope-risk: narrow Reversibility: clean Directive: when extending image fill metadata, update shared pen-types and both Figma mapper copies together Tested: bun --bun run test (148/149 files passed; only server/__tests__/sse-keepalive.test.ts blocked by missing agent_napi.node), cd apps/web && bun --bun vitest run src/canvas/skia/drag-reparent-policy.test.ts src/components/panels/layer-dnd-utils.test.ts src/stores/document-position-utils.test.ts src/components/panels/code-panel.test.tsx ../../packages/pen-renderer/src/__tests__/document-flattener.test.ts ../../packages/pen-figma/src/figma-fill-mapper.test.ts, cd apps/web && bun --bun vitest run src/services/ai/__tests__/codegen-assets.test.ts src/services/ai/__tests__/structure-bundle.test.ts src/services/ai/__tests__/consumer-view-enrichment.test.ts, cd apps/web && bun --bun vitest run src/utils/__tests__/security.test.ts, bun test scripts/loopback-no-proxy.test.ts, npx tsc --noEmit, bun --bun run build Not-tested: server/__tests__/sse-keepalive.test.ts without a locally built @zseven-w/agent-native addon * docs(editor): normalize new PR comments to English The PR had a handful of newly introduced Chinese code comments in dev, sync, and AI helper paths. This follow-up keeps the implementation unchanged while translating those comments to English so the PR stays consistent with the repository comment-language expectation. Constraint: The request was limited to comment language cleanup after the conflict-resolution merge, so behavior had to remain unchanged Rejected: Leave the mixed-language comments in place | conflicts with the PR requirement for English comments Rejected: Broader repository-wide translation sweep | unnecessary scope expansion beyond the PR-introduced comments Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep code comments in English on this branch, even when local notes or working memory are in another language Tested: bun test scripts/loopback-no-proxy.test.ts apps/desktop/__tests__/dev-utils.test.ts; cd apps/web && bun --bun vitest run server/__tests__/mcp-sync-state-active.test.ts src/canvas/skia/__tests__/skia-interaction.test.ts; npx tsc --noEmit; branch-diff comment scan for Han characters in comment lines Not-tested: Manual runtime behavior, since this change only rewrote comments * style(editor): apply repository formatting expected by CI The PR was failing the CI Format check after the conflict-resolution and comment-normalization follow-ups. This commit applies the repository formatter output to the files touched by the branch so CI sees the exact formatting it expects, without changing behavior. Constraint: The failing GitHub Actions job stopped at Format check, so the fix had to match oxfmt output rather than introduce functional changes Rejected: Leave the branch as-is and rely on local formatting differences being acceptable | CI explicitly rejects the current formatting Rejected: Broader code cleanup beyond formatter output | unnecessary scope while repairing the failing check Confidence: high Scope-risk: narrow Reversibility: clean Directive: After conflict resolution or comment-only edits on this repo, run bun run format:check before pushing because formatter expectations are stricter than the existing file style in some touched files Tested: bun run format:check; bun run lint; npx tsc --noEmit Not-tested: Full test suite after this formatting-only commit (previous run showed formatting was the first CI blocker) * refactor(editor): remove proxy-specific dev workarounds from PR The PR no longer needs the loopback proxy bypass layer, so this cleanup removes the proxy-specific dev entrypoint, environment bootstrap, helper module, and its tests while keeping the unrelated Electron and AI handoff changes intact. Constraint: Removal had to be limited to proxy-related code on PR #104 without undoing the other merged fixes on the branch Rejected: Keep the helper and stop using it | leaves proxy-specific maintenance surface and tests in the PR Rejected: Revert the entire Electron dev file to upstream earlier than necessary | would risk dropping unrelated local conflict-resolution choices beyond the proxy scope Confidence: high Scope-risk: moderate Reversibility: clean Directive: If proxy handling is reintroduced later, keep it out of this PR unless there is a dedicated, separately justified change for it Tested: bun run format:check; bun run lint; npx tsc --noEmit Not-tested: Manual electron:dev behavior after removing the proxy-specific launcher path * docs(ai): translate JSON-facing semantic descriptions to English The PR still emitted Chinese semantic description strings inside the AI consumer-view and structure-bundle JSON outputs. This change translates those JSON-facing runtime descriptions and updates the affected tests so exported AI-facing structure data is consistently English. Constraint: The request was limited to JSON description strings, so the change had to preserve the same semantics and structure while only translating output text Rejected: Leave Chinese test fixtures and runtime descriptions in place | conflicts with the requirement for English JSON descriptions Rejected: Broader i18n cleanup outside these AI JSON description paths | unnecessary scope expansion beyond the requested exported-description surface Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep AI/exported JSON explanation strings in English unless a future change explicitly adds localized output modes Tested: cd apps/web && bun --bun vitest run src/services/ai/__tests__/consumer-view-enrichment.test.ts src/services/ai/__tests__/structure-bundle.test.ts src/services/ai/__tests__/codegen-assets.test.ts; bun run format:check; npx tsc --noEmit Not-tested: Full app runtime flows that consume these JSON descriptions outside the covered unit tests * refactor(ai): remove remaining network-proxy handling The current project still carried Anthropic proxy-specific heuristics and environment handling outside the PR-specific cleanup. Since the earlier crashes and connectivity issues were unrelated to proxying, this removes the remaining network-proxy branches, model remapping, and TLS override advice while leaving unrelated request flows intact. Constraint: The cleanup needed to remove proxy-specific logic without disturbing unrelated transport concepts such as app-internal API proxy routes or React proxy objects used in tests Rejected: Keep the proxy heuristics as dormant fallback logic | preserves misleading operational guidance and dead maintenance surface Rejected: Rename every remaining literal use of the word proxy in the repo | would overreach into unrelated concepts like internal API proxying and JS Proxy-based test setup Confidence: medium Scope-risk: moderate Reversibility: clean Directive: If endpoint-specific compatibility logic is needed later, add it as explicit endpoint handling rather than generic proxy heuristics Tested: bun run format:check; bun run lint; npx tsc --noEmit; repo-wide search for network-proxy env references after cleanup Not-tested: End-to-end Claude connection flows against custom base URLs after removing proxy-specific remapping * fix(electron): keep Node-backed dev launch for Nitro compatibility Comparing against upstream commit |
||
|---|---|---|
| .githooks | ||
| .github | ||
| .vscode | ||
| apps | ||
| packages | ||
| screenshot | ||
| scripts | ||
| .cta.json | ||
| .dockerignore | ||
| .editorconfig | ||
| .gitignore | ||
| .gitmodules | ||
| .oxfmtrc.json | ||
| .prettierignore | ||
| AGENTS.md | ||
| bun.lock | ||
| CLAUDE.md | ||
| Dockerfile | ||
| LICENSE | ||
| oxlintrc.json | ||
| package.json | ||
| README.de.md | ||
| README.es.md | ||
| README.fr.md | ||
| README.hi.md | ||
| README.id.md | ||
| README.ja.md | ||
| README.ko.md | ||
| README.md | ||
| README.pt.md | ||
| README.ru.md | ||
| README.th.md | ||
| README.tr.md | ||
| README.vi.md | ||
| README.zh-TW.md | ||
| README.zh.md | ||
| tsconfig.base.json | ||
| tsconfig.json | ||
OpenPencil
The world's first open-source AI-native vector design tool.
Concurrent Agent Teams • Design-as-Code • Built-in MCP Server • Multi-model Intelligence
English · 简体中文 · 繁體中文 · 日本語 · 한국어 · Français · Español · Deutsch · Português · Русский · हिन्दी · Türkçe · ไทย · Tiếng Việt · Bahasa Indonesia
Click the image to watch the demo video
Note: There is another open-source project with the same name — OpenPencil, focused on Figma-compatible visual design with real-time collaboration. This project focuses on AI-native design-to-code workflows.
Why OpenPencil
🎨 Prompt → CanvasDescribe any UI in natural language. Watch it appear on the infinite canvas in real-time with streaming animation. Modify existing designs by selecting elements and chatting. |
🤖 Concurrent Agent TeamsThe orchestrator decomposes complex pages into spatial sub-tasks. Multiple AI agents work on different sections simultaneously — hero, features, footer — all streaming in parallel with per-member canvas indicators. |
🧠 Multi-Model IntelligenceAutomatically adapts to each model's capabilities. Claude gets full prompts with thinking; GPT-4o/Gemini disable thinking; smaller models (MiniMax, Qwen, Llama) get simplified prompts for reliable output. |
🔌 MCP ServerOne-click install into Claude Code, Codex, Gemini, OpenCode, Kiro, or Copilot CLIs. Design from your terminal — read, create, and modify |
🎨 Style GuidesBuilt-in style guide library with tag-based fuzzy matching. Apply visual styles (glassmorphism, brutalist, retro, etc.) to AI-generated designs. MCP tools for external agent access. |
📦 Design-as-Code
|
🖥️ Runs EverywhereWeb app + native desktop on macOS, Windows, and Linux via Electron. Auto-updates from GitHub Releases. |
⌨️ CLI —
|
🎯 Multi-Platform Code ExportExport to React + Tailwind, HTML + CSS, Vue, Svelte, Flutter, SwiftUI, Jetpack Compose, React Native — all from one |
🧩 Embeddable SDK
|
🛡️ Design System KitManage reusable UIKits with style switching and component composition. Import/export kits from |
Install
macOS (Homebrew):
brew tap zseven-w/openpencil
brew install --cask openpencil
Windows (Scoop):
scoop bucket add openpencil https://github.com/zseven-w/scoop-openpencil
scoop install openpencil
Linux / Windows direct download: GitHub Releases — .exe (Windows), .AppImage / .deb (Linux)
CLI (op):
npm install -g @zseven-w/openpencil
Quick Start (Development)
# Install dependencies
bun install
# Start dev server at http://localhost:3000
bun --bun run dev
Or run as a desktop app:
bun run electron:dev
Prerequisites: Bun >= 1.0 and Node.js >= 18. Optional: Zig >= 0.14 for building
agent-nativefrom source (a prebuilt binary will be downloaded automatically if Zig is not installed).
Docker
Multiple image variants are available — pick the one that fits your needs:
| Image | Size | Includes |
|---|---|---|
openpencil:latest |
~226 MB | Web app only |
openpencil-claude:latest |
— | + Claude Code CLI |
openpencil-codex:latest |
— | + Codex CLI |
openpencil-opencode:latest |
— | + OpenCode CLI |
openpencil-copilot:latest |
— | + GitHub Copilot CLI |
openpencil-gemini:latest |
— | + Gemini CLI |
openpencil-full:latest |
~1 GB | All CLI tools |
Run (web only):
docker run -d -p 3000:3000 ghcr.io/zseven-w/openpencil:latest
Run with AI CLI (e.g. Claude Code):
The AI chat relies on Claude CLI OAuth login. Use a Docker volume to persist the login session:
# Step 1 — Login (one-time)
docker volume create openpencil-claude-auth
docker run -it --rm \
-v openpencil-claude-auth:/root/.claude \
ghcr.io/zseven-w/openpencil-claude:latest claude login
# Step 2 — Start
docker run -d -p 3000:3000 \
-v openpencil-claude-auth:/root/.claude \
ghcr.io/zseven-w/openpencil-claude:latest
Build locally:
# Base (web only)
docker build --target base -t openpencil .
# With a specific CLI
docker build --target with-claude -t openpencil-claude .
# Full (all CLIs)
docker build --target full -t openpencil-full .
AI-Native Design
Prompt to UI
- Text-to-design — describe a page, get it generated on canvas in real-time with SSE streaming animation
- Orchestrator — decomposes complex pages into spatial sub-tasks for parallel generation
- Agent Teams — concurrent team members with delegate tool, per-member canvas indicators, and fallback strategies
- Design modification — select elements, then describe changes in natural language
- Vision input — attach screenshots or mockups for reference-based design
- Style Guides — apply visual styles (glassmorphism, brutalist, retro, etc.) via tag-based fuzzy matching
- Anti-slop — cross-generation diversity tracking to avoid repetitive AI output
Multi-Agent Support
| Agent | Setup |
|---|---|
| Built-in (9+ providers) | Select from provider presets with region switcher — Anthropic, OpenAI, Google, DeepSeek, and more |
| Claude Code | No config — uses Claude Agent SDK with local OAuth |
| Codex CLI | Connect in Agent Settings (Cmd+,) |
| OpenCode | Connect in Agent Settings (Cmd+,) |
| GitHub Copilot | copilot login then connect in Agent Settings (Cmd+,) |
| Gemini CLI | Connect in Agent Settings (Cmd+,) |
Model Capability Profiles — automatically adapts prompts, thinking mode, and timeouts per model tier. Full-tier models (Claude) get complete prompts; standard-tier (GPT-4o, Gemini, DeepSeek) disable thinking; basic-tier (MiniMax, Qwen, Llama, Mistral) get simplified nested-JSON prompts for maximum reliability.
i18n — Full interface localization in 15 languages: English, 简体中文, 繁體中文, 日本語, 한국어, Français, Español, Deutsch, Português, Русский, हिन्दी, Türkçe, ไทย, Tiếng Việt, Bahasa Indonesia.
MCP Server
- Built-in MCP server (
pen-mcppackage) — one-click install into Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs - Auto-detects Node.js — if not installed, falls back to HTTP transport and auto-starts the MCP HTTP server
- Design automation from terminal: read, create, and modify
.opfiles via any MCP-compatible agent - Layered design workflow —
design_skeleton→design_content→design_refinefor higher-fidelity multi-section designs - Segmented prompt retrieval — load only the design knowledge you need (schema, layout, roles, icons, planning, etc.)
- Style guide tools —
get_style_guide_tagsandget_style_guidefor applying visual styles via MCP - Multi-page support — create, rename, reorder, and duplicate pages via MCP tools
Code Generation
- React + Tailwind CSS, HTML + CSS, CSS Variables
- Vue, Svelte, Flutter, SwiftUI, Jetpack Compose, React Native
CLI — op
Install globally and control the design tool from your terminal:
npm install -g @zseven-w/openpencil
op start # Launch desktop app
op design @landing.txt # Batch design from file
op insert '{"type":"RECT"}' # Insert a node
op export react --out . # Export to React + Tailwind
op import:figma design.fig # Import Figma file
cat design.dsl | op design - # Pipe from stdin
Supports three input methods: inline string, @filepath (read from file), or - (read from stdin). Works with desktop app or web dev server. See CLI README for full command reference.
LLM Skill — install the OpenPencil Skill plugin to teach AI agents (Claude Code, Cursor, Codex, Gemini CLI, etc.) how to design with op.
Features
Canvas & Drawing
- Infinite canvas with pan, zoom, smart alignment guides, and snapping
- Rectangle, Ellipse, Line, Polygon, Pen (Bezier), Frame, Text
- Boolean operations — union, subtract, intersect with contextual toolbar
- Icon picker (Iconify) and image import (PNG/JPEG/SVG/WebP/GIF)
- Auto-layout — vertical/horizontal with gap, padding, justify, align
- Multi-page documents with tab navigation
Design System
- Design variables — color, number, string tokens with
$variablereferences - Multi-theme support — multiple axes, each with variants (Light/Dark, Compact/Comfortable)
- Component system — reusable components with instances and overrides
- CSS sync — auto-generated custom properties,
var(--name)in code output - Reusable UIKits — import/export component kits from
.penfiles
AI & Agents
- Prompt-to-canvas with streaming generation and orchestrator-driven spatial decomposition
- Concurrent Agent Teams — multiple designers work on different sections in parallel with per-member canvas indicators
- Layered workflow —
design_skeleton→design_content→design_refinewith focused prompts per phase - Style Guides — 50+ built-in styles (glassmorphism, brutalist, retro, etc.) with tag-based fuzzy matching, wired into planning and generation
- Multi-model capability profiles — auto-adapts thinking mode, effort, and prompt shape per model tier
- Built-in agent runtime (
agent-native, Zig NAPI) + Anthropic, Claude Agent SDK, OpenCode, Codex, Copilot, Gemini providers - Anthropic-format passthrough for Chinese LLM providers — Kimi, Zhipu, GLM, DouBao, Ark, Bailian/DashScope, ModelScope, Coding Plans
Git Integration
- Clone wizard with SSH / HTTPS auth and SSH key management
- Branch picker — create, switch, delete, merge, all from the git panel
- Pull / push cascades with auth retry and non-fast-forward handling
- Folder-mode three-way merge with on-disk
MERGE_HEADstate tracking - Conflict panel with per-node / per-field three-way cards, inline JSON editor, bulk actions, and inline diff block
- Remote settings and SSH keys UI; 15-locale i18n across the whole Git surface
Export
- Canvas export — PNG, JPEG, WEBP, PDF (
Cmd+Shift+P) - Code export — React + Tailwind, HTML + CSS, Vue, Svelte, Flutter, SwiftUI, Jetpack Compose, React Native
- Incremental MCP codegen pipeline —
codegen_plan,codegen_submit_chunk,codegen_assemble,codegen_clean
Figma Import
- Import
.figfiles with layout, fills, strokes, effects, text, images, and vectors preserved
Desktop App
- Native macOS, Windows, and Linux via Electron
.opfile association — double-click to open, single-instance lock- Auto-update from GitHub Releases
- Native application menu with Save As, Open Recent, and an unsaved-changes dialog on close
- Recent files persistence
Tech Stack
| Frontend | React 19 · TanStack Start · Tailwind CSS v4 · shadcn/ui · i18next |
| Canvas | CanvasKit/Skia (WASM, GPU-accelerated) |
| Engine | pen-engine (headless) · pen-react (React UI SDK) |
| State | Zustand v5 |
| Server | Nitro |
| Desktop | Electron 35 |
| CLI | op — terminal control, batch design DSL, code export |
| AI | agent-native (Zig NAPI) · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| Runtime | Bun · Vite 7 |
| Lint | oxlint · oxfmt |
| File format | .op — JSON-based, human-readable, Git-friendly |
Project Structure
openpencil/
├── apps/
│ ├── web/ TanStack Start web app
│ │ ├── src/
│ │ │ ├── canvas/ CanvasKit/Skia engine — drawing, sync, layout
│ │ │ ├── components/ React UI — editor, panels, shared dialogs, icons
│ │ │ ├── services/ai/ AI chat, orchestrator, design generation, streaming
│ │ │ ├── services/codegen/ Code generation service wrappers
│ │ │ ├── stores/ Zustand — canvas, document, pages, history, AI
│ │ │ ├── hooks/ Keyboard shortcuts, file drop, Figma paste, MCP sync
│ │ │ ├── i18n/ Internationalization — 15 locales
│ │ │ └── uikit/ Reusable component kit system
│ │ └── server/
│ │ ├── api/ai/ Nitro API — streaming chat, agent, generation, image search
│ │ ├── api/mcp/ MCP HTTP transport endpoints
│ │ └── utils/ Claude, OpenCode, Codex, Copilot, Gemini CLI wrappers
│ ├── desktop/ Electron desktop app
│ │ ├── main.ts Window, Nitro fork, native menu, auto-updater
│ │ ├── ipc-handlers.ts Native file dialogs, theme sync, prefs IPC
│ │ └── preload.ts IPC bridge
│ └── cli/ CLI tool — `op` command
│ ├── src/commands/ Design, document, export, import, node, page, variable commands
│ ├── connection.ts WebSocket connection to running app
│ └── launcher.ts Auto-detect and launch desktop app or web server
├── packages/
│ ├── pen-types/ Type definitions for PenDocument model
│ ├── pen-core/ Document tree ops, layout engine, variables
│ ├── pen-engine/ Headless design engine — document, selection, history, viewport
│ ├── pen-react/ React UI SDK — provider, canvas, hooks, panels, toolbar
│ ├── pen-codegen/ Code generators (React, HTML, Vue, Flutter, ...)
│ ├── pen-figma/ Figma .fig file parser and converter
│ ├── pen-renderer/ Standalone CanvasKit/Skia renderer
│ ├── pen-mcp/ MCP server — tools, routes, document manager
│ ├── pen-sdk/ Umbrella SDK (re-exports all packages)
│ ├── pen-ai-skills/ AI prompt skill engine (phase-driven prompt loading)
│ └── agent-native/ Native AI agent runtime (Zig NAPI, multi-provider, teams)
└── .githooks/ Pre-commit version sync from branch name
Keyboard Shortcuts
| Key | Action | Key | Action | |
|---|---|---|---|---|
V |
Select | Cmd+S |
Save | |
R |
Rectangle | Cmd+Z |
Undo | |
O |
Ellipse | Cmd+Shift+Z |
Redo | |
L |
Line | Cmd+C/X/V/D |
Copy/Cut/Paste/Duplicate | |
T |
Text | Cmd+G |
Group | |
F |
Frame | Cmd+Shift+G |
Ungroup | |
P |
Pen tool | Cmd+Shift+P |
Export (PNG/JPG/WEBP/PDF) | |
H |
Hand (pan) | Cmd+Shift+C |
Code panel | |
Del |
Delete | Cmd+Shift+V |
Variables panel | |
[ / ] |
Reorder | Cmd+J |
AI chat | |
| Arrows | Nudge 1px | Cmd+, |
Agent settings | |
Cmd+Alt+U |
Boolean union | Cmd+Alt+S |
Boolean subtract | |
Cmd+Alt+I |
Boolean intersect | Cmd+Shift+S |
Save As |
Scripts
bun --bun run dev # Dev server (port 3000)
bun --bun run build # Production build
bun --bun run test # Run tests (Vitest)
npx tsc --noEmit # Type check
bun run lint # Lint (oxlint)
bun run format # Format (oxfmt)
bun run bump <version> # Sync version across all package.json
bun run electron:dev # Electron dev
bun run electron:build # Electron package
bun run cli:dev # Run CLI from source
bun run cli:compile # Compile CLI to dist
bun run mcp:dev # Run MCP server from source
Contributing
Contributions are welcome! See CLAUDE.md for architecture details and code style.
- Fork and clone
- Set up version sync:
git config core.hooksPath .githooks - Create a branch:
git checkout -b feat/my-feature - Run checks:
npx tsc --noEmit && bun --bun run test - Commit with Conventional Commits:
feat(canvas): add rotation snapping - Open a PR against
main
Roadmap
- Design variables & tokens with CSS sync
- Component system (instances & overrides)
- AI design generation with orchestrator
- MCP server integration with layered design workflow
- Multi-page support
- Figma
.figimport - Boolean operations (union, subtract, intersect)
- Multi-model capability profiles
- Monorepo restructure with reusable packages
- CLI tool (
op) for terminal control - Built-in AI agent SDK with multi-provider support
- i18n — 15 languages
- Headless design engine (
pen-engine) + React UI SDK (pen-react) - Style Guides with tag-based matching and MCP tools
- Concurrent Agent Teams with delegate tool and canvas indicators
- Native agent runtime (
agent-native— Zig NAPI) - Git integration — clone, branch, push/pull, folder-mode three-way merge
- Canvas raster export (PNG / JPEG / WEBP / PDF)
- Collaborative editing
- Plugin system
Contributors
Sponsors
OpenPencil is free and open-source. Development is funded by people who find it useful — thank you for keeping the canvas open.
Thanks to MrQyun — want your name here too? Become a sponsor →
Community
Star History
License
MIT — Copyright (c) 2026 ZSeven-W