unDrifter Setup Guide
Complete setup instructions for running unDrifter locally.
What you get: Capture page copy and screenshots in Chrome; the server stores them; the Figma / FigJam plugin injects them and can link text layers to the source for updates. Optional FigJam widget for variables on the board.
Using a team-hosted server instead of localhost? Follow docs/TRY_HOSTED.md (build clients with UNDRIFTER_SERVER, then load extension + plugin).
Prerequisites
- Node.js 18+ (required)
- Chrome (for the extension)
- Figma Desktop or FigJam (for the plugin)
- Bun (optional, faster): https://bun.sh
- mkcert (for local HTTPS): https://github.com/FiloSottile/mkcert
mkcert setup (one-time)
unDrifter uses HTTPS-only so the FigJam widget works from https://www.figma.com (browsers block HTTP from HTTPS pages). mkcert creates locally trusted certs—no browser warnings.
bash
Install mkcert (macOS: brew install mkcert; or see https://github.com/FiloSottile/mkcert)
brew install mkcert # or: choco install mkcert (Windows)
Trust the local CA (one-time per machine)
mkcert -install
Generate certs for unDrifter (from server directory)
cd server
mkcert localhost 127.0.0.1 ::1
This creates localhost+2.pem (cert) and localhost+2-key.pem (key) in server/. Add to server/.env:
SSL_CERT=./localhost+2.pem
SSL_KEY=./localhost+2-key.pem
Or use absolute paths. The server reads these on startup. Add server/*.pem to .gitignore—cert files are local and should not be committed.
Quick Start
From the repo root you can use a single control surface:
bash
Clone and install all dependencies (from repo root)
npm run install:all
Start the server (keep running)
npm start
When done, or to clear "port in use" errors:
npm run stop
To restart (stop + wait + start):
npm run restart
If you get "port in use": Run npm run stop first, then npm start. Use npm run restart to do both with a short delay so the port releases.
Root scripts (from repo root)
npm start— Start the local server (HTTPS on port 3001)npm run stop— Stop the server (kills processes on 3001, 3002)npm run restart— Stop, wait 2.5s for port release, then startnpm run build— Build extension, plugin, widget (all use https://localhost:3001)npm run reload:all— Build all + print reload steps (use after code changes)npm run figjam— Start server + build + reload instructions (FigJam flow)npm run install:all— Install dependencies in server, extension, plugin, shared
Or install per package:
bash
cd server && npm install && cd ..
cd extension && npm install && cd ..
cd plugin && npm install && cd ..
---
FigJam Quick Start
Use FigJam to edit CopyDoc without a design file. One command gets you running.
1. One-time: mkcert (see Prerequisites above)
2. Terminal 1: npm run figjam — starts server and builds; prints reload steps
3. FigJam: Resources → Widgets → Development → Import from widget/manifest.json
4. Insert the unDrifter Copy widget, add variables, link Figma if needed
To rebuild after code changes: npm run reload:all. See FigJam Flow (HTTPS-only) for details.
---
1. Local Server
The server bridges the Chrome extension and Figma plugin.
bash
cd server
npm run dev
You should see:
╔══════════════════════════════════════════════════════════════╗
║ unDrifter Server ║
║ Status: RUNNING ║
║ URL: https://localhost:3001 ║
╚══════════════════════════════════════════════════════════════╝
Keep this terminal running.
Optional: Figma OAuth Setup
If you want OAuth authentication (optional for v0):
1. Start the server first (see "1. Local Server" above). OAuth redirects to https://localhost:3001/auth/callback — if the server isn’t running, you’ll get "This site can’t be reached" / ERR_FAILED.
2. Go to https://www.figma.com/developers/apps
3. Create a new app
4. Set callback URL to: https://localhost:3001/auth/callback
5. Enable scopes: file_content:read, file_metadata:read
6. Copy Client ID and Secret
7. Create server/.env (from the local template: cp server/.env.local.template server/.env), then set at least:
FIGMA_CLIENT_ID=your_client_id
FIGMA_CLIENT_SECRET=your_client_secret
For production, start from server/.env.prod.template instead; see server/.env.example. The public hub is https://undrifter.com; self-hosters set PUBLIC_URL to their origin.
---
2. Chrome Extension
bash
cd extension
npm run build
Load in Chrome:
1. Go to chrome://extensions
2. Enable Developer mode (top right toggle)
3. Click Load unpacked
4. Select the extension/dist folder
The unDrifter icon should appear in your toolbar.
Development Mode:
bash
npm run dev
This enables hot reload — changes rebuild automatically.
---
3. FigJam/Figma Plugin
bash
cd plugin
npm run build
Link in Figma:
1. Open Figma Desktop
2. Go to Resources → Plugins → Development
3. Click New plugin...
4. Choose Link existing plugin
5. Select plugin/manifest.json from this repo
6. Note the generated plugin ID
7. Replace PLUGIN_ID_PLACEHOLDER in plugin/manifest.json with your ID
8. Rebuild: npm run build
Now run from Plugins → Development → unDrifter
---
4. FigJam Widget (CopyDoc Editor)
The widget lets you create and edit the CopyDoc in FigJam—with or without a design file.
bash
cd widget
npm run build
Or from repo root: npm run build:widget
Import in FigJam:
1. Open FigJam
2. Go to Resources → Widgets → Development
3. Click Import widget from manifest (or New widget → Link existing)
4. Select widget/manifest.json from this repo
5. The widget appears in Assets → Development → unDrifter Copy
Widget usage:
Create CopyDoc from FigJam (before any design exists):
1. Start the unDrifter server (npm start from repo root)
2. In FigJam, insert the unDrifter Copy widget
3. Add variables: enter Name (optional, e.g. hero-headline) and Value, click Add
4. Edit in sticky notes—blur or Enter to save
5. Optional: Paste Figma design URL/key, click Link Figma
Use with extension capture: 1. Capture copy in the extension and push to FigJam 2. Click Refresh in the widget to load variables 3. Edit inline; changes sync to the server 4. In Figma design file: unDrifter plugin → Sync vars to pull edits
Widget: "Request failed" troubleshooting
1. Start the server first — cd server && npm run dev (or npm start from repo root).
2. FigJam in browser: FigJam runs on https://www.figma.com. unDrifter uses HTTPS-only so this works. Ensure mkcert is set up and server uses https://localhost:3001.
3. Re-import after manifest changes — If you changed networkAccess in widget/manifest.json, remove and re-import the widget so FigJam picks up the new config.
---
FigJam Flow (HTTPS-only)
All components use HTTPS on port 3001. FigJam runs on https://www.figma.com; browsers block HTTP requests from HTTPS pages, so everything must use HTTPS.
1. Set up mkcert (one-time; see Prerequisites above)
2. Start server (Terminal 1, leave running)
bash
npm start
Server listens on https://localhost:3001 (mkcert or self-signed fallback).
3. Build (Terminal 2)
bash
npm run build
Or use npm run figjam to start + build + show reload steps.
4. Reload tools
- Chrome: Reload extension
- Figma: Reload plugin
- FigJam: Re-import widget from
widget/manifest.json
5. Use
- Landing page, extension, plugin, FigJam widget → https://localhost:3001
Note: The "aria-hidden" accessibility warning in the console comes from Figma's own UI, not unDrifter. It does not block the widget from working.
---
Usage Workflow
Capture Zones (Extension)
1. Navigate to any webpage 2. Click the unDrifter extension icon 3. Choose a capture mode:
- Select — Click on the page to detect a container (use arrows to move one level in/out)
- Draw — Drag a full-width band to select a vertical region
- Full Page — Click "Full Page" to capture the entire page (scrolls to load lazy content first)
5. Enter a zone name and confirm 6. Repeat for multiple zones 7. Click Push to FigJam
Inject to Canvas (Plugin)
1. Open FigJam or Figma file 2. Run unDrifter plugin 3. Click 🔄 Refresh to load zones 4. Click ⬇️ Inject All to Canvas
Paste to Layers (Plugin)
1. Select a text layer on canvas 2. In plugin's Outline view, find the content 3. Click Paste & Link 4. Content is pasted and linked
Create Variables (Plugin)
1. Load zones in plugin 2. Click 📊 Vars 3. Opens Figma's Variables panel with CopyDoc collection
Sync Links (Plugin)
1. Switch to 🔗 Links tab 2. See all linked layers 3. Click Sync All to update from source
FigJam Widget → Figma Sync
1. In FigJam, insert unDrifter Copy widget 2. Add variables or Refresh to load from extension 3. Edit in sticky notes (blur/Enter saves) 4. In Figma design file: unDrifter plugin → Sync vars 5. Bind text layers to CopyDoc variables to see updates
Manage Zones by URL (Plugin)
1. Zones are automatically grouped by source URL
2. Click URL link to open in browser (adds ?undrifter=load)
3. Extension automatically loads zones for that URL
4. Edit zone names or delete zones in extension
5. Changes sync to server immediately
6. Use delete button (🗑️) in plugin to remove all zones from a URL
---
File Structure
unDrifter/
├── extension/ # Chrome Extension (MV3)
│ ├── src/
│ │ ├── background/ # Service worker
│ │ ├── content/ # Overlay UI + extraction
│ │ ├── store/ # Zustand state
│ │ └── panel/ # Options page
│ ├── manifest.json
│ └── dist/ # Built extension (load this)
│
├── server/ # Local Server
│ ├── src/
│ │ ├── index.ts # Hono server, landing page, zones, enrich, logs
│ │ ├── enrich-provider.ts
│ │ ├── ollama.ts
│ │ └── openclaw.ts
│ ├── data/
│ │ ├── copydoc.json # Persisted zones
│ │ ├── structure-examples.json # User good/bad examples (Feed Gwen)
│ │ ├── semantic-vocabulary.json # Semantic component names for clean-structure
│ │ ├── base-structure-corpus.json # Seed good/bad examples (committed)
│ │ └── screenshots/ # Zone screenshots (optional)
│ ├── .env.example # Points to .env.local.template vs .env.prod.template
│ ├── .env.local.template # Copy to .env for localhost dev
│ ├── .env.prod.template # Copy to .env for production / hosted hub
│ └── .env # OAuth, Ollama/OpenClaw (optional; not committed)
│
├── plugin/ # Figma Plugin
│ ├── src/
│ │ ├── code.ts # Plugin API code
│ │ └── ui.html # Plugin UI
│ ├── manifest.json
│ └── dist/ # Built plugin
│
├── shared/ # Shared Types
│ └── types.ts
│
├── Readme.md
├── SETUP.md # This file
└── AGENTS.md # AI agent context
---
API Endpoints
| Endpoint | Method | Purpose |
| ---------- | -------- | --------- |
/ | GET | Landing page (Home, Zones, Docs, API, Logs) |
/favicon.ico | GET | 204 No Content (avoids 404 in tab) |
/logs | GET | Server log buffer (last 500 lines; for debugging) |
/zones | GET | Get all zones |
/zones | POST | Create zones |
/zones/:id | GET | Get single zone |
/zones/:id | PUT | Update zone |
/zones/:id | DELETE | Delete zone |
/zones/by-url | GET | Get zones for URL |
/zones/by-url | DELETE | Delete all zones for URL |
/zones/:id/children | POST | Add manual content |
/zones | DELETE | Delete all zones |
/copydoc | GET | Get full CopyDoc |
/proxy/image | GET | Proxy external images |
/auth/login | GET | Start OAuth flow |
/auth/callback | GET | OAuth callback |
/auth/status | GET | Check auth status |
/auth/logout | POST | Clear auth |
---
Troubleshooting
"Port in use" or EADDRINUSE
- Run
npm run stop— Kills processes on ports 3001 and 3002 - Then
npm start— Or usenpm run restart(stop + 2.5s delay + start) so the port releases before starting
"Cannot connect to server"
- Make sure server is running on port 3001
- Check terminal for errors
- Try
curl -sk https://localhost:3001to test
Extension not loading
- Make sure you selected
extension/dist, notextension/ - Check Chrome DevTools console for errors
- Rebuild:
npm run build
Plugin can't fetch zones
- Server must be running
- Check plugin manifest has
networkAccess.allowedDomains: ["https://localhost:3001"] - Open Figma console (Plugins → Development → Open console)
Images show as gray boxes
- External images need proxy
- Check server logs for proxy errors
- Some sites block image requests
"Syntax error" in plugin
- Figma uses older JS engine
- No spread syntax, no
for...ofon Sets - Rebuild after any code changes
Zones not loading in extension
- Make sure URL has
?undrifter=loadquery param - Check extension console for fetch errors
- Verify server is running and zones exist for that URL
- URL is normalized (query params/hash removed) for matching
"This site can't be reached" on /auth/callback (ERR_FAILED)
- The server was not running when Figma redirected your browser to
https://localhost:3001/auth/callback. - Start the server first: from repo root run
npm startorcd server && npm run dev, then go to/auth/loginagain (or your app’s "Connect to Figma" link). - Ensure nothing else is using port 3001 and that you’re using the same port in your Figma app callback URL.
---
Development Commands
bash
Server
cd server
npm run dev # Node.js with tsx
npm run dev:bun # Bun (faster)
Extension
cd extension
npm run dev # Watch mode
npm run build # Production
Plugin
cd plugin
npm run dev # Watch mode
npm run build # Production
---
Environment Variables
Server (server/.env)
env
PORT=3001
FIGMA_CLIENT_ID=figma_oauth_client_id
FIGMA_CLIENT_SECRET=figma_oauth_client_secret
Callback URL for OAuth: https://localhost:3001/auth/callback
All are optional. Defaults work for local development. For AI enrichment (Ollama or OpenClaw), see docs/AI_FEATURES.md.
---
Get OpenClaw running (optional)
Use OpenClaw instead of Ollama for enrichment (skills + persistent memory). UnDrifter supports the current OpenClaw Gateway (recommended) and a legacy/custom skill server contract.
OpenClaw runs on your machine (or a VPS you control). unDrifter talks to it over HTTP (OPENCLAW_URL); it does not embed inside the Ollama Cloud API.
Option A0 — Ollama launch openclaw (Ollama 0.17+)
Ollama can install and wire OpenClaw for you, including cloud models (:cloud). Official walkthrough: The simplest and fastest way to setup OpenClaw (Ollama blog).
Example (cloud model):
bash
ollama launch openclaw --model kimi-k2.5:cloud
Run ollama launch openclaw without flags to see other recommended models. Cloud models use your Ollama account; local models use your GPU instead.
After OpenClaw is up, unDrifter still needs the Gateway Chat Completions HTTP API (same as Option A below): default port 18789, gateway.http.endpoints.chatCompletions.enabled: true in OpenClaw config. Then set unDrifter env using the checklist in the next subsection.
Security: OpenClaw can use tools and access your system. See OpenClaw gateway security.
Option A — Current OpenClaw Gateway (recommended)
The official OpenClaw is a self-hosted gateway (WhatsApp, Telegram, Discord, etc.) that can expose an OpenAI-compatible Chat Completions endpoint. Default port: 18789.
1. Install and start the Gateway
- Quick path: Option A0 (
ollama launch openclaw …) if you use Ollama 0.17+. - Manual: OpenClaw Setup or:
npm install -g openclaw@latestthenopenclaw onboard --install-daemon, thenopenclaw gateway --port 18789. - Open dashboard at
http://127.0.0.1:18789/(auth required).
2. Enable Chat Completions
- In
~/.openclaw/openclaw.json(or your config), set:
gateway.http.endpoints.chatCompletions.enabled: true.
- Note your gateway token (or password): from config or env
OPENCLAW_GATEWAY_TOKEN/OPENCLAW_GATEWAY_PASSWORD.
3. Configure unDrifter in server/.env (gateway must be reachable from the same host as the server, or use a private URL / tunnel—never expose an unauthenticated gateway publicly):
| Variable | Value / notes |
|----------|----------------|
| OPENCLAW_ENABLED | true |
| OPENCLAW_URL | http://127.0.0.1:18789 (or http://localhost:18789, or your tunnel/VPC URL) |
| OPENCLAW_USE_CHAT_COMPLETIONS | true |
| OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD | As required by your gateway config |
env
OPENCLAW_ENABLED=true
OPENCLAW_URL=http://localhost:18789
OPENCLAW_USE_CHAT_COMPLETIONS=true
OPENCLAW_GATEWAY_TOKEN=your_gateway_token
Optional: OPENCLAW_TIMEOUT_MS=60000.
Hosted unDrifter (e.g. DigitalOcean) with OpenClaw on your laptop: the app cannot reach localhost on your machine—run the gateway on a Droplet/VPC the app can call, or use a secured tunnel. See docs/deploy/OPENCLAW_DROPLET.md.
Option B — Legacy / custom skill server
If you run a custom server that exposes a skill endpoint (e.g. POST /skill/enrich-chat with body { messages: [{ role, content }] } and returns { content: string }), leave OPENCLAW_USE_CHAT_COMPLETIONS unset and point unDrifter at that server:
env
OPENCLAW_ENABLED=true
OPENCLAW_URL=http://localhost:3002
OPENCLAW_SKILL_CHAT=/skill/enrich-chat
OPENCLAW_SKILL_VISION=/skill/enrich-vision
Use the port where your skill server listens (e.g. 3002 so it doesn’t clash with unDrifter on 3001).
Restart and verify
bash
npm run restart
Then:
- https://localhost:3001 → status line: Enrichment: ✓ OpenClaw …
- CLI checks (replace host/port if needed):
bash
curl -sk https://localhost:3001/enrich/status | jq '.data.provider, .data.openClaw'
expect: "openclaw" and an object with enabled / useChatCompletions
curl -sk https://localhost:3001/enrich/test | jq '.success'
expect: true
- https://localhost:3001/enrich/status in the browser →
"provider": "openclaw"(andopenClaw.useChatCompletions: truewhen using the Gateway). - https://localhost:3001/enrich/test →
success: true.
In the Figma plugin, open Settings → AI: ✓ ready. Try Suggest name in Blank or Enrich on a zone.
Trade-offs with OpenClaw as the only enrich provider: POST /enrich/chat/stream (Gwen SSE) and POST /enrich/chat/mosme are Ollama-only in unDrifter today—the hub uses non-streaming Gwen chat and MOSME is unavailable until you switch back to Ollama as provider. See docs/AI_FEATURES.md (Planned) for possible future routing.
If you use Ollama as well, set OLLAMA_ENABLED=false when testing OpenClaw so the server doesn’t fall back to Ollama.
---
IDE: MOSME (optional Figma jockey)
For Cursor (or similar) with Figma Console MCP and this repo’s mosme-enrichment MCP—Figma PAT stays on your machine—see docs/mosme/README.md. One-time compile:
bash
npm run build:mosme-enrichment
Then merge docs/mosme/CURSOR_MCP_EXAMPLE.json into your MCP config (do not commit tokens).
---
Deploy to Digital Ocean
To run the server on Digital Ocean (or another host) so extension and plugin can use a hosted URL, see docs/DEPLOY.md. It covers env vars (PORT, PUBLIC_URL, FIGMA_CALLBACK_URL, TRUST_PROXY), building extension/plugin/widget with UNDRIFTER_SERVER, and a sanity check: run curl https://undrifter.com/enrich/status (or your hub) before loading any client.
---
Release testing
Before pushing a release:
1. Build everything: npm run build (extension, plugin, widget).
2. Run server: npm start; confirm https://localhost:3001.
3. E2E flows: Use docs/backlog/RELEASE_TESTING.md for step-by-step feature testing (capture → plugin, widget ↔ Figma, vars, URL management) and release-branch checklist.
---
"Ka is a wheel."