Find a file
Danila Poyarkov c51f34689f
Multi-provider AI, sucrase JSX, Figma zoom shortcuts (#72)
* Add multi-provider AI support (Anthropic, OpenAI, Google AI, OpenAI-compatible)

- Add AI_PROVIDERS registry with per-provider model lists, key placeholders, and URLs
- Refactor use-chat composable: provider factory creates the right AI SDK model
- Per-provider API key storage in localStorage with automatic legacy migration
- New ProviderSetup.vue replaces APIKeySetup.vue with provider selector
- New ProviderSettings.vue popover accessible from gear icon in chat input
- OpenAI-compatible provider with custom base URL and model ID fields
- Install @ai-sdk/anthropic and @ai-sdk/google dependencies
- Update E2E tests for new provider setup flow

* Fix reactivity, key masking, missing model ID field, and chat reset on model change

- Use ref instead of computed for apiKey — computed getter over localStorage
  wasn't triggering reactivity when the key was set
- Load stored key for new provider in providerId watcher
- Reset chat on modelId and customModelId change, not just provider switch
- Add custom model ID field to ProviderSetup.vue for OpenAI-compatible
- Simplify ProviderSettings key field — empty input with contextual placeholder
  instead of fragile dot-masking that broke on partial edits
- Use data-test-id locator for model selector in E2E test

* Use useLocalStorage from vueuse instead of manual ref + watch + localStorage

Replaces 5 hand-rolled ref/watch/localStorage sync pairs with useLocalStorage.
API key uses a computed storage key that rebinds when the provider changes.

* Use useLocalStorage for collab name persistence

* Review fixes: changelog placement, remove dead class, cursor-pointer

* Extract ProviderSelect component and uiInput helper

- ProviderSelect.vue: shared select dropdown for AI providers
- ui/input.ts: shared input styling (sm/md sizes)
- Remove 7 duplicated input class strings across chat components

* Use uppercase acronyms: keyURL, customBaseURL, supportsCustomBaseURL

* Match select dropdown width to trigger via --reka-select-trigger-width

Move min-w-[var(--reka-select-trigger-width)] into selectContent base
style so all selects get it by default. Remove per-component overrides.

* Uppercase acronyms in variable names, improve setup form layout

- providerID, modelID, customModelID, setAPIKey, AIProviderID
- Keep apiKey lowercase at start (standard JS convention)
- Compact setup form: single column, full-width Connect button
- Fix grammar: 'a OpenRouter' → 'an OpenRouter'
- Shorter promo text

* Allow text selection in chat panel

* Fix chat E2E test: Save → Connect button text

* Disable mermaid in chat markdown renderer

Alias mermaid and beautiful-mermaid to empty shims in Vite config,
preventing vue-stream-markdown from attempting to load them.

* Interleave text and tool calls in chat messages

Render message parts in order instead of grouping all tool calls
first then all text. Use SDK's isToolUIPart/isTextUIPart/getToolName
instead of custom type guards. Fix error state: output-error, not error.

* Implement figma.viewport.scrollAndZoomIntoView()

Figma Plugin API method that centers the viewport on given nodes.
Reuses the same bounding box logic as the viewport_zoom_to_fit tool.

* Catch tool execution errors and return them to the AI

Instead of crashing with an unhandled exception, tool errors are
caught and returned as { error: message } so the AI can retry or
explain the failure. UI detects error outputs and shows them in red
with the error message when expanded.

* Replace esbuild with sucrase for JSX transform

Sucrase is a pure JS transform (201 KB / 46 KB gzip) that works in
both Node/Bun and the browser. Replaces esbuild (13 MB WASM) which
only worked in Node/Bun.

- buildComponent() and renderJSX() are now synchronous
- render tool works in browser AI chat (no more 'esbuild required')
- Handles full JS expressions (map, ternaries, Array.from, etc.)

* Update system prompt to prioritize render tool with JSX

Document available tags, props, layout, text, and sizing options.
Instruct the AI to use full JS expressions in JSX for complex layouts.

* Fix zoom shortcuts to match Figma

Cmd+0: Zoom to 100% (was incorrectly mapped to Zoom to fit)
Cmd+1: Zoom to fit
Cmd+2: Zoom to selection
Shift+1 / Shift+2: same as Cmd+1 / Cmd+2

Add zoomTo100() and zoomToSelection() to editor store.
Refactor zoomToFit() to use shared zoomToBounds() helper.

* Refactor keyboard shortcuts to useMagicKeys

Replace manual keydown handler with VueUse useMagicKeys + whenever
for declarative shortcut registration.

- mod() helper for cross-platform Meta/Control shortcuts
- plain() helper for modifier-free keys
- Proper modifier exclusion (⌘G vs ⌘⇧G no longer conflict)
- Add E2E tests: duplicate, zoom (⌘0/⌘1/⌘2/⇧1/⇧2), auto-layout (⇧A)
- All 26 keyboard shortcut tests pass

* Fix scrollAndZoomIntoView to actually zoom

Match Figma Plugin API behavior (equivalent to Shift-1): compute
zoom level that fits all nodes with padding, capped at 100%.
Previously only set center without adjusting zoom.
2026-03-08 22:05:18 +03:00
.github Fix release notes: extract from CHANGELOG.md directly (drop ffurrer2 action) 2026-03-08 02:06:52 +03:00
.pi Sync specs & docs: variables, image export, CLI, core extraction 2026-03-01 02:17:48 +03:00
.vscode Initial commit: Tauri + CanvasKit + React editor scaffold 2026-02-27 15:48:29 +03:00
desktop Release v0.8.0 2026-03-08 00:20:47 +03:00
docs Merge docs PR, fix all lint/typecheck errors, update changelog 2026-03-07 23:19:19 +03:00
openspec Merge PR #70: E2E test coverage + mutation unit tests 2026-03-07 23:47:00 +03:00
packages Multi-provider AI, sucrase JSX, Figma zoom shortcuts (#72) 2026-03-08 22:05:18 +03:00
public feat: mobile layout, PWA support (#27) (#27) 2026-03-05 19:51:51 +03:00
scripts Guard against double destroy and post-unmount rendering 2026-03-04 10:35:44 +03:00
src Multi-provider AI, sucrase JSX, Figma zoom shortcuts (#72) 2026-03-08 22:05:18 +03:00
tests Multi-provider AI, sucrase JSX, Figma zoom shortcuts (#72) 2026-03-08 22:05:18 +03:00
.gitattributes Cache effect ImageFilters and reuse layer paint 2026-03-03 10:12:02 +03:00
.gitignore Ignore VitePress build artifacts under docs/ 2026-03-07 21:45:32 +03:00
.lfsconfig Use Cloudflare R2 for Git LFS storage 2026-03-04 01:25:33 +03:00
.oxfmtrc.json Configure oxfmt import sorting with proper groups 2026-03-06 21:43:23 +03:00
AGENTS.md Merge branch 'master' into add-vitepress-docs 2026-03-07 19:01:09 +03:00
ARCHITECTURE-NEXT.md Adopt motion-v for mobile drawer and toolbar animations 2026-03-06 08:50:38 +03:00
bun.lock Multi-provider AI, sucrase JSX, Figma zoom shortcuts (#72) 2026-03-08 22:05:18 +03:00
CHANGELOG.md Multi-provider AI, sucrase JSX, Figma zoom shortcuts (#72) 2026-03-08 22:05:18 +03:00
components.d.ts Multi-provider AI, sucrase JSX, Figma zoom shortcuts (#72) 2026-03-08 22:05:18 +03:00
CONTRIBUTING.md feat: mobile layout, PWA support (#27) (#27) 2026-03-05 19:51:51 +03:00
index.html Fix PWA manifest error in dev, handle invalid font data 2026-03-06 21:43:16 +03:00
LICENSE Add MIT license 2026-03-02 08:31:54 +03:00
oxlint.json Fix all lint, typecheck, and formatting issues 2026-02-28 22:57:33 +03:00
package.json Multi-provider AI, sucrase JSX, Figma zoom shortcuts (#72) 2026-03-08 22:05:18 +03:00
plan-refactor.md Merge docs PR, fix all lint/typecheck errors, update changelog 2026-03-07 23:19:19 +03:00
PLAN.md Fix Figma MCP timeline, update tool counts to 26, add AI providers to roadmap 2026-03-02 08:19:08 +03:00
playwright.config.ts Fix flaky E2E tests: layers panel, zoom-to-fit, snapshot stability 2026-03-07 11:08:22 +03:00
pnpm-workspace.yaml Release v0.8.0 2026-03-08 00:20:47 +03:00
README.md Update README and docs for CSS Grid support 2026-03-08 18:29:44 +03:00
SECURITY.md Add CI for PRs, issue/PR templates, CONTRIBUTING and SECURITY docs 2026-03-03 17:24:45 +03:00
tsconfig.json Fix InvalidCharacterError when copying nodes with non-ASCII text 2026-03-04 01:15:44 +03:00
tsconfig.node.json Initial commit: Tauri + CanvasKit + React editor scaffold 2026-02-27 15:48:29 +03:00
vite.config.ts Multi-provider AI, sucrase JSX, Figma zoom shortcuts (#72) 2026-03-08 22:05:18 +03:00

OpenPencil

Open-source design editor. Opens Figma files, built-in AI, fully programmable.

Status: Active development. Not ready for production use.

Try it online → · Download · Documentation

OpenPencil

Installation

macOS (Homebrew):

brew install open-pencil/tap/open-pencil

Or download from the releases page, or use the web app — no install needed.

What it does

  • Opens .fig files — read and write native Figma files, copy & paste nodes between apps
  • AI builds designs — describe what you want in chat, 90 tools create and modify nodes. Bring your own API key
  • Fully programmable — headless CLI, Figma Plugin API via eval, MCP server for AI agents
  • Real-time collaboration — P2P via WebRTC, no server, no account. Cursors, presence, follow mode
  • Auto layout & CSS Grid — flex and grid layout via Yoga WASM, with gap, padding, alignment, track sizing
  • Tailwind CSS export — export any selection as HTML with Tailwind v4 utility classes
  • ~7 MB desktop app — Tauri v2 for macOS, Windows, Linux. Also runs in the browser as a PWA

CLI

bun add -g @open-pencil/cli

Inspect .fig files

Browse node trees, search by name or type, dig into properties — all without opening the editor:

open-pencil tree design.fig
open-pencil find design.fig --type TEXT
open-pencil node design.fig --id 1:23
open-pencil info design.fig
[0] [page] "Getting started" (0:46566)
  [0] [section] "" (0:46567)
    [0] [frame] "Body" (0:46568)
      [0] [frame] "Introduction" (0:46569)
        [0] [frame] "Introduction Card" (0:46570)
          [0] [frame] "Guidance" (0:46571)

Export

Render to PNG, JPG, WEBP, SVG — or export as JSX with Tailwind utility classes:

open-pencil export design.fig                          # PNG
open-pencil export design.fig -f jpg -s 2 -q 90       # JPG at 2x, quality 90
open-pencil export design.fig -f jsx --style tailwind  # Tailwind JSX
<div className="flex flex-col gap-4 p-6 bg-white rounded-xl">
  <p className="text-2xl font-bold text-[#1D1B20]">Card Title</p>
  <p className="text-sm text-[#49454F]">Description text</p>
</div>

Analyze design tokens

Audit an entire design system from the terminal — find inconsistencies, extract the real palette, spot components waiting to be extracted:

open-pencil analyze colors design.fig
open-pencil analyze typography design.fig
open-pencil analyze spacing design.fig
open-pencil analyze clusters design.fig
#1d1b20  ██████████████████████████████ 17155×
#49454f  ██████████████████████████████ 9814×
#ffffff  ██████████████████████████████ 8620×
#6750a4  ██████████████████████████████ 3967×

3771× frame "container" (100% match)
     size: 40×40, structure: Frame > [Frame]

2982× instance "Checkboxes" (100% match)
     size: 48×48, structure: Instance > [Frame]

Script with Figma Plugin API

eval gives you the full Figma Plugin API. Modify the file, write it back:

open-pencil eval design.fig -c "figma.currentPage.children.length"
open-pencil eval design.fig -c "figma.currentPage.selection.forEach(n => n.opacity = 0.5)" -w

Control the running app

When the desktop app is running, omit the file argument — the CLI connects via RPC and operates on the live canvas. Useful for automation scripts, CI pipelines, or AI agents that need to interact with the editor:

open-pencil tree                               # Inspect the live document
open-pencil export -f png                      # Screenshot the current canvas
open-pencil eval -c "figma.currentPage.name"   # Query the editor

All commands support --json for machine-readable output.

AI & MCP

Built-in chat

Press J to open the AI assistant. It has 87 tools that can create shapes, set fills and strokes, manage auto-layout, work with components and variables, run boolean operations, analyze design tokens, and export assets. Bring your own OpenRouter API key — no backend, no account.

MCP server

Connect Claude Code, Cursor, Windsurf, or any MCP client to read and write .fig files headlessly. 90 tools (87 core + 3 file management). Full docs →

Stdio (Claude Code, Cursor, Windsurf):

bun add -g @open-pencil/mcp
{
  "mcpServers": {
    "open-pencil": {
      "command": "openpencil-mcp"
    }
  }
}

HTTP (scripts, CI):

openpencil-mcp-http   # http://localhost:3100/mcp

AI agent skill

Teach your AI coding agent to use OpenPencil — inspect designs, export assets, analyze tokens, modify .fig files:

npx skills add open-pencil/skills@open-pencil

Works with Claude Code, Cursor, Windsurf, Codex, and any agent that supports skills.

Collaboration

Share a link to co-edit in real time. No server, no account — peers connect directly via WebRTC.

  1. Click the share button in the top-right panel
  2. Share the generated link (app.openpencil.dev/share/<room-id>)
  3. Collaborators see your cursor, selection, and edits in real time
  4. Click a peer's avatar to follow their viewport

Why

Figma is a closed platform that actively fights programmatic access. Their MCP server is read-only. figma-use added full read/write automation via CDP — then Figma 126 killed CDP. Your design files are in a proprietary binary format that only their software can fully read. Your workflows break when they decide to ship a point release.

OpenPencil is the alternative: open source (MIT), reads .fig files natively, every operation is scriptable, and your data never leaves your machine.

Roadmap

  • 100% .fig compatibility — full rendering parity with Figma
  • Shader effects (SkSL) — custom visual effects via GPU shaders
  • Skewing and OkHCL color support
  • Raster tile caching — instant zoom/pan for complex documents
  • Component libraries — publish, share, and consume design systems
  • CI tools — design linting, code export, visual regression in pipelines
  • Windows code signing (Azure certificates for properly signed binaries)
  • Experimental WebGPU/Graphite rendering backend

Contributing

Setup

bun install
bun run dev        # Dev server at localhost:1420
bun run tauri dev  # Desktop app (requires Rust)

Quality gates

Command Description
bun run check Lint + typecheck
bun run test E2E visual regression
bun run test:unit Unit tests
bun run format Code formatting

Project structure

packages/
  core/           @open-pencil/core — engine (scene graph, renderer, layout, codec)
  cli/            @open-pencil/cli — headless CLI
  mcp/            @open-pencil/mcp — MCP server (stdio + HTTP)
  docs/           Documentation site (openpencil.dev)
src/              Vue app (components, composables, stores)
desktop/          Tauri v2 (Rust + config)
tests/            E2E (188 tests) + unit (764 tests)

Tech stack

Layer Tech
Rendering Skia (CanvasKit WASM)
Layout Yoga WASM
UI Vue 3, Reka UI, Tailwind CSS 4
File format Kiwi binary + Zstd + ZIP
Collaboration Trystero (WebRTC P2P) + Yjs (CRDT)
Desktop Tauri v2
AI/MCP OpenRouter, @modelcontextprotocol/sdk, Hono

Desktop builds

Requires Rust and platform-specific prerequisites (Tauri v2 guide).

bun run tauri build

Acknowledgments

Thanks to @sld0Ant (Anton Soldatov) for creating and maintaining the documentation site.

License

MIT