figma-dev-bypass 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,123 @@
1
+ # Agent instructions
2
+
3
+ Use this playbook when implementing UI from Figma in a consumer project. The spec JSON is the contract — do not guess colors, copy, or layout from screenshots.
4
+
5
+ ## Setup from zero
6
+
7
+ 1. **Install the CLI**
8
+
9
+ ```bash
10
+ git clone https://github.com/guilhermemouraovc/figma-dev-bypass.git
11
+ cd figma-dev-bypass
12
+ cp .env.example .env
13
+ ```
14
+
15
+ Or, once published: `npx figma-dev-bypass --help`
16
+
17
+ 2. **Get a Figma token**
18
+
19
+ - Go to [figma.com/developers/api#access-tokens](https://www.figma.com/developers/api#access-tokens)
20
+ - Create a personal access token with scope **`file_content:read`**
21
+ - Add it to `.env`: `FIGMA_TOKEN=figd_...`
22
+
23
+ 3. **Find file key and node id from a Figma URL**
24
+
25
+ ```
26
+ https://www.figma.com/design/RXv1deGII4NMLUjk8yPm3w/My-File?node-id=1021-16710
27
+ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^
28
+ file-key node-id → 1021:16710
29
+ ```
30
+
31
+ Set in `.env` or pass as CLI flags: `--file-key` and `--node`.
32
+
33
+ ## Workflow
34
+
35
+ 1. **List frames** in the target section:
36
+
37
+ ```bash
38
+ figma-dev-bypass --list --file-key <KEY> --node <ID>
39
+ ```
40
+
41
+ 2. **Extract specs** (do not guess from screenshots):
42
+
43
+ ```bash
44
+ figma-dev-bypass --extract --file-key <KEY> --node <ID> \
45
+ --frames "Login,Register" \
46
+ --tokens path/to/variables.scss \
47
+ --agent-pack --target vue
48
+ ```
49
+
50
+ 3. **Read the output**
51
+ - With `--agent-pack`: `output/specs/<slug>/spec.json`, `prompt.md`, `checklist.md`
52
+ - Without: `output/specs/<slug>.json`
53
+
54
+ 4. **Implement** using the consumer project's component patterns. Prefer `tokenMatch` over raw hex.
55
+
56
+ 5. **Verify** against `checklist.md` when using `--agent-pack`.
57
+
58
+ ## Agent operating rules
59
+
60
+ - Treat `spec.json` as the source of truth. Screenshots are secondary references only.
61
+ - Read `prompt.md` and `checklist.md` before editing UI code.
62
+ - Prefer `tokenMatch` values over raw hex when the consumer project has matching tokens.
63
+ - Use the consumer project's existing components and layout patterns.
64
+ - Use `box` coordinates to understand relative hierarchy and spacing; avoid blind absolute positioning unless the app already uses it.
65
+ - Do not invent copy, colors, buttons, input fields, navigation, or progress states that are not present in the spec.
66
+ - If a required UI element is missing from the spec, stop and ask whether to update the Figma design or implement a project-specific exception.
67
+
68
+ ## Spec fields
69
+
70
+ | Field | Meaning |
71
+ |---|---|
72
+ | `name` | Frame name in Figma |
73
+ | `nodeId` | Figma node id |
74
+ | `size` | Frame width and height |
75
+ | `background` | Frame fill color |
76
+ | `texts[]` | Copy + typography + relative position (`box`) |
77
+ | `buttons[]` | CTA instances or drawn rectangles with label |
78
+ | `bars[]` | Thin rounded rectangles (raw, before progress pairing) |
79
+ | `progress` | Paired thin bars (track + fill) when two bars align |
80
+ | `tokenMatch` | Suggested SCSS variable when `--tokens` was passed |
81
+
82
+ Full schema: [schema/spec.schema.json](schema/spec.schema.json)
83
+
84
+ ## Target framework (`--target`)
85
+
86
+ When using `--agent-pack`, pass `--target` to get framework-specific guidance in `prompt.md`:
87
+
88
+ | Value | Use for |
89
+ |---|---|
90
+ | `generic` | Framework-agnostic (default) |
91
+ | `react` | React + CSS Modules / Tailwind |
92
+ | `vue` | Vue 3 Composition API |
93
+ | `quasar` | Quasar Framework |
94
+ | `react-native` | React Native |
95
+ | `swiftui` | SwiftUI |
96
+
97
+ The spec JSON stays neutral — only the agent prompt changes.
98
+
99
+ ## Consumer project setup
100
+
101
+ Copy specs into your app repo (e.g. `design/specs/`) and reference them in `CLAUDE.md`, Cursor rules, or `AGENTS.md`:
102
+
103
+ > Before implementing onboarding UI, read the matching file in `design/specs/`.
104
+
105
+ Optional: copy [skills/figma-dev-bypass/SKILL.md](skills/figma-dev-bypass/SKILL.md) into your project's `.cursor/skills/` or `.claude/skills/` folder.
106
+
107
+ See [examples/cursor-rule.md](examples/cursor-rule.md) for a ready-to-paste Cursor rule.
108
+
109
+ ## Copy-paste prompt for implementation
110
+
111
+ ```markdown
112
+ Implement the `<ScreenName>` screen from `design/specs/<screen-slug>/`.
113
+
114
+ Before coding:
115
+ - Read `spec.json`, `prompt.md`, and `checklist.md`.
116
+ - Use existing app components and styling conventions.
117
+ - Prefer `tokenMatch` variables over raw hex.
118
+ - Preserve exact text copy, typography, button labels, and progress values from the spec.
119
+
120
+ After coding:
121
+ - Run the relevant tests, type checks, or lint command.
122
+ - Verify every item in `checklist.md`.
123
+ ```
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Guilherme Mourão
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # figma-dev-bypass
2
+
3
+ [![npm version](https://img.shields.io/npm/v/figma-dev-bypass)](https://www.npmjs.com/package/figma-dev-bypass)
4
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+ [![node](https://img.shields.io/badge/node-%3E%3D18-green.svg)](https://nodejs.org/)
6
+ [![dependencies](https://img.shields.io/badge/dependencies-zero-brightgreen.svg)](#)
7
+
8
+ **Structured design contracts for AI coding agents — extracted from Figma without Dev Mode.**
9
+
10
+ A lightweight CLI that uses the [Figma REST API](https://www.figma.com/developers/api) to fetch frame data, let you pick which screens to export, and output **agent-ready JSON specs** (colors, typography, buttons, progress bars, spacing) that Claude Code, Cursor, Codex, and any coding agent can implement against.
11
+
12
+ No MCP rate limits. No per-screen AI calls. No paid Dev Mode seats required.
13
+
14
+ <!-- TODO: record demo GIF: --list then --extract then cat spec.json -->
15
+
16
+ ## Demo
17
+
18
+ _Recording coming soon — run `npm run list` then `npm run extract -- --frames "Login" --agent-pack` to try it locally._
19
+
20
+ ## Why agent-ready?
21
+
22
+ | Problem | This tool |
23
+ |---|---|
24
+ | Dev Mode / MCP costs and limits | One REST call per section, cached locally |
25
+ | Screenshots are expensive for LLMs | Small JSON specs (~2–5 KB per screen) |
26
+ | Design changes are invisible in code | Version specs in git, diff in PRs |
27
+ | Agents guess colors and copy | Structured contract with exact values |
28
+
29
+ ## Features
30
+
31
+ - List frames in a Figma section (`--list`)
32
+ - Extract one or many screens (`--frames`, `--ids`, `--all`)
33
+ - Local API cache (`output/cache/`) to respect rate limits
34
+ - Optional design-token matching via SCSS file (`--tokens`)
35
+ - Optional PNG exports (`--images`)
36
+ - Agent pack: `spec.json` + `prompt.md` + `checklist.md` per screen (`--agent-pack`)
37
+ - Framework-specific agent hints (`--target react|vue|quasar|react-native|swiftui`)
38
+ - Zero npm dependencies — Node.js 18+ only
39
+
40
+ ## Requirements
41
+
42
+ - [Node.js](https://nodejs.org/) 18+
43
+ - Figma [personal access token](https://www.figma.com/developers/api#access-tokens) with `file_content:read` scope
44
+ - View access to the target Figma file
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ git clone https://github.com/guilhermemouraovc/figma-dev-bypass.git
50
+ cd figma-dev-bypass
51
+ cp .env.example .env
52
+ # Edit .env — add FIGMA_TOKEN (and optional defaults)
53
+ ```
54
+
55
+ Or link globally:
56
+
57
+ ```bash
58
+ npm link
59
+ # then run: figma-dev-bypass --help
60
+ ```
61
+
62
+ ## Quick start
63
+
64
+ ```bash
65
+ # 1. Configure token
66
+ export FIGMA_TOKEN=figd_xxxxxxxx
67
+
68
+ # 2. List available frames in a section
69
+ node src/cli.mjs --list \
70
+ --file-key RXv1deGII4NMLUjk8yPm3w \
71
+ --node 1021:16710
72
+
73
+ # 3. Extract specific screens
74
+ node src/cli.mjs --extract \
75
+ --file-key RXv1deGII4NMLUjk8yPm3w \
76
+ --node 1021:16710 \
77
+ --frames "Login,Register,Payment"
78
+
79
+ # 4. Extract everything + agent prompts + token matching + Vue hints
80
+ node src/cli.mjs --extract --all \
81
+ --file-key RXv1deGII4NMLUjk8yPm3w \
82
+ --node 1021:16710 \
83
+ --tokens ./examples/tokens.example.scss \
84
+ --agent-pack --target vue
85
+ ```
86
+
87
+ With `.env` defaults (`FIGMA_FILE_KEY`, `FIGMA_NODE`):
88
+
89
+ ```bash
90
+ npm run list
91
+ npm run extract -- --frames "Login" --agent-pack --target vue
92
+ ```
93
+
94
+ Use `node --env-file=.env src/cli.mjs` if your Node version supports `--env-file`.
95
+
96
+ ## Teach your team
97
+
98
+ Use [docs/team-playbook.md](docs/team-playbook.md) to onboard designers, developers, and coding agents. It includes the golden path, copy-paste agent prompts, PR review checklist, Figma handoff conventions, and common mistakes to avoid.
99
+
100
+ ## CLI reference
101
+
102
+ ```
103
+ figma-dev-bypass --list --file-key KEY --node ID
104
+ figma-dev-bypass --extract --file-key KEY --node ID (--all | --frames "a,b" | --ids x,y)
105
+
106
+ Options:
107
+ --out DIR Output directory (default: output/specs)
108
+ --cache DIR API cache (default: output/cache)
109
+ --tokens PATH SCSS variables file for tokenMatch
110
+ --refresh Re-download from API
111
+ --images Export PNGs to <out>/img/
112
+ --agent-pack Write prompt.md + checklist.md per screen
113
+ --target NAME Framework hints for agent-pack (generic, react, vue, quasar, react-native, swiftui)
114
+ --version Show version
115
+ ```
116
+
117
+ ### Finding file key and node id
118
+
119
+ From a Figma URL:
120
+
121
+ ```
122
+ https://www.figma.com/design/RXv1deGII4NMLUjk8yPm3w/My-File?node-id=1021-16710
123
+ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^
124
+ file-key node-id → 1021:16710
125
+ ```
126
+
127
+ ## Output format
128
+
129
+ Each screen becomes a JSON spec. See [schema/spec.schema.json](schema/spec.schema.json) for the full contract.
130
+
131
+ ```json
132
+ {
133
+ "name": "Registre-se",
134
+ "nodeId": "918:906",
135
+ "size": { "width": 440, "height": 956 },
136
+ "background": { "hex": "#121212", "tokenMatch": "$dm-bg" },
137
+ "texts": [
138
+ { "text": "Registre-se", "fontSize": 32, "fontWeight": 800, "color": { "hex": "#FFFFFF" } }
139
+ ],
140
+ "buttons": [
141
+ { "label": "Continuar", "fill": { "hex": "#410E47" }, "radius": 68 }
142
+ ],
143
+ "progress": { "track": { "hex": "#1E1E1E" }, "fill": { "hex": "#C184FF" }, "radius": 93 }
144
+ }
145
+ ```
146
+
147
+ With `--agent-pack`, each screen gets its own folder:
148
+
149
+ ```
150
+ output/specs/registre-se/
151
+ spec.json
152
+ prompt.md # ready-to-paste implementation prompt
153
+ checklist.md # verification checklist
154
+ ```
155
+
156
+ See [examples/login.spec.json](examples/login.spec.json) for a sample.
157
+
158
+ ## Using specs in your app repo
159
+
160
+ 1. Extract specs into your project: `--out ../my-app/design/specs`
161
+ 2. Commit `design/specs/*.json` (not `output/cache/`)
162
+ 3. Point your agent at the spec before implementing UI — see [AGENTS.md](AGENTS.md)
163
+ 4. Optional: pass your project's SCSS variables with `--tokens src/css/variables.scss`
164
+
165
+ Specs never ship to mobile/web bundles — they are dev-time contracts only.
166
+
167
+ ## For AI coding tools
168
+
169
+ | Tool | How to use |
170
+ |---|---|
171
+ | **Claude Code** | Copy [skills/figma-dev-bypass/SKILL.md](skills/figma-dev-bypass/SKILL.md) to `.claude/skills/`; run extract with `--agent-pack`; implement from `prompt.md` |
172
+ | **Cursor** | Copy the skill to `.cursor/skills/`; add [examples/cursor-rule.md](examples/cursor-rule.md) as a project rule |
173
+ | **Codex / others** | Same JSON contract; no Figma integration needed at implement time |
174
+
175
+ For team rollout, start with the copy-paste prompts and review checklist in [docs/team-playbook.md](docs/team-playbook.md).
176
+
177
+ ## Security
178
+
179
+ - Never commit `FIGMA_TOKEN` or `.env`
180
+ - Token only needs `file_content:read`
181
+ - Revoke tokens you accidentally exposed
182
+
183
+ ## Limitations
184
+
185
+ - Uses the official Figma REST API — not a scraper or Dev Mode hack
186
+ - Animations and complex interactions are not captured
187
+ - Some hand-drawn buttons may need manual review (radius detection heuristic)
188
+ - Illustrations/vectors are listed as assets, not reconstructed in CSS
189
+
190
+ ## Contributing
191
+
192
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
193
+
194
+ ## License
195
+
196
+ MIT — see [LICENSE](LICENSE)
@@ -0,0 +1,128 @@
1
+ # Team Playbook
2
+
3
+ Use this guide to teach a team how to turn Figma screens into implementation-ready specs for coding agents.
4
+
5
+ ## Mental model
6
+
7
+ `figma-dev-bypass` is not a code generator. It is a deterministic extraction step:
8
+
9
+ ```text
10
+ Figma REST API -> JSON spec -> agent implements UI in your app
11
+ ```
12
+
13
+ The JSON spec is the source of truth for copy, colors, typography, buttons, progress bars, and relative positions. Screenshots are only visual references; agents should not guess implementation details from screenshots.
14
+
15
+ ## Roles
16
+
17
+ | Role | Responsibility |
18
+ |---|---|
19
+ | Designer | Keeps frame names stable and gives developers access to the Figma file |
20
+ | Developer | Extracts specs, commits them to the app repo, and maps `tokenMatch` to real project tokens |
21
+ | Coding agent | Reads `spec.json`, follows `prompt.md`, implements with project components, and verifies `checklist.md` |
22
+
23
+ ## Golden path
24
+
25
+ ### 1. Extract specs from this repo
26
+
27
+ ```bash
28
+ cp .env.example .env
29
+ # add FIGMA_TOKEN, FIGMA_FILE_KEY, FIGMA_NODE
30
+
31
+ npm run list
32
+ npm run extract -- --frames "Login,Register" \
33
+ --tokens ../my-app/src/css/variables.scss \
34
+ --out ../my-app/design/specs \
35
+ --agent-pack --target vue
36
+ ```
37
+
38
+ Use `--target quasar`, `--target react`, `--target react-native`, or `--target swiftui` when that matches the consumer app. The spec stays framework-neutral; only `prompt.md` changes.
39
+
40
+ ### 2. Commit specs in the consumer app
41
+
42
+ Commit the generated `design/specs/` files in the app repo. Do not commit `output/cache/` or `.env`.
43
+
44
+ Recommended layout:
45
+
46
+ ```text
47
+ my-app/
48
+ design/
49
+ specs/
50
+ login/
51
+ spec.json
52
+ prompt.md
53
+ checklist.md
54
+ ```
55
+
56
+ ### 3. Add an agent rule to the consumer app
57
+
58
+ Add this policy to `AGENTS.md`, `CLAUDE.md`, or Cursor rules:
59
+
60
+ ```markdown
61
+ Before implementing any Figma-based UI, read the matching file in `design/specs/`.
62
+ Treat `spec.json` as the contract. Prefer `tokenMatch` over raw hex. Do not invent copy,
63
+ colors, buttons, or progress states that are not listed in the spec.
64
+ ```
65
+
66
+ ### 4. Give the agent a narrow implementation prompt
67
+
68
+ Use this prompt when asking a coding agent to build a screen:
69
+
70
+ ```markdown
71
+ Implement the `<ScreenName>` screen from `design/specs/<screen-slug>/`.
72
+
73
+ Required:
74
+ - Read `spec.json`, `prompt.md`, and `checklist.md` first.
75
+ - Use existing app components and styling conventions.
76
+ - Prefer `tokenMatch` variables over raw hex values.
77
+ - Preserve exact text copy, typography, button labels, and progress bar values from the spec.
78
+ - Run the relevant tests or type checks when done.
79
+ ```
80
+
81
+ ## Review checklist
82
+
83
+ Use this when reviewing a PR generated from specs:
84
+
85
+ - The PR includes the generated spec files or references the existing committed specs.
86
+ - The implementation uses project tokens where `tokenMatch` exists.
87
+ - Text copy and button labels match `spec.json`.
88
+ - The layout is responsive; `box` coordinates were used as guidance, not blind absolute positioning, unless the app intentionally uses absolute layout.
89
+ - `checklist.md` was completed for each screen.
90
+ - No Figma token, `.env`, or cache file was committed.
91
+
92
+ ## Common mistakes
93
+
94
+ | Mistake | Fix |
95
+ |---|---|
96
+ | Asking the agent to implement from a screenshot | Extract specs first and point the agent to `spec.json` |
97
+ | Committing only screenshots | Commit `design/specs/` so future design changes can be reviewed as diffs |
98
+ | Ignoring `tokenMatch` | Pass the app SCSS variables with `--tokens` and prefer matched variables |
99
+ | Extracting stale design data | Re-run with `--refresh` after Figma changes |
100
+ | No frames matched | Run `--list`, copy the exact frame name or use `--ids` |
101
+ | Wrong section node | Use the Figma URL `node-id`, replacing `-` with `:` |
102
+ | Treating specs as runtime assets | Keep specs in dev-only folders; they should not ship in the app bundle |
103
+
104
+ ## Figma handoff conventions
105
+
106
+ Ask designers to keep these conventions when possible:
107
+
108
+ - Name top-level frames clearly, for example `Login`, `Register`, `Checkout`.
109
+ - Use component instances named `Button`, `Botão`, or equivalent for CTAs when possible.
110
+ - Keep progress bars as two thin rounded rectangles aligned on the same Y axis.
111
+ - Keep screen variants as separate top-level frames when they need separate implementation.
112
+
113
+ ## Commands to memorize
114
+
115
+ ```bash
116
+ # list frames
117
+ figma-dev-bypass --list --file-key <KEY> --node <ID>
118
+
119
+ # extract agent pack
120
+ figma-dev-bypass --extract --file-key <KEY> --node <ID> \
121
+ --frames "Login,Register" \
122
+ --tokens path/to/variables.scss \
123
+ --out path/to/app/design/specs \
124
+ --agent-pack --target quasar
125
+
126
+ # refresh after design changes
127
+ figma-dev-bypass --extract --refresh --all --agent-pack
128
+ ```
@@ -0,0 +1,20 @@
1
+ # Cursor rule example (paste into `.cursor/rules/` or project rules)
2
+
3
+ ```markdown
4
+ ---
5
+ description: Read Figma design specs before implementing UI
6
+ globs: src/pages/**, src/components/**, src/views/**
7
+ ---
8
+
9
+ Before implementing any Figma-based UI:
10
+
11
+ 1. Check `design/specs/` for a matching screen spec (JSON or agent pack folder).
12
+ 2. Read `spec.json`, `prompt.md`, and `checklist.md` when an agent pack exists.
13
+ 3. Treat `spec.json` as the design contract (colors, copy, buttons, progress).
14
+ 4. Prefer `tokenMatch` SCSS variables over raw hex values.
15
+ 5. Do not invent UI elements not listed in the spec.
16
+ 6. Use `box` coordinates as relative layout guidance, not mandatory absolute positioning.
17
+ 7. If no spec exists, ask the user to run figma-dev-bypass extract first.
18
+ ```
19
+
20
+ Adjust the `globs` and `design/specs/` path to match your project layout.
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "Login",
3
+ "nodeId": "1016:1275",
4
+ "size": { "width": 440, "height": 956 },
5
+ "background": { "hex": "#410E47" },
6
+ "texts": [
7
+ {
8
+ "text": "Entrar",
9
+ "fontFamily": "Open Sauce Sans",
10
+ "fontSize": 24,
11
+ "fontWeight": 600,
12
+ "color": { "hex": "#FFFFFF" }
13
+ }
14
+ ],
15
+ "buttons": [
16
+ {
17
+ "name": "Botão Branco - Secundário",
18
+ "label": "Continuar com o telefone",
19
+ "fill": { "hex": "#FFFFFF" },
20
+ "radius": 68,
21
+ "box": { "x": 45, "y": 714, "width": 350, "height": 56 }
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,10 @@
1
+ // Example design tokens for tokenMatch in exported specs.
2
+ // Point the CLI with: --tokens ./examples/tokens.example.scss
3
+
4
+ $dm-bg: #121212;
5
+ $dm-card: #1E1E1E;
6
+ $dm-text-primary: #FFFFFF;
7
+ $dm-text-subtitle: #A0A0A0;
8
+ $dm-open: #C184FF;
9
+ $dm-brand-purple: #410E47;
10
+ $dm-brand-lime: #D0FF59;
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "figma-dev-bypass",
3
+ "version": "0.1.0",
4
+ "description": "Agent-ready Figma spec extractor via REST API — no Dev Mode required",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "figma-dev-bypass": "src/cli.mjs"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "docs",
13
+ "examples",
14
+ "schema",
15
+ "skills",
16
+ "AGENTS.md",
17
+ "LICENSE",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "list": "node --env-file=.env src/cli.mjs --list",
22
+ "extract": "node --env-file=.env src/cli.mjs --extract",
23
+ "help": "node src/cli.mjs --help",
24
+ "test": "node --test test/*.mjs"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "keywords": [
30
+ "figma",
31
+ "figma-api",
32
+ "design-tokens",
33
+ "design-to-code",
34
+ "agent-ready",
35
+ "cli"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/guilhermemouraovc/figma-dev-bypass.git"
40
+ }
41
+ }
@@ -0,0 +1,95 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/guilhermemouraovc/figma-dev-bypass/schema/spec.schema.json",
4
+ "title": "Figma Screen Spec",
5
+ "description": "Structured design contract extracted from a Figma frame by figma-dev-bypass",
6
+ "type": "object",
7
+ "required": ["name", "nodeId", "size", "texts"],
8
+ "properties": {
9
+ "name": { "type": "string", "description": "Frame name in Figma" },
10
+ "nodeId": { "type": "string", "description": "Figma node id, e.g. 1016:1275" },
11
+ "size": {
12
+ "type": "object",
13
+ "required": ["width", "height"],
14
+ "properties": {
15
+ "width": { "type": "number" },
16
+ "height": { "type": "number" }
17
+ }
18
+ },
19
+ "background": { "$ref": "#/definitions/color" },
20
+ "texts": {
21
+ "type": "array",
22
+ "items": { "$ref": "#/definitions/text" }
23
+ },
24
+ "buttons": {
25
+ "type": "array",
26
+ "items": { "$ref": "#/definitions/button" }
27
+ },
28
+ "bars": {
29
+ "type": "array",
30
+ "items": { "$ref": "#/definitions/bar" }
31
+ },
32
+ "progress": { "$ref": "#/definitions/progress" }
33
+ },
34
+ "definitions": {
35
+ "color": {
36
+ "type": "object",
37
+ "required": ["hex"],
38
+ "properties": {
39
+ "hex": { "type": "string", "pattern": "^#[0-9A-F]{6}$" },
40
+ "tokenMatch": { "type": "string", "description": "Suggested SCSS variable, e.g. $primary" }
41
+ }
42
+ },
43
+ "box": {
44
+ "type": "object",
45
+ "required": ["x", "y", "width", "height"],
46
+ "properties": {
47
+ "x": { "type": "integer" },
48
+ "y": { "type": "integer" },
49
+ "width": { "type": "integer" },
50
+ "height": { "type": "integer" }
51
+ }
52
+ },
53
+ "text": {
54
+ "type": "object",
55
+ "required": ["text"],
56
+ "properties": {
57
+ "text": { "type": "string" },
58
+ "fontFamily": { "type": "string" },
59
+ "fontSize": { "type": "number" },
60
+ "fontWeight": { "type": "number" },
61
+ "lineHeight": { "type": "number" },
62
+ "color": { "$ref": "#/definitions/color" },
63
+ "box": { "$ref": "#/definitions/box" }
64
+ }
65
+ },
66
+ "button": {
67
+ "type": "object",
68
+ "properties": {
69
+ "name": { "type": "string" },
70
+ "label": { "type": ["string", "null"] },
71
+ "fill": { "$ref": "#/definitions/color" },
72
+ "radius": { "type": ["number", "null"] },
73
+ "box": { "$ref": "#/definitions/box" }
74
+ }
75
+ },
76
+ "bar": {
77
+ "type": "object",
78
+ "properties": {
79
+ "name": { "type": "string" },
80
+ "color": { "$ref": "#/definitions/color" },
81
+ "radius": { "type": "number" },
82
+ "box": { "$ref": "#/definitions/box" }
83
+ }
84
+ },
85
+ "progress": {
86
+ "type": "object",
87
+ "properties": {
88
+ "track": { "$ref": "#/definitions/color" },
89
+ "fill": { "$ref": "#/definitions/color" },
90
+ "radius": { "type": "number" },
91
+ "height": { "type": "integer" }
92
+ }
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: figma-dev-bypass
3
+ description: Extract and implement Figma screen specs via figma-dev-bypass CLI. Use when the user asks to implement a Figma screen, extract Figma specs, design-to-code from Figma, or mentions figma-dev-bypass.
4
+ ---
5
+
6
+ # figma-dev-bypass
7
+
8
+ Extract structured JSON specs from Figma frames and implement UI from the spec contract.
9
+
10
+ ## When to use
11
+
12
+ - User wants to implement a Figma screen in code
13
+ - User mentions figma-dev-bypass or design specs
14
+ - User provides a Figma file key / node id / section name
15
+
16
+ ## Prerequisites
17
+
18
+ - Node.js 18+
19
+ - `FIGMA_TOKEN` with `file_content:read` scope
20
+ - Access to the target Figma file
21
+
22
+ ## Extract workflow
23
+
24
+ 1. List available frames:
25
+
26
+ ```bash
27
+ figma-dev-bypass --list --file-key <KEY> --node <ID>
28
+ ```
29
+
30
+ 2. Extract with agent pack and token matching:
31
+
32
+ ```bash
33
+ figma-dev-bypass --extract --file-key <KEY> --node <ID> \
34
+ --frames "<screen names>" \
35
+ --tokens path/to/variables.scss \
36
+ --agent-pack --target <framework>
37
+ ```
38
+
39
+ Valid `--target` values: `generic`, `react`, `vue`, `quasar`, `react-native`, `swiftui`
40
+
41
+ 3. Read output from `output/specs/<slug>/`:
42
+ - `spec.json` — the design contract
43
+ - `prompt.md` — implementation instructions
44
+ - `checklist.md` — verification checklist
45
+
46
+ If the CLI is being run from a local clone before npm publish, use `node --env-file=.env src/cli.mjs` or the repo's `npm run list` / `npm run extract` scripts.
47
+
48
+ ## Implementation rules
49
+
50
+ 1. **Read the spec first** — never guess colors or copy from screenshots
51
+ 2. **Prefer `tokenMatch`** over raw hex when mapping to project design tokens
52
+ 3. **Use project patterns** — the spec is the contract, not the framework
53
+ 4. **Do not invent UI** — only implement elements listed in the spec
54
+ 5. **Verify** against `checklist.md` before marking done
55
+ 6. **Use `box` coordinates as layout guidance** — avoid blind absolute positioning unless the target project uses it
56
+
57
+ ## Consumer app policy
58
+
59
+ Add this to the app repo's `AGENTS.md`, `CLAUDE.md`, or Cursor rules:
60
+
61
+ ```markdown
62
+ Before implementing any Figma-based UI, read the matching file in `design/specs/`.
63
+ Treat `spec.json` as the contract. Prefer `tokenMatch` over raw hex. Do not invent copy,
64
+ colors, buttons, or progress states that are not listed in the spec.
65
+ ```
66
+
67
+ ## Spec field reference
68
+
69
+ | Field | Meaning |
70
+ |---|---|
71
+ | `background` | Frame fill color |
72
+ | `texts[]` | Copy, typography, position |
73
+ | `buttons[]` | CTAs with label, fill, radius |
74
+ | `progress` | Progress bar track + fill |
75
+ | `tokenMatch` | SCSS variable suggestion |
76
+
77
+ Full schema: `schema/spec.schema.json` in the figma-dev-bypass repo.
78
+
79
+ ## Install in consumer project
80
+
81
+ Copy this skill to the consumer project's agent skills folder:
82
+
83
+ - Cursor: `.cursor/skills/figma-dev-bypass/SKILL.md`
84
+ - Claude Code: `.claude/skills/figma-dev-bypass/SKILL.md`
85
+
86
+ Add a Cursor rule from `examples/cursor-rule.md` pointing agents at `design/specs/` in the app repo.
87
+
88
+ For team rollout, see `docs/team-playbook.md` in the figma-dev-bypass repo.
package/src/cli.mjs ADDED
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env node
2
+ import { mkdirSync, existsSync, readFileSync, writeFileSync } from 'node:fs'
3
+ import { join, dirname } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { loadTokens, parseFrame, slugify } from './parse.mjs'
6
+ import { targets, targetNames } from './targets.mjs'
7
+
8
+ const root = join(dirname(fileURLToPath(import.meta.url)), '..')
9
+ const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
10
+ const defaultCache = join(root, 'output/cache')
11
+ const defaultSpecs = join(root, 'output/specs')
12
+
13
+ const args = process.argv.slice(2)
14
+ const flag = name => args.includes(`--${name}`)
15
+ const opt = name => {
16
+ const i = args.indexOf(`--${name}`)
17
+ return i >= 0 ? args[i + 1] : null
18
+ }
19
+
20
+ function printHelp () {
21
+ console.log(`figma-dev-bypass — extract agent-ready specs from Figma via REST API
22
+
23
+ Usage:
24
+ figma-dev-bypass --list --file-key KEY --node ID [options]
25
+ figma-dev-bypass --extract --file-key KEY --node ID (--all | --frames "a,b" | --ids x,y) [options]
26
+
27
+ Options:
28
+ --file-key KEY Figma file key (or env FIGMA_FILE_KEY)
29
+ --node ID Section/frame node id, e.g. 1021:16710 (or env FIGMA_NODE)
30
+ --out DIR Output specs directory (default: output/specs)
31
+ --cache DIR API cache directory (default: output/cache)
32
+ --tokens PATH Optional SCSS variables file for tokenMatch (e.g. quasar.variables.scss)
33
+ --refresh Re-download from Figma API (ignore cache)
34
+ --images Export PNG screenshots to <out>/img/
35
+ --agent-pack Also write prompt.md + checklist.md per screen
36
+ --target NAME Framework hints for agent-pack (${targetNames.join(', ')})
37
+ --version Show version
38
+ --help Show this help
39
+
40
+ Environment:
41
+ FIGMA_TOKEN Personal access token (required, scope: file_content:read)
42
+ FIGMA_FILE_KEY Default file key
43
+ FIGMA_NODE Default node id
44
+
45
+ Examples:
46
+ figma-dev-bypass --list --file-key RXv1deGII4NMLUjk8yPm3w --node 1021:16710
47
+ figma-dev-bypass --extract --file-key RXv1deGII4NMLUjk8yPm3w --node 1021:16710 --frames "Login,Register"
48
+ figma-dev-bypass --extract --all --tokens ./examples/tokens.example.scss --agent-pack --target vue
49
+ `)
50
+ }
51
+
52
+ function readCacheJson (filePath) {
53
+ try {
54
+ return JSON.parse(readFileSync(filePath, 'utf8'))
55
+ } catch {
56
+ return null
57
+ }
58
+ }
59
+
60
+ async function api (token, path) {
61
+ const res = await fetch(`https://api.figma.com${path}`, { headers: { 'X-Figma-Token': token } })
62
+ if (!res.ok) {
63
+ const body = await res.text().catch(() => '')
64
+ const hints = {
65
+ 403: ' — check that FIGMA_TOKEN is valid and has file_content:read scope',
66
+ 404: ' — check the file key and that your token has access to this file',
67
+ 429: ' — rate limited, wait a minute and retry'
68
+ }
69
+ const hint = hints[res.status] || ''
70
+ throw new Error(`Figma API ${res.status}: ${path}${hint}${body ? ` — ${body.slice(0, 200)}` : ''}`)
71
+ }
72
+ return res.json()
73
+ }
74
+
75
+ function findNode (node, id) {
76
+ if (node.id === id) return node
77
+ for (const c of node.children || []) {
78
+ const hit = findNode(c, id)
79
+ if (hit) return hit
80
+ }
81
+ return null
82
+ }
83
+
84
+ async function getSection ({ token, fileKey, nodeId, cacheDir, refresh }) {
85
+ mkdirSync(cacheDir, { recursive: true })
86
+ const cacheFile = join(cacheDir, `${fileKey}-${nodeId.replace(':', '-')}.json`)
87
+
88
+ if (!refresh && existsSync(cacheFile)) {
89
+ const cached = readCacheJson(cacheFile)
90
+ if (cached) return cached
91
+ }
92
+
93
+ if (!refresh) {
94
+ const { readdirSync } = await import('node:fs')
95
+ for (const f of readdirSync(cacheDir)) {
96
+ if (!f.endsWith('.json') || !f.startsWith(fileKey)) continue
97
+ const data = readCacheJson(join(cacheDir, f))
98
+ if (!data) continue
99
+ const hit = findNode(data, nodeId)
100
+ if (hit) return hit
101
+ }
102
+ }
103
+
104
+ console.log(`Fetching node ${nodeId} from file ${fileKey}...`)
105
+ const data = await api(token, `/v1/files/${fileKey}/nodes?ids=${encodeURIComponent(nodeId)}`)
106
+ const node = data.nodes[nodeId]?.document
107
+ if (!node) throw new Error(`Node ${nodeId} not found in file ${fileKey}`)
108
+ writeFileSync(cacheFile, JSON.stringify(node))
109
+ return node
110
+ }
111
+
112
+ function topFrames (section) {
113
+ return (section.children || []).filter(n => n.type === 'FRAME')
114
+ }
115
+
116
+ function writeAgentPack (dir, spec, targetKey) {
117
+ const target = targets[targetKey] || targets.generic
118
+
119
+ const prompt = `# Implement: ${spec.name}
120
+
121
+ Read the attached spec (\`spec.json\`) and implement this screen in the target project.
122
+
123
+ ## Screen
124
+ - **Name:** ${spec.name}
125
+ - **Node ID:** ${spec.nodeId}
126
+ - **Size:** ${spec.size?.width}×${spec.size?.height}
127
+
128
+ ## Background
129
+ ${spec.background ? `- ${spec.background.hex}${spec.background.tokenMatch ? ` (${spec.background.tokenMatch})` : ''}` : '- (none detected)'}
130
+
131
+ ## Texts
132
+ ${(spec.texts || []).map(t => `- "${t.text}" — ${t.fontSize}px / weight ${t.fontWeight}${t.color?.tokenMatch ? ` — ${t.color.tokenMatch}` : t.color?.hex ? ` — ${t.color.hex}` : ''}`).join('\n') || '- (none)'}
133
+
134
+ ## Buttons
135
+ ${(spec.buttons || []).map(b => `- "${b.label || b.name}" — radius ${b.radius ?? '?'}${b.fill?.tokenMatch ? ` — ${b.fill.tokenMatch}` : b.fill?.hex ? ` — ${b.fill.hex}` : ''}`).join('\n') || '- (none)'}
136
+
137
+ ## Target framework: ${target.label}
138
+ ${target.guidance.map(g => `- ${g}`).join('\n')}
139
+ ${target.skeleton ? `\n## Component skeleton\n\`\`\`\n${target.skeleton}\n\`\`\`\n` : ''}
140
+ ## Rules
141
+ 1. Match colors and typography from the spec; prefer tokenMatch variables when present.
142
+ 2. Do not invent UI elements not listed in the spec.
143
+ 3. Use the project's existing component patterns (buttons, headers, inputs).
144
+ `
145
+
146
+ const checklist = `# Checklist: ${spec.name}
147
+
148
+ - [ ] Background color matches spec
149
+ - [ ] All text copy matches spec
150
+ - [ ] Typography (size, weight, line-height) matches spec
151
+ - [ ] Buttons: shape, colors, labels match spec
152
+ ${spec.progress ? '- [ ] Progress bar: track, fill, radius match spec' : ''}
153
+ - [ ] Dark/light mode handled per project conventions
154
+ `
155
+
156
+ writeFileSync(join(dir, 'prompt.md'), prompt)
157
+ writeFileSync(join(dir, 'checklist.md'), checklist)
158
+ writeFileSync(join(dir, 'spec.json'), JSON.stringify(spec, null, 2) + '\n')
159
+ }
160
+
161
+ async function main () {
162
+ if (flag('version')) {
163
+ console.log(pkg.version)
164
+ return
165
+ }
166
+
167
+ if (flag('help') || !args.length) {
168
+ printHelp()
169
+ process.exit(args.length ? 0 : 1)
170
+ }
171
+
172
+ const token = process.env.FIGMA_TOKEN
173
+ if (!token) {
174
+ console.error('FIGMA_TOKEN is missing. Copy .env.example to .env and add your token.')
175
+ process.exit(1)
176
+ }
177
+
178
+ const fileKey = opt('file-key') || process.env.FIGMA_FILE_KEY
179
+ const nodeId = opt('node') || process.env.FIGMA_NODE
180
+ if (!fileKey || !nodeId) {
181
+ console.error('--file-key and --node are required (or set FIGMA_FILE_KEY / FIGMA_NODE in .env)')
182
+ process.exit(1)
183
+ }
184
+
185
+ const targetKey = opt('target') || 'generic'
186
+ if (!targets[targetKey]) {
187
+ console.error(`Invalid --target "${targetKey}". Valid values: ${targetNames.join(', ')}`)
188
+ process.exit(1)
189
+ }
190
+
191
+ const cacheDir = opt('cache') || defaultCache
192
+ const specsDir = opt('out') || defaultSpecs
193
+ const tokensPath = opt('tokens') || null
194
+
195
+ const section = await getSection({ token, fileKey, nodeId, cacheDir, refresh: flag('refresh') })
196
+ const frames = topFrames(section)
197
+
198
+ if (flag('list')) {
199
+ for (const f of frames) {
200
+ const b = f.absoluteBoundingBox || {}
201
+ console.log(`${f.id.padEnd(14)} ${f.name} ${Math.round(b.width)}x${Math.round(b.height)}`)
202
+ }
203
+ return
204
+ }
205
+
206
+ if (!flag('extract')) {
207
+ console.error('Use --list or --extract. Run with --help for usage.')
208
+ process.exit(1)
209
+ }
210
+
211
+ let selected
212
+ if (flag('all')) {
213
+ selected = frames
214
+ } else if (opt('ids')) {
215
+ const ids = opt('ids').split(',').map(s => s.trim())
216
+ selected = frames.filter(f => ids.includes(f.id))
217
+ } else if (opt('frames')) {
218
+ const names = opt('frames').split(',').map(s => s.trim().toLowerCase())
219
+ selected = frames.filter(f => names.some(n => f.name.toLowerCase().includes(n)))
220
+ } else {
221
+ console.error('Pass --frames "name1,name2", --ids id1,id2, or --all')
222
+ process.exit(1)
223
+ }
224
+
225
+ if (!selected.length) {
226
+ console.error('No frames matched. Run --list to see available frames.')
227
+ process.exit(1)
228
+ }
229
+
230
+ mkdirSync(specsDir, { recursive: true })
231
+ const tokens = loadTokens(tokensPath)
232
+ const used = new Set()
233
+ const frameSlugs = new Map()
234
+
235
+ for (const frame of selected) {
236
+ const spec = parseFrame(frame, tokens)
237
+ let slug = slugify(frame.name)
238
+ if (used.has(slug)) slug += `-${frame.id.replace(':', '-')}`
239
+ used.add(slug)
240
+ frameSlugs.set(frame.id, slug)
241
+
242
+ if (flag('agent-pack')) {
243
+ const packDir = join(specsDir, slug)
244
+ mkdirSync(packDir, { recursive: true })
245
+ writeAgentPack(packDir, spec, targetKey)
246
+ console.log(`✓ ${frame.name} → ${slug}/ (agent pack)`)
247
+ } else {
248
+ writeFileSync(join(specsDir, `${slug}.json`), JSON.stringify(spec, null, 2) + '\n')
249
+ console.log(`✓ ${frame.name} → ${slug}.json`)
250
+ }
251
+ }
252
+
253
+ if (flag('images')) {
254
+ const imgDir = join(specsDir, 'img')
255
+ mkdirSync(imgDir, { recursive: true })
256
+ const ids = selected.map(f => f.id).join(',')
257
+ const { images } = await api(token, `/v1/images/${fileKey}?ids=${encodeURIComponent(ids)}&format=png&scale=2`)
258
+ for (const frame of selected) {
259
+ const url = images[frame.id]
260
+ if (!url) { console.warn(`No image for ${frame.name}`); continue }
261
+ const buf = Buffer.from(await (await fetch(url)).arrayBuffer())
262
+ const slug = frameSlugs.get(frame.id)
263
+ writeFileSync(join(imgDir, `${slug}.png`), buf)
264
+ console.log(`✓ img ${frame.name}`)
265
+ }
266
+ }
267
+ }
268
+
269
+ main().catch(e => { console.error(e.message); process.exit(1) })
package/src/parse.mjs ADDED
@@ -0,0 +1,132 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+
3
+ /**
4
+ * Load hex → token map from an optional SCSS variables file.
5
+ * Matches lines like `$dm-bg: #121212;`
6
+ */
7
+ export function loadTokens (scssPath) {
8
+ const map = {}
9
+ if (!scssPath || !existsSync(scssPath)) return map
10
+
11
+ const src = readFileSync(scssPath, 'utf8')
12
+ for (const [, name, hex] of src.matchAll(/(\$[\w-]+)\s*:\s*(#[0-9a-fA-F]{3,8})\b/g)) {
13
+ const h = normalizeHex(hex)
14
+ if (!(h in map)) map[h] = name
15
+ }
16
+ return map
17
+ }
18
+
19
+ function normalizeHex (hex) {
20
+ let h = hex.slice(1).toUpperCase()
21
+ if (h.length === 3) h = h.split('').map(c => c + c).join('')
22
+ return '#' + h
23
+ }
24
+
25
+ function toHex (color) {
26
+ const c = v => Math.round(v * 255).toString(16).padStart(2, '0').toUpperCase()
27
+ return `#${c(color.r)}${c(color.g)}${c(color.b)}`
28
+ }
29
+
30
+ function solidFill (node) {
31
+ const f = (node.fills || []).find(f => f.type === 'SOLID' && f.visible !== false)
32
+ return f ? toHex(f.color) : null
33
+ }
34
+
35
+ function withToken (hex, tokens) {
36
+ if (!hex) return null
37
+ const out = { hex }
38
+ if (tokens[hex]) out.tokenMatch = tokens[hex]
39
+ return out
40
+ }
41
+
42
+ function walk (node, fn) {
43
+ fn(node)
44
+ for (const child of node.children || []) walk(child, fn)
45
+ }
46
+
47
+ function firstText (node) {
48
+ let found = null
49
+ walk(node, n => { if (!found && n.type === 'TEXT') found = n })
50
+ return found
51
+ }
52
+
53
+ export function parseFrame (frame, tokens) {
54
+ const spec = {
55
+ name: frame.name,
56
+ nodeId: frame.id,
57
+ size: { width: frame.absoluteBoundingBox?.width, height: frame.absoluteBoundingBox?.height },
58
+ background: withToken(solidFill(frame), tokens),
59
+ texts: [],
60
+ buttons: [],
61
+ bars: []
62
+ }
63
+ const origin = frame.absoluteBoundingBox || { x: 0, y: 0 }
64
+ const box = n => n.absoluteBoundingBox && {
65
+ x: Math.round(n.absoluteBoundingBox.x - origin.x),
66
+ y: Math.round(n.absoluteBoundingBox.y - origin.y),
67
+ width: Math.round(n.absoluteBoundingBox.width),
68
+ height: Math.round(n.absoluteBoundingBox.height)
69
+ }
70
+ const claimed = new Set()
71
+
72
+ walk(frame, node => {
73
+ if (node === frame || node.visible === false) return
74
+
75
+ const isNamedButton = node.type === 'INSTANCE' && /bot[aã]o|button/i.test(node.name)
76
+ const isDrawnButton = node.type === 'RECTANGLE' && (node.cornerRadius ?? 0) >= 16 &&
77
+ node.absoluteBoundingBox?.height >= 30 && node.absoluteBoundingBox?.height <= 80
78
+ if (isNamedButton || isDrawnButton) {
79
+ const label = isNamedButton ? firstText(node) : null
80
+ if (label) claimed.add(label.id)
81
+ spec.buttons.push({
82
+ name: node.name,
83
+ label: label?.characters ?? null,
84
+ fill: withToken(solidFill(node) || deepFill(node), tokens),
85
+ radius: node.cornerRadius ?? null,
86
+ box: box(node)
87
+ })
88
+ return
89
+ }
90
+
91
+ if (node.type === 'RECTANGLE' && node.absoluteBoundingBox?.height < 8 && (node.cornerRadius ?? 0) > 0) {
92
+ spec.bars.push({ name: node.name, color: withToken(solidFill(node), tokens), radius: node.cornerRadius, box: box(node) })
93
+ }
94
+ })
95
+
96
+ walk(frame, node => {
97
+ if (node.type !== 'TEXT' || node.visible === false || claimed.has(node.id)) return
98
+ const s = node.style || {}
99
+ spec.texts.push({
100
+ text: node.characters,
101
+ fontFamily: s.fontFamily,
102
+ fontSize: s.fontSize,
103
+ fontWeight: s.fontWeight,
104
+ lineHeight: s.lineHeightPx ? Math.round(s.lineHeightPx * 100) / 100 : undefined,
105
+ color: withToken(solidFill(node), tokens),
106
+ box: box(node)
107
+ })
108
+ })
109
+
110
+ if (spec.bars.length === 2 && Math.abs(spec.bars[0].box.y - spec.bars[1].box.y) <= 2) {
111
+ const [a, b] = spec.bars
112
+ const [track, fill] = a.box.width >= b.box.width ? [a, b] : [b, a]
113
+ spec.progress = { track: track.color, fill: fill.color, radius: track.radius, height: track.box.height }
114
+ delete spec.bars
115
+ } else if (!spec.bars.length) {
116
+ delete spec.bars
117
+ }
118
+ if (!spec.buttons.length) delete spec.buttons
119
+
120
+ return spec
121
+ }
122
+
123
+ function deepFill (node) {
124
+ let hex = null
125
+ walk(node, n => { if (!hex && n.type !== 'TEXT') hex = solidFill(n) })
126
+ return hex
127
+ }
128
+
129
+ export function slugify (name) {
130
+ return name.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
131
+ .toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
132
+ }
@@ -0,0 +1,135 @@
1
+ export const targets = {
2
+ generic: {
3
+ label: 'Generic (framework-agnostic)',
4
+ guidance: [
5
+ 'Use the target project\'s existing component library and styling conventions.',
6
+ 'Prefer design tokens (tokenMatch) over raw hex values when present.',
7
+ 'Build a responsive layout; do not rely on absolute positioning from box coordinates unless the project already uses that pattern.'
8
+ ]
9
+ },
10
+ react: {
11
+ label: 'React + CSS Modules',
12
+ guidance: [
13
+ 'Create a functional component with a named export matching the screen name.',
14
+ 'Use the project\'s existing styling approach (CSS Modules, Tailwind, or styled-components).',
15
+ 'Map buttons to the project\'s Button component or a styled <button>.',
16
+ 'Use CSS custom properties or theme tokens for colors when tokenMatch is present.',
17
+ 'For progress bars, use a div-based track/fill or the project\'s existing Progress component.'
18
+ ],
19
+ skeleton: `// ScreenName.tsx
20
+ export function ScreenName() {
21
+ return (
22
+ <div className="screen">
23
+ {/* background: use tokenMatch or hex from spec.background */}
24
+ <h1>{/* text from spec.texts[0] */}</h1>
25
+ <button>{/* label from spec.buttons[0] */}</button>
26
+ </div>
27
+ )
28
+ }`
29
+ },
30
+ vue: {
31
+ label: 'Vue 3 (Composition API)',
32
+ guidance: [
33
+ 'Create a single-file component (.vue) using <script setup>.',
34
+ 'Use scoped CSS or the project\'s existing SCSS variables for colors.',
35
+ 'Prefer tokenMatch SCSS variables over raw hex values.',
36
+ 'Map buttons to native <button> or the project\'s existing button component.',
37
+ 'Use flexbox/grid for layout rather than absolute positioning from box coordinates.'
38
+ ],
39
+ skeleton: `<script setup>
40
+ // props/emits as needed by the project
41
+ </script>
42
+
43
+ <template>
44
+ <div class="screen">
45
+ <!-- background: use tokenMatch or hex from spec.background -->
46
+ <h1><!-- text from spec.texts[0] --></h1>
47
+ <button><!-- label from spec.buttons[0] --></button>
48
+ </div>
49
+ </template>
50
+
51
+ <style scoped lang="scss">
52
+ /* use tokenMatch variables from the project */
53
+ </style>`
54
+ },
55
+ quasar: {
56
+ label: 'Quasar Framework (Vue 3)',
57
+ guidance: [
58
+ 'Create a page or component using <script setup> and Quasar components.',
59
+ 'Use q-btn for buttons; match label, color, and border-radius from the spec.',
60
+ 'Use q-linear-progress for progress bars when spec.progress is present.',
61
+ 'Apply colors via SCSS variables from quasar.variables.scss (prefer tokenMatch).',
62
+ 'Use q-page for full-screen layouts and Quasar spacing utilities where appropriate.'
63
+ ],
64
+ skeleton: `<script setup>
65
+ // import composables as needed
66
+ </script>
67
+
68
+ <template>
69
+ <q-page class="screen">
70
+ <!-- :style or class using tokenMatch from spec.background -->
71
+ <div class="text-h5"><!-- spec.texts[0].text --></div>
72
+ <q-btn unelevated rounded><!-- spec.buttons[0].label --></q-btn>
73
+ </q-page>
74
+ </template>
75
+
76
+ <style scoped lang="scss">
77
+ @use 'src/css/quasar.variables' as *;
78
+ </style>`
79
+ },
80
+ 'react-native': {
81
+ label: 'React Native',
82
+ guidance: [
83
+ 'Create a functional screen component using StyleSheet.create for styles.',
84
+ 'Use Pressable or TouchableOpacity for buttons.',
85
+ 'Box coordinates in the spec are px at the design width — scale proportionally to device width.',
86
+ 'Map hex colors to StyleSheet color values or theme constants.',
87
+ 'Use a View-based progress bar (track + fill) when spec.progress is present.'
88
+ ],
89
+ skeleton: `import { View, Text, Pressable, StyleSheet } from 'react-native'
90
+
91
+ export function ScreenName() {
92
+ return (
93
+ <View style={styles.screen}>
94
+ <Text style={styles.title}>{/* spec.texts[0].text */}</Text>
95
+ <Pressable style={styles.button}>
96
+ <Text>{/* spec.buttons[0].label */}</Text>
97
+ </Pressable>
98
+ </View>
99
+ )
100
+ }
101
+
102
+ const styles = StyleSheet.create({
103
+ screen: { flex: 1 /* backgroundColor from spec.background */ },
104
+ title: { /* fontSize, fontWeight from spec.texts[0] */ },
105
+ button: { /* borderRadius, backgroundColor from spec.buttons[0] */ }
106
+ })`
107
+ },
108
+ swiftui: {
109
+ label: 'SwiftUI',
110
+ guidance: [
111
+ 'Create a SwiftUI View struct matching the screen name.',
112
+ 'Use Button for CTAs and Text for copy with font modifiers from the spec.',
113
+ 'Use ProgressView or a custom Capsule-based bar when spec.progress is present.',
114
+ 'Map hex colors to Color(hex:) extensions or asset catalog color sets.',
115
+ 'Prefer VStack/HStack/ZStack over absolute positioning from box coordinates.'
116
+ ],
117
+ skeleton: `import SwiftUI
118
+
119
+ struct ScreenName: View {
120
+ var body: some View {
121
+ ZStack {
122
+ // background: Color from spec.background hex or asset
123
+ VStack {
124
+ Text(/* spec.texts[0].text */)
125
+ .font(.system(size: /* spec.texts[0].fontSize */))
126
+ Button(/* spec.buttons[0].label */) { }
127
+ .buttonStyle(/* match radius and fill */)
128
+ }
129
+ }
130
+ }
131
+ }`
132
+ }
133
+ }
134
+
135
+ export const targetNames = Object.keys(targets)