figura-cli 0.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "figura-cli",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Command-line client for the Figura visualization SaaS",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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, mcp__figura__figura_revise_fig, mcp__figura__figura_publish_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 (optionally published with a shareable
12
- link) to your team's timeline. Build a fig to pressure-test a layout or screen in
13
- seconds, before writing the real code.
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
@@ -74,12 +75,26 @@ greenfield.
74
75
  fetched design system. **Follow that design system verbatim; never invent brand
75
76
  values.**
76
77
 
77
- **Landing IS hosting — leave `publish` unset.** Creating a fig always persists
78
- it to Figura (there is no separate "save it for real" step); the response's
79
- **`viewUrl`** is a link you can open right now, whether or not anyone has
80
- decided to share it wider. Don't pass `publish: true` just to get something
81
- viewable that makes a first-draft iteration PUBLIC. Publishing is a
82
- deliberate, later step (see step 5).
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.
83
98
 
84
99
  3. **Server render — the user's call, never yours.** Figura can also render the
85
100
  fig server-side against the team brand: **`figura_generate_fig`** with a
@@ -121,13 +136,30 @@ greenfield.
121
136
  **`figura_publish_fig`** (or `figura fig publish <id>`) — Figura's
122
137
  `figura.so/f/<id>` share link. Team-only review never needed this; treat
123
138
  publishing as a deliberate call the human makes, not something create/revise
124
- does on its own.
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.
125
156
 
126
157
  ## Presenting a redesign — overview ⇄ focus
127
158
 
128
159
  When a fig argues a change against a current state (a shipped baseline, or
129
160
  several candidate directions), build one fig with **two switchable views** —
130
- CSS-only radio tabs, since figs carry no `<script>`:
161
+ plain CSS-only radio tabs for THIS specific toggle (it's binary, must survive
162
+ truncation, and doesn't need a library):
131
163
 
132
164
  - **Overview — side by side.** Every state at once: the honest current state
133
165
  first, then each variation, as scaled-down frames in a grid. Under each: a
@@ -167,6 +199,45 @@ layout with pixel-level tweaks. Never ship a squeezed side-by-side as the
167
199
  *only* view of desktop frames, and never a full-frame toggle as the only
168
200
  view of many variations — each is one half of the pair above.
169
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
+
170
241
  ## Metadata & geometry — stamped for you
171
242
 
172
243
  Every landed fig is **auto-stamped server-side** (on `create` and `generate`)
@@ -198,7 +269,9 @@ So when you **hand-build** a fig (the default path), do your part of the contrac
198
269
 
199
270
  - **MCP unavailable** (headless / cron, or no interactive auth): fall back to the
200
271
  CLI — `bunx figura-cli fig create` / `fig publish`, with `fig revise` /
201
- `fig revisions` for versions, and `bunx figura-cli brand --json` for the brand.
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.
202
275
  - **`figura_create_fig`/`figura_revise_fig` itself fails** (no network, API down,
203
276
  auth expired): the fig never entered Figura, so there is no `id`/`viewUrl` to
204
277
  open. Only THEN write the HTML to a local scratch file (project-scoped —