figura-cli 0.14.0 → 0.16.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/README.md +8 -1
- package/dist/index.js +139 -111
- package/dist/mcp.js +12 -12
- package/package.json +1 -1
- package/skills/fig/SKILL.md +138 -23
package/package.json
CHANGED
package/skills/fig/SKILL.md
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
name: fig
|
|
3
3
|
description: "Mock up a screen as an on-brand, shareable HTML fig via your Figura team — render a UI state in seconds before writing the real code. Use when asked to visualize a screen, mock a UI, or 'make a fig'. iPhone-framed by default; browser frame for web/desktop."
|
|
4
4
|
argument-hint: '<what to visualize>'
|
|
5
|
-
allowed-tools: 'mcp__figura__figura_whoami, mcp__figura__figura_generate_fig, mcp__figura__figura_create_fig, Bash(figura *), Bash(bunx figura-cli *)'
|
|
5
|
+
allowed-tools: 'mcp__figura__figura_whoami, mcp__figura__figura_generate_fig, mcp__figura__figura_create_fig, mcp__figura__figura_revise_fig, mcp__figura__figura_publish_fig, mcp__figura__figura_commit_fig, Bash(figura *), Bash(bunx figura-cli *)'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# /fig — figure it out before you build it
|
|
9
9
|
|
|
10
10
|
A **fig** is a self-contained HTML mock of one UI state, rendered against your
|
|
11
|
-
Figura team's design system and saved
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
Figura team's design system and saved to Figura as a workspace draft —
|
|
12
|
+
committed to the team library when it's the final pick, and optionally
|
|
13
|
+
published with a shareable link. Build a fig to pressure-test a layout or
|
|
14
|
+
screen in seconds, before writing the real code.
|
|
14
15
|
|
|
15
16
|
This skill is a **thin wrapper over the Figura MCP**. It deliberately carries **no
|
|
16
17
|
brand tokens, colors, fonts, or design rules** — the brand lives in exactly one
|
|
@@ -65,36 +66,100 @@ greenfield.
|
|
|
65
66
|
|
|
66
67
|
2. **Default — build it yourself, with YOUR Claude.** Fetch the team's design
|
|
67
68
|
system — `bunx figura-cli brand --json` — and hand-build the fig's HTML against
|
|
68
|
-
it, then land it with **`figura_create_fig
|
|
69
|
+
it, then land it with **`figura_create_fig`**, setting
|
|
69
70
|
`area`, `orientation` (`portrait` / `landscape` / `both`), `mode` (`single` /
|
|
70
71
|
`compare` / `variations` — default to `compare` when Existing-first turned up a
|
|
71
72
|
baseline), `title`, and a real description. This uses the
|
|
72
73
|
caller's own Claude (the model running this skill) — no extra cost, full control
|
|
73
74
|
for precise broken-vs-proposed diptychs — and applies the team's brand from the
|
|
74
75
|
fetched design system. **Follow that design system verbatim; never invent brand
|
|
75
|
-
values.**
|
|
76
|
+
values.**
|
|
77
|
+
|
|
78
|
+
**Landing IS hosting — leave `publish` and `commit` unset.** Creating a fig
|
|
79
|
+
always persists it to Figura (there is no separate "save it for real" step);
|
|
80
|
+
the response's **`viewUrl`** is a link you can open right now, whether or not
|
|
81
|
+
anyone has decided to share it wider. It lands as a **workspace draft** —
|
|
82
|
+
hosted and shareable by URL, visible to its author, but NOT in the team
|
|
83
|
+
library and silent for teammates — so iterate freely without claiming the
|
|
84
|
+
team's attention. Don't pass `publish: true` just to get something viewable
|
|
85
|
+
(that makes a first-draft iteration PUBLIC), and don't pass `commit: true`
|
|
86
|
+
on an exploration — committing is reserved for the session's final
|
|
87
|
+
recommendation (step 7).
|
|
88
|
+
|
|
89
|
+
**Pass the fig markup INLINE as `html` — never a shell reference.** The `html`
|
|
90
|
+
argument to `figura_create_fig` / `figura_revise_fig` is the *full HTML
|
|
91
|
+
document itself*, passed directly as the value. An MCP tool argument is **not**
|
|
92
|
+
a shell: `$(cat "…/fig.html")`, a backtick `` `cat file` ``, any command
|
|
93
|
+
substitution, or a bare file path will **not** expand — the literal string is
|
|
94
|
+
stored as the fig, producing a tiny black/empty canvas (the engine now rejects
|
|
95
|
+
this with a 400). If you authored the HTML in a scratch file to render/verify
|
|
96
|
+
it (step "Verify before publishing"), **read that file's contents** and pass
|
|
97
|
+
the contents as `html` — do not reference the path.
|
|
76
98
|
|
|
77
99
|
3. **Server render — the user's call, never yours.** Figura can also render the
|
|
78
100
|
fig server-side against the team brand: **`figura_generate_fig`** with a
|
|
79
|
-
natural-language brief (same `area` / `orientation` / `mode` / `title
|
|
80
|
-
`publish
|
|
81
|
-
render ("let Figura render it", headless/CI
|
|
82
|
-
when you've offered both paths and they
|
|
83
|
-
**async job the tool polls for you** — expect
|
|
84
|
-
returns; don't re-call while one is in flight.
|
|
85
|
-
**spends the team's Figura credits** (a `403`
|
|
86
|
-
Pass **`fidelity: 'high'`** to render on the
|
|
87
|
-
`'fast'` is lighter — fine for rough states, weaker
|
|
88
|
-
this path on your own to save effort: it costs credits
|
|
89
|
-
track the brand better.
|
|
90
|
-
|
|
91
|
-
4. **
|
|
101
|
+
natural-language brief (same `area` / `orientation` / `mode` / `title`, and
|
|
102
|
+
again leave `publish` unset by default). Use it only when the **user
|
|
103
|
+
explicitly asks** for a server render ("let Figura render it", headless/CI
|
|
104
|
+
flows with no local model) — or when you've offered both paths and they
|
|
105
|
+
picked this one. It runs as an **async job the tool polls for you** — expect
|
|
106
|
+
~30–90s before the landed fig returns; don't re-call while one is in flight.
|
|
107
|
+
Unlike the default path, this **spends the team's Figura credits** (a `403`
|
|
108
|
+
means no active subscription). Pass **`fidelity: 'high'`** to render on the
|
|
109
|
+
better model (the default `'fast'` is lighter — fine for rough states, weaker
|
|
110
|
+
on brand). Never pick this path on your own to save effort: it costs credits
|
|
111
|
+
and hand-built figs track the brand better.
|
|
112
|
+
|
|
113
|
+
4. **Open the HOSTED fig for approval — never a local file.** Run
|
|
114
|
+
`figura fig open <id>` (the id `figura_create_fig`/`figura_generate_fig` just
|
|
115
|
+
returned). It opens **`viewUrl`** in the reviewer's browser — the actual
|
|
116
|
+
Figura render (brand chrome, CSP, favicon, responsive frame), team-scoped so
|
|
117
|
+
any signed-in teammate can open it even though nothing was published. That's
|
|
118
|
+
what gets approved, and it's the exact page that ships once published — not a
|
|
119
|
+
`file://` reconstruction that can drift from it. No display reachable (a
|
|
120
|
+
headless/CI session)? Paste the printed `viewUrl` into the conversation
|
|
121
|
+
instead, or fall back to `figura fig open <id> --local` for a local sandboxed
|
|
122
|
+
HTML preview.
|
|
123
|
+
|
|
124
|
+
5. **Iterate against the SAME fig, not a fresh one.** Changes from review go
|
|
125
|
+
through **`figura_revise_fig`** (`id`, the updated `html`) — it joins the
|
|
126
|
+
existing chain as a new version and returns a fresh `viewUrl`. Publishing is
|
|
127
|
+
NOT inherited automatically: a revision of an already-published fig still
|
|
128
|
+
lands unpublished by default (`viewUrl` is a team-scoped view, and the
|
|
129
|
+
existing public link keeps serving the OLD version) — pass `publish: true`
|
|
130
|
+
on the revise call itself if the fig is already live and the public link
|
|
131
|
+
needs to move to this version. Re-open it (`figura fig open <id>`) and
|
|
132
|
+
repeat until approved.
|
|
133
|
+
|
|
134
|
+
6. **Approve, then share.** Once the human signs off AND wants a link people
|
|
135
|
+
outside the team can open, publish it explicitly —
|
|
136
|
+
**`figura_publish_fig`** (or `figura fig publish <id>`) — Figura's
|
|
137
|
+
`figura.so/f/<id>` share link. Team-only review never needed this; treat
|
|
138
|
+
publishing as a deliberate call the human makes, not something create/revise
|
|
139
|
+
does on its own. Publishing is the share link ONLY — it does not put the
|
|
140
|
+
fig in the team library (that's committing, step 7); a shared draft's URL
|
|
141
|
+
resolves fine.
|
|
142
|
+
|
|
143
|
+
7. **Commit exactly ONE fig — the session's recommendation.** Every fig this
|
|
144
|
+
session landed is a workspace draft: teammates never saw it. **Committing**
|
|
145
|
+
(`draft → published`) is what puts a fig in the team library and notifies
|
|
146
|
+
the team (digest-batched) — so at the end of the session, commit exactly
|
|
147
|
+
the fig the user asked for / the final agreed version, and nothing else.
|
|
148
|
+
Explorations, rejected variants, and intermediate iterations STAY drafts.
|
|
149
|
+
If the deliverable is the fig you're about to create/revise anyway, prefer
|
|
150
|
+
`commit: true` on that final `figura_create_fig` / `figura_revise_fig`
|
|
151
|
+
call (CLI: `--commit`) over a separate call; otherwise commit it afterwards
|
|
152
|
+
with **`figura_commit_fig`** (CLI: `figura fig commit <id>`). Committed the
|
|
153
|
+
wrong one? `figura_commit_fig` with `uncommit: true` (CLI:
|
|
154
|
+
`figura fig uncommit <id>`) returns it to a draft — the share link is
|
|
155
|
+
untouched either way.
|
|
92
156
|
|
|
93
157
|
## Presenting a redesign — overview ⇄ focus
|
|
94
158
|
|
|
95
159
|
When a fig argues a change against a current state (a shipped baseline, or
|
|
96
160
|
several candidate directions), build one fig with **two switchable views** —
|
|
97
|
-
CSS-only radio tabs
|
|
161
|
+
plain CSS-only radio tabs for THIS specific toggle (it's binary, must survive
|
|
162
|
+
truncation, and doesn't need a library):
|
|
98
163
|
|
|
99
164
|
- **Overview — side by side.** Every state at once: the honest current state
|
|
100
165
|
first, then each variation, as scaled-down frames in a grid. Under each: a
|
|
@@ -124,14 +189,55 @@ CSS-only radio tabs, since figs carry no `<script>`:
|
|
|
124
189
|
**deterministic**, project-scoped path — the repo's `.figura/figs/` (already
|
|
125
190
|
gitignored), or `~/.figura/figs/` outside a bound repo — never `/tmp` or
|
|
126
191
|
`mktemp`. A stable path means a rerun overwrites the same file instead of
|
|
127
|
-
littering volatile temp locations, and matches where `figura fig open`
|
|
128
|
-
(`<repo>/.figura/figs/figura-fig-<id>.html`)
|
|
192
|
+
littering volatile temp locations, and matches where `figura fig open --local`
|
|
193
|
+
writes (`<repo>/.figura/figs/figura-fig-<id>.html`) — this is YOUR headless
|
|
194
|
+
self-check copy, separate from the hosted `viewUrl` a human reviewer opens
|
|
195
|
+
(step 4 above).
|
|
129
196
|
|
|
130
197
|
A **wipe-slider** stays the narrow tool for exactly two states of the same
|
|
131
198
|
layout with pixel-level tweaks. Never ship a squeezed side-by-side as the
|
|
132
199
|
*only* view of desktop frames, and never a full-frame toggle as the only
|
|
133
200
|
view of many variations — each is one half of the pair above.
|
|
134
201
|
|
|
202
|
+
## JavaScript is ALLOWED — use it for high-fidelity animation
|
|
203
|
+
|
|
204
|
+
Figs are **not** CSS-only. The served fig's CSP (`script-src 'unsafe-inline'
|
|
205
|
+
https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com`) runs
|
|
206
|
+
inline `<script>` **and** external libraries loaded from those three CDNs — so
|
|
207
|
+
reach for real JS whenever it raises fidelity, especially for a mobile-app fig
|
|
208
|
+
where the point is to sell a screen's motion (entrance transitions, spring/
|
|
209
|
+
easing on interactive controls, scroll-driven or gesture-driven animation),
|
|
210
|
+
not just its static layout. Don't self-censor to CSS keyframes when a library
|
|
211
|
+
like GSAP does the job better.
|
|
212
|
+
|
|
213
|
+
Rules, so the script actually runs on the published fig instead of getting
|
|
214
|
+
CSP-blocked or failing validation:
|
|
215
|
+
|
|
216
|
+
- **Only the 3 allowlisted CDNs** — `cdn.jsdelivr.net`, `cdnjs.cloudflare.com`,
|
|
217
|
+
`unpkg.com`. Any other host is CSP-blocked at serve time; the script silently
|
|
218
|
+
never runs.
|
|
219
|
+
- **Pin an exact version** — `gsap@3.13.0`, never `gsap@latest` or an
|
|
220
|
+
unversioned path. The engine's `depsPinned` check fails an unpinned CDN
|
|
221
|
+
script.
|
|
222
|
+
- Inline `<script>` (no `src`) has no CDN restriction — it's your own code, not
|
|
223
|
+
a third-party load.
|
|
224
|
+
- **No network calls** — `connect-src 'none'` blocks fetch/XHR/WebSocket/
|
|
225
|
+
sendBeacon outright; a script that tries to phone home just fails silently.
|
|
226
|
+
Animate/interact with what's already in the DOM.
|
|
227
|
+
- The fig still runs in a sandboxed, opaque-origin iframe (no cookies, no
|
|
228
|
+
parent-document access) — the security tradeoff Figura accepts to allow this
|
|
229
|
+
is documented in `packages/core/src/fig/fig-csp.ts` and `cdn-hosts.ts`.
|
|
230
|
+
|
|
231
|
+
Example — a GSAP-driven entrance for a mobile screen, pinned + allowlisted:
|
|
232
|
+
|
|
233
|
+
```html
|
|
234
|
+
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
|
|
235
|
+
<script>
|
|
236
|
+
gsap.from('.card', { y: 24, opacity: 0, duration: 0.5, stagger: 0.08, ease: 'power2.out' });
|
|
237
|
+
gsap.from('.cta', { scale: 0.9, opacity: 0, duration: 0.4, delay: 0.3, ease: 'back.out(1.7)' });
|
|
238
|
+
</script>
|
|
239
|
+
```
|
|
240
|
+
|
|
135
241
|
## Metadata & geometry — stamped for you
|
|
136
242
|
|
|
137
243
|
Every landed fig is **auto-stamped server-side** (on `create` and `generate`)
|
|
@@ -163,6 +269,15 @@ So when you **hand-build** a fig (the default path), do your part of the contrac
|
|
|
163
269
|
|
|
164
270
|
- **MCP unavailable** (headless / cron, or no interactive auth): fall back to the
|
|
165
271
|
CLI — `bunx figura-cli fig create` / `fig publish`, with `fig revise` /
|
|
166
|
-
`fig revisions` for versions,
|
|
272
|
+
`fig revisions` for versions, `fig commit` / `fig uncommit` (or `--commit` on
|
|
273
|
+
the final create/revise) for the team library, and
|
|
274
|
+
`bunx figura-cli brand --json` for the brand.
|
|
275
|
+
- **`figura_create_fig`/`figura_revise_fig` itself fails** (no network, API down,
|
|
276
|
+
auth expired): the fig never entered Figura, so there is no `id`/`viewUrl` to
|
|
277
|
+
open. Only THEN write the HTML to a local scratch file (project-scoped —
|
|
278
|
+
`.figura/figs/`, never `/tmp`) and tell the user it's a local-only draft that
|
|
279
|
+
still needs landing once connectivity is back — this is the degraded fallback,
|
|
280
|
+
not the default path. Don't reach for it just because publishing felt like
|
|
281
|
+
an extra step; `figura_create_fig` hosts on the first call, every time.
|
|
167
282
|
- **One job, one place.** Brand = the Figura brand profile. Generation = the Figura
|
|
168
283
|
engine. This skill only decides *when* to fig and *which MCP call* to make.
|