← unDrifter

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

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)

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 ResourcesPluginsDevelopment 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 PluginsDevelopmentunDrifter

---

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 ResourcesWidgetsDevelopment 3. Click Import widget from manifest (or New widgetLink existing) 4. Select widget/manifest.json from this repo 5. The widget appears in AssetsDevelopmentunDrifter 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 firstcd 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

5. Use

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:

4. Adjust with crop handles if needed (Select/Draw)

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

EndpointMethodPurpose
---------------------------
/GETLanding page (Home, Zones, Docs, API, Logs)
/favicon.icoGET204 No Content (avoids 404 in tab)
/logsGETServer log buffer (last 500 lines; for debugging)
/zonesGETGet all zones
/zonesPOSTCreate zones
/zones/:idGETGet single zone
/zones/:idPUTUpdate zone
/zones/:idDELETEDelete zone
/zones/by-urlGETGet zones for URL
/zones/by-urlDELETEDelete all zones for URL
/zones/:id/childrenPOSTAdd manual content
/zonesDELETEDelete all zones
/copydocGETGet full CopyDoc
/proxy/imageGETProxy external images
/auth/loginGETStart OAuth flow
/auth/callbackGETOAuth callback
/auth/statusGETCheck auth status
/auth/logoutPOSTClear auth

---

Troubleshooting

"Port in use" or EADDRINUSE

"Cannot connect to server"

Extension not loading

Plugin can't fetch zones

Images show as gray boxes

"Syntax error" in plugin

Zones not loading in extension

"This site can't be reached" on /auth/callback (ERR_FAILED)

---

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

2. Enable Chat Completions

gateway.http.endpoints.chatCompletions.enabled: true.

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:

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

In the Figma plugin, open SettingsAI: ✓ 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."