@unoff/cli
v0.4.4
Published
A CLI tool to create plugins for Figma, Penpot, Sketch, and Framer
Downloads
799
Maintainers
Readme
Unoff CLI
A CLI tool to quickly scaffold plugins for Figma, Penpot, Sketch, and Framer with built-in development tools and best practices.
Prerequisites
- Node.js ≥ 18
- npm ≥ 9
- git — required for
unoff create(initializes a git repository) and for allunoff addcommands (workers, skills, specs use git submodules)
Installing Prerequisites
Homebrew (macOS only)
If you don't have Homebrew installed, open Terminal and install it:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Then verify:
brew --versionNode.js and npm
On macOS: Open Terminal and run:
# Using Homebrew (requires Homebrew to be installed)
brew install nodeOn Windows:
- Download the installer from nodejs.org
- Run the installer and follow the setup wizard
- Open Command Prompt (cmd.exe) or PowerShell and verify installation:
node --version npm --version
On Linux (Ubuntu/Debian): Open Terminal and run:
sudo apt update
sudo apt install nodejs npmOn Linux (Fedora/CentOS): Open Terminal and run:
sudo dnf install nodejs npmGit
On macOS: Open Terminal and run:
# Using Homebrew
brew install gitOn Windows:
- Download the installer from git-scm.com
- Run the installer and follow the setup wizard
- Open Command Prompt (cmd.exe) or PowerShell and verify installation:
git --version
On Linux (Ubuntu/Debian): Open Terminal and run:
sudo apt update
sudo apt install gitOn Linux (Fedora/CentOS): Open Terminal and run:
sudo dnf install gitInstallation
A global install is recommended — it makes every unoff command available directly in any plugin project:
npm install -g @unoff/cliIf you prefer not to install globally, every command works with npx @unoff/cli as a drop-in replacement:
# Instead of: unoff create figma-plugin
npx @unoff/cli create figma-plugin
# Instead of: unoff dev
npx @unoff/cli dev
# Instead of: unoff build
npx @unoff/cli buildQuick Example
# Scaffold a plugin (global install)
unoff create figma-plugin
# Or without global install
npx @unoff/cli create figma-plugin
# Navigate to the plugin directory
cd my-plugin
# Install dependencies
npm install
# Start development (or: npx @unoff/cli dev)
unoff dev
# Figma: Plugins > Development > Import plugin from manifest...
# Penpot: Plugins > Add custom plugin > http://localhost:4400/manifest.jsonCLI Commands
unoff help
Show all commands, available platforms, and workers.
unoff create <platform>
Scaffold a new plugin project with an interactive prompt.
| Platform | Status |
| --------------- | -------------- |
| figma-plugin | ✅ Available |
| penpot-plugin | ✅ Available |
| sketch-plugin | 🚧 Coming soon |
| framer-plugin | 🚧 Coming soon |
The interactive prompt will ask for plugin name, output directory, GitHub username, author, license, and which external services to enable:
| Service | Default | | ------------------------------------ | ------- | | Supabase (Database & Authentication) | ✅ | | Mixpanel (Analytics) | ✅ | | Sentry (Error Monitoring) | ✅ | | Notion (Announcements & Onboarding) | ✅ |
Selected services update the is*Enabled flags in global.config.ts. All environment variable placeholders are always generated in .env.local.
unoff dev
Start development mode (npm run start:dev).
unoff build
Build for production (npm run build:prod).
unoff check
Run lint and type checking sequentially.
unoff format
Format source code with Prettier.
unoff add worker <name>
Add a Cloudflare Worker as a git submodule. Automatically updates package.json workspaces and injects the corresponding start script. You will be prompted for the destination path.
| Worker | Script | Port |
| -------------- | --------------------- | ---- |
| announcement | start:announcements | 8888 |
| auth | start:token | 8787 |
| cors | start:cors | 8989 |
unoff add worker announcement
npm install
npm run start:announcementsunoff add skills
Add the unoff-skills repository as a git submodule, then link it into the skills directory of every configured assistant.
unoff add skills
git submodule update --init --recursiveThe submodule lands in .unoff/skills/ by default — outside your skills directories on purpose. The repo root holds a unoff-create-plugin/ folder, so cloning it straight into .claude/skills/unoff-create-plugin/ would bury SKILL.md one level too deep and no assistant would ever load it. Instead each skills directory gets a symlink to the skill itself:
.unoff/skills/ # submodule (whole repo)
unoff-create-plugin/SKILL.md
.claude/skills/unoff-create-plugin -> ../../.unoff/skills/unoff-create-plugin
.agents/skills/unoff-create-plugin -> ../../.unoff/skills/unoff-create-pluginOne source of truth: git submodule update --remote refreshes every assistant at once. On platforms that refuse symlinks the CLI falls back to a copy. Run unoff sync skills after changing which assistants the project targets.
If you already installed the submodule the old way, unoff add skills detects it, moves it up a level, and links it back in.
The version is pinned in your project's history — update it deliberately with git submodule update --remote.
Alternative — skills.sh, if you would rather copy the files than track a submodule:
npx skills add yelbolt/unoff-skillsThis installs into every agent directory it detects (.claude/, .cursor/, …) instead of a single path, and records provenance in skills-lock.json. No git submodule, no pinning.
Both give you the skill library only. For skills plus specs plus agents, wired per assistant, see AI assistants below.
unoff add specs
Scaffold a functional spec — what the product does, as opposed to the skills, which describe how it is built. It asks two questions: the folder and the spec name. Nothing else — you should not have to know the architecture before writing down what you want.
unoff add specs
# → specs/credits-paywall.md---
name: credits-paywall
description: Functional spec — metered exports with a paywall past the free tier.
# Which parts of the plugin this touches: canvas, bridge, ui, config, externals.
# Narrows the skill files your assistant reads. Empty means all of them.
layers: []
# Which platforms this targets: figma, penpot. Empty means all.
platforms: []
status: draft
---
# Credits Paywall
## Problem
## User flow
## Rules
## Acceptance criteria
- [ ] Given <context>, when <action>, then <observable outcome>
## Out of scope
## Implementation noteslayers is what takes an agent from what to build to which architecture docs to read. A spec declaring layers: [ui, config] tells it to load ui/component-patterns.md, config/feature-flags.md and the rest of those layers from the skill library — no guessing.
It ships empty, which reads as "all of them": a working default that costs some context but never routes the agent away from a file it needed. Fill it in when you know — the commented frontmatter says how, and so can your assistant once the spec is written.
unoff sync specs
Regenerate specs/INDEX.md and point every AI instruction file at it. Run it after adding a spec by hand, or after editing frontmatter.
unoff sync specs # auto-detects specs/, docs/specs/ or .specs/
unoff sync specs docs/specsIt writes a managed block, delimited by <!-- unoff:specs:start --> / <!-- unoff:specs:end -->, into the rules file of each assistant configured in unoff.config.json — see AI assistants for the mapping. Assistants you did not select are never touched.
Two extra behaviours worth knowing:
- An assistant's canonical entry point (
CLAUDE.md,AGENTS.md,.github/copilot-instructions.md) is created if missing. Secondary rules files (Cursor, Windsurf) are only updated when they already exist. - Assistants with no agent file format also get the agent role table inlined, so the same routing information reaches them as instructions.
Re-running replaces the block in place — it never duplicates, and everything outside the markers is left untouched. AGENTS.md is the only file created on demand, since it is the cross-agent entry point and templates do not ship one.
The result is that an agent asked to "add the paywall" reads the spec and the matching skill files, instead of one or the other.
Renamed from
unoff specs sync. The old form still works and prints a deprecation notice.
unoff sync skills
Re-point every configured skills directory at the skills submodule. Run it after changing the assistant list in unoff.config.json, or after a fresh clone on a platform where the links did not survive.
unoff sync skillsIt is idempotent, and it repairs a submodule that was checked out inside a skills directory by moving it up a level first.
unoff remove worker <name>
Remove a worker submodule and clean up package.json (workspaces + scripts).
unoff remove worker announcementunoff remove skills
Remove the skills submodule from the project, along with the links it placed in each assistant's skills directory.
unoff remove skillsunoff remove specs
Remove the local specs folder and all its contents.
unoff remove specsAI assistants
The CLI is assistant-agnostic. It installs three pieces, each in the format the assistant actually reads:
| Piece | Answers | Source |
| ---------- | -------------- | --------------------------------------------------------------- |
| Skills | how we build | yelbolt/unoff-skills |
| Specs | what it does | your specs/ folder |
| Agents | who does it | agents/ in the same skills repo |
Start here:
unoff ai # pick assistants, then install all three
unoff ai status # what is configured, what is installedYour choice is persisted in unoff.config.json and committed, so the whole team targets the same set. Every other command (add agents, sync specs) reads it. Without a config file, the CLI falls back to detecting which assistants the project already shows traces of.
What each assistant gets
| Assistant | Rules | Skills | Agents | MCP |
| --------------- | --------------------------------- | ------------------- | --------------------------- | -------------------------------------- |
| Claude Code | CLAUDE.md | .claude/skills/ | .claude/agents/*.md | .claude/settings.json — url |
| GitHub Copilot | .github/copilot-instructions.md | — via rules path | .github/agents/*.agent.md | .vscode/mcp.json — type + url |
| ChatGPT / Codex | AGENTS.md | .agents/skills/ | role table in rules | .codex/config.toml — TOML |
| Cursor | .cursor/rules/project.mdc | — via rules path | role table in rules | .cursor/mcp.json — url |
| Windsurf | .windsurf/rules/project.md | .windsurf/skills/ | role table in rules | .windsurf/mcp.json — serverUrl |
Every column is a capability some tools have and others don't, so the CLI degrades rather than pretends:
- Agents — only Claude Code and Copilot have a file-based format. For the rest, agents become a role table inlined in the rules file they already read: same routing information, expressed as instructions.
- Skills — only Claude Code, Codex and Windsurf read a skills directory (verified against
npx skills add -a '*'; Cursor and Copilot create none). The library is installed once at a canonical path, and every rules file points at it explicitly — so it works even where there is no native directory. - MCP — one server definition per platform, four different spellings. Windsurf reads
serverUrland silently ignores a plainurl; Codex reads TOML, not JSON; VS Code wants an explicittype: "http". Each platform ships a hosted endpoint and a machine-local one (Figma desktop on127.0.0.1:3845, the Penpot dev server onlocalhost:4401). The write merges into existing JSON, so Claude'spermissionssurvive.
The canonical skills path follows the assistants you chose: .claude/skills/… when Claude is in the set, otherwise .agents/skills/…. A Codex-only project never ends up with a .claude/ folder.
unoff add agents
Install the five agents for every configured assistant. Definitions come from agents/ in the skills repo — one source, several output formats.
unoff add agents| Agent | Layers | Owns |
| ---------------------------- | --------------------- | ------------------------------------------------------------------------------- |
| unoff-plugin-architect | all | Resolves the target platform, decomposes and sequences work by layer |
| unoff-canvas-bridge | canvas, bridge | src/index.ts, src/canvas/, src/bridges/ — Canvas APIs, storage, messaging |
| unoff-ui | ui | src/app/ — Preact, @unoff/ui, Nanostores, theming, Tolgee, a11y |
| unoff-platform-services | config, externals | global.config.ts, Vite, feature flags, credits, external services, payments |
| unoff-conformance-reviewer | all | Quality gate — conventions, message contracts, Figma/Penpot parity, build |
Agents declare layers in the same vocabulary as specs. That is what closes the loop: a spec says which layers it touches, the skill library is organised by layer, and each agent owns a set of layers — so an assistant can resolve both which docs to read and which role to adopt.
Claude Code plugin
Claude users can get skills and agents pre-wired as a plugin instead of running unoff ai:
/plugin marketplace add yelbolt/claude-marketplace
/plugin install unoff@yelboltThe marketplace is shared with the other yelbolt plugins, so the first command is only needed once. Later releases: /plugin marketplace update yelbolt. Specs still come from unoff add specs and unoff sync specs — they describe your product, not ours.
Features
- 🚀 Quick setup with interactive CLI
- 📦 Multiple platform support (Figma ✅, Penpot ✅, Sketch 🚧, Framer 🚧)
- 🔧 Built-in development server with hot reload
- 🏗️ Production-ready build system
- 🎛️ Toggleable external services (Supabase, Mixpanel, Sentry, Notion)
- 🔐 Authentication scaffolding (Supabase)
- 💳 License management (LemonSqueezy)
- 📊 Analytics and monitoring (Mixpanel, Sentry)
- 🌍 Internationalization (Tolgee)
- 📢 Announcement & onboarding system (Notion + Cloudflare Workers)
- ⚙️ Worker management via git submodules (
add worker/remove worker) - 📚 Skills library via git submodule (
add skills/remove skills) or skills.sh - 📐 Functional specs discoverable by any LLM (
add specs/sync specs) — layer-routed to the implementation skills - 🤖 Claude Code plugin with 5 layer-specialized agents (
yelbolt/claude-marketplace) - 📝 Project specs scaffolding (
add specs/remove specs) - 📚 Comprehensive AI-assistant documentation
What's included in the template?
UI & Components
- @unoff/ui — Pre-built UI components for design tool plugins
Authentication & Database
- Supabase — Backend as a Service for authentication and database
Licensing & Payments
- LemonSqueezy — License management and payments
Monitoring & Analytics
Content & Communication
- Notion — CMS for announcements and onboarding
- Cloudflare Workers — Proxy layer for Notion API (auth + CORS)
Localization
- Tolgee — Translation management and i18n
Documentation
For detailed usage instructions, see USAGE.md.
Contributing
Contributions are welcome! Please open an issue or pull request on GitHub.
License
MPL-2.0
