figura-cli 0.22.0 → 0.23.1

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.22.0",
3
+ "version": "0.23.1",
4
4
  "description": "Command-line client for the Figura visualization SaaS",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -2,7 +2,7 @@
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_fig_scaffold, mcp__figura__figura_fig_devices, 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 *)'
5
+ allowed-tools: 'mcp__figura__figura_whoami, mcp__figura__figura_fig_scaffold, mcp__figura__figura_fig_devices, mcp__figura__figura_list_figs, mcp__figura__figura_generate_fig, mcp__figura__figura_compose_fig, mcp__figura__figura_create_fig, mcp__figura__figura_revise_fig, mcp__figura__figura_publish_fig, mcp__figura__figura_commit_fig, mcp__figura__figura_request_feedback, Bash(figura *), Bash(bunx figura-cli *)'
6
6
  ---
7
7
 
8
8
  # /fig — figure it out before you build it
@@ -105,28 +105,57 @@ so you hand it the studied structure in the brief (see step 3, "Server render").
105
105
  to the user verbatim (a one-line "figura-mcp is behind / below min — run …"),
106
106
  then proceed. It's a passive nudge, shown once per session — never a blocker.
107
107
 
108
- 2. **Default — build it yourself, with YOUR Claude.** Fetch the **canonical
109
- authoring scaffold** **`figura_fig_scaffold`** (CLI:
110
- `bunx figura-cli fig scaffold --platform <mobile|web> [--device <id>] [--area <str>]`)
111
- and hand-build the fig's HTML against it, then land it with
112
- **`figura_create_fig`**, setting `area`, `orientation` (`portrait` /
113
- `landscape` / `both`), `mode` (`single` / `compare` / `variations` default
114
- to `compare` when Existing-first turned up a baseline), `title`, and a real
115
- description. This uses the caller's own Claude (the model running this skill)
116
- no extra cost, full control for precise broken-vs-proposed diptychs.
117
-
118
- The scaffold IS the source of truth: it's the **exact system prompt Figura
119
- injects when it renders a fig server-side** the design rules, exemplars,
120
- accent hints, your team's composed brand, and the component/device kit
121
- context, all in one. Follow it verbatim; never invent brand values or restate
122
- design rules from memory. Match `--platform` to the target (a web fig's
123
- scaffold composes the team's *web* brand surface, a mobile fig's the *mobile*
124
- one). **Fallback if the scaffold fetch fails** (older engine/CLI, or the
125
- `figura_fig_scaffold` tool is unavailable): fetch just the brand with
126
- `bunx figura-cli brand --json` and author against that the scaffold is the
127
- preferred path, the raw brand is the degraded one.
128
-
129
- **Landing IS hostingleave `publish` and `commit` unset.** Creating a fig
108
+ 2. **Default — write the CONTENT, let Figura own the STRUCTURE.** Build device
109
+ figs with **`figura_compose_fig`**: you author only each **screen's CONTENT**
110
+ (the markup *inside* the phone); Figura owns the parts that used to drift — the
111
+ iPhone **frame** (bezel + Dynamic Island/notch + home bar), the **safe zone**,
112
+ the exact catalog **artboard geometry**, and the **composition** (grid /
113
+ diptych / overview). The frame/safe/device/composition can no longer be wrong:
114
+ they're sourced from the device catalog server-side, not hand-written, so the
115
+ chronic failures (a rolled-your-own artboard, a guessed `--fig-safe-top:59px`,
116
+ a forgotten `data-device`) are impossible by construction.
117
+
118
+ First fetch context the **scaffold** (`figura_fig_scaffold`, CLI:
119
+ `bunx figura-cli fig scaffold --platform <mobile|web> [--area <str>]`) for the
120
+ brand tokens + design rules, and the **device catalog**
121
+ (`figura_fig_devices`) for the valid `device` ids. Then call
122
+ **`figura_compose_fig`** with:
123
+
124
+ - **`layout`** `single` (one frame), `compare` (as-is proposed diptych;
125
+ default this when Existing-first turned up a baseline — pass a `label` per
126
+ screen), `overview-focus` (**the one for arguing a redesign or several
127
+ candidate directions** opens on the full-frame Focus toggle, Overview
128
+ grid behind the second tab), `variations` (a scan-only grid of states at
129
+ ~0.34 scale, NO toggle only for a pure catalogue nobody has to read
130
+ detail in; never for a redesign argument), or `document` (NO device
131
+ framing — brand/overview figs).
132
+ - **`screens`** — an ARRAY. Each screen's **`html` is CONTENT ONLY**: the
133
+ markup that goes *inside* the phone. Do **NOT** draw a frame, bezel, status
134
+ bar, or safe padding; do **NOT** set `--fig-safe-*`; do **NOT** wrap it in
135
+ `.fig-device`. For every device layout each screen also carries the typed
136
+ intent Figura needs — **`device`** (a real catalog id from
137
+ `figura_fig_devices`, e.g. `iphone-16`), **`orientation`**
138
+ (`portrait`/`landscape`), and **`safeZone`** (`respect` = Figura pads your
139
+ content to the device's real safe insets; `ignore` = edge-to-edge for a
140
+ splash/hero). (`document` screens need none of these.)
141
+ - **`sharedCss`** — ONE stylesheet every screen shares: put your brand tokens
142
+ (from the scaffold) and shared content classes here. Per-screen one-offs go
143
+ in that screen's `css`.
144
+ - Plus `area`, `title`, `orientation`, and a real `description`.
145
+
146
+ This uses the caller's own Claude — no extra cost. Follow the scaffold's brand
147
+ values verbatim; never invent brand values or restate design rules from memory.
148
+ **Fallback if the scaffold fetch fails** (older engine/CLI): fetch just the
149
+ brand with `bunx figura-cli brand --json` and author your content styles
150
+ against that.
151
+
152
+ **`figura_create_fig` is the escape hatch** — reach for it only for a
153
+ `document`/exotic fig where you must hand-author the entire HTML (or when
154
+ `compose` is unavailable on an older engine). It takes a full `html` document
155
+ and you own the whole contract yourself. For any device fig, prefer
156
+ `compose` — it can't drift.
157
+
158
+ **Landing IS hosting — leave `publish` and `commit` unset.** Composing a fig
130
159
  always persists it to Figura (there is no separate "save it for real" step);
131
160
  the response's **`viewUrl`** is a link you can open right now, whether or not
132
161
  anyone has decided to share it wider. It lands as a **workspace draft** —
@@ -137,15 +166,14 @@ so you hand it the studied structure in the brief (see step 3, "Server render").
137
166
  on an exploration — committing is reserved for the session's final
138
167
  recommendation (step 7).
139
168
 
140
- **Pass the fig markup INLINE as `html` — never a shell reference.** The `html`
141
- argument to `figura_create_fig` / `figura_revise_fig` is the *full HTML
142
- document itself*, passed directly as the value. An MCP tool argument is **not**
143
- a shell: `$(cat "…/fig.html")`, a backtick `` `cat file` ``, any command
169
+ **Pass markup INLINE — never a shell reference.** The `html` (create) and each
170
+ screen's `html`/`css` + `sharedCss` (compose) arguments are the *strings
171
+ themselves*, passed directly as the value. An MCP tool argument is **not** a
172
+ shell: `$(cat "…/fig.html")`, a backtick `` `cat file` ``, any command
144
173
  substitution, or a bare file path will **not** expand — the literal string is
145
- stored as the fig, producing a tiny black/empty canvas (the engine now rejects
146
- this with a 400). If you authored the HTML in a scratch file to render/verify
147
- it (step "Verify before publishing"), **read that file's contents** and pass
148
- the contents as `html` — do not reference the path.
174
+ stored (a tiny black/empty canvas; the engine rejects a shell-ref create with
175
+ a 400). If you authored content in a scratch file, **read that file's
176
+ contents** and pass the contents do not reference the path.
149
177
 
150
178
  3. **Server render — the user's call, never yours.** Figura can also render the
151
179
  fig server-side against the team brand: **`figura_generate_fig`** with a
@@ -222,27 +250,48 @@ so you hand it the studied structure in the brief (see step 3, "Server render").
222
250
  `figura fig uncommit <id>`) returns it to a draft — the share link is
223
251
  untouched either way.
224
252
 
225
- ## Presenting a redesignoverview ⇄ focus
253
+ 8. **Assign a reviewer.** Committing notifies the team in a digest that is
254
+ ambient, not an assignment. If the fig argues a change or wants a decision,
255
+ finish by putting it in front of a named person with
256
+ **`figura_request_feedback`** (CLI: `figura fig request-feedback <id> --from
257
+ <name|email> --note <text>`). See "Assign a reviewer" below — this step is
258
+ what stops a fig from being landed, committed, and never actually read.
259
+
260
+ ## Presenting a redesign — FOCUS first, overview second
226
261
 
227
262
  When a fig argues a change against a current state (a shipped baseline, or
228
- several candidate directions), build one fig with **two switchable views** —
229
- plain CSS-only radio tabs for THIS specific toggle (it's binary, must survive
230
- truncation, and doesn't need a library):
231
-
232
- - **Overview side by side.** Every state at once: the honest current state
233
- first, then each variation, as scaled-down frames in a grid. Under each: a
234
- status chip and ONE condensed context line (`+ / –`). This view is for
235
- scanning and picking a direction, not for reading detail. The grid must
263
+ several candidate directions), use **`figura_compose_fig` with
264
+ `layout: 'overview-focus'`** Figura emits the two switchable views (the
265
+ CSS-only radio-tab Focus full-frame toggle ⇄ Overview grid) for you from your
266
+ per-screen content. You supply each state's CONTENT + a `label`; the composition,
267
+ the focus pills, the scaled overview grid, and the no-horizontal-scroll guarantee
268
+ are server-owned. The design intent below still guides WHAT each state's content
269
+ says and which is recommended you just no longer hand-write the toggle markup
270
+ or the frame scaling. (If you're on the `create` escape hatch instead, the same
271
+ pattern must be hand-authored — plain CSS-only radio tabs, binary, no library:)
272
+
273
+ **★ FOCUS IS THE DEFAULT VIEW.** The fig opens on ONE full-size frame with state
274
+ pills — on desktop AND mobile. Overview is the *alternative*, behind the second
275
+ tab. Never open a redesign fig on the scaled grid: `layout: 'variations'` (a
276
+ scan-only grid at the kit's `--fig-scale:0.34`) is NOT a redesign presentation —
277
+ using it for one shipped a 4-state fig as 134px phones with ~3px annotation
278
+ chips. A Focus-only fig is **complete on its own**; add Overview only when there
279
+ are enough states that scanning them at once genuinely helps.
280
+
281
+ - **Focus — full frame (default).** One state at a time behind state pills
282
+ (Today / A / B / …), each state framed identically so flipping states in place
283
+ reads as spot-the-difference. This is where the context lives, pinned on the
284
+ frame as annotation chips: what's broken today, what each variation improves,
285
+ what it trades away. Open on the first pill = the honest current state.
286
+ - **Overview — side by side (alternative).** Every state at once: the honest
287
+ current state first, then each variation, as scaled-down frames in a grid.
288
+ Under each: a status chip and ONE condensed context line (`+ / –`). This view
289
+ is for scanning and picking a direction, not for reading detail. The grid must
236
290
  wrap to one column before it can overflow — **a fig never scrolls
237
291
  horizontally**, at any viewport. To shrink the framed phones, wrap each in the
238
292
  kit's **`.fig-mini`** wrapper (see "Scaling a framed device" below), never a
239
293
  rule on `.fig-device` and never CSS `zoom` (zoom leaks an unzoomed
240
294
  `scrollWidth` into horizontal overflow — the exact thing this view forbids).
241
- - **Focus — full frame.** One state at a time behind state pills (Today / A /
242
- B / …), each state framed identically so flipping states in place reads as
243
- spot-the-difference. This is where the context lives, pinned on the frame
244
- as annotation chips: what's broken today, what each variation improves,
245
- what it trades away.
246
295
  - **In both views:** render the current state honestly from the shipped
247
296
  implementation — **reconstructed from source** per "Reconstruct the baseline
248
297
  from SOURCE" (no screenshot ⇒ study the component file(s) and rebuild the real
@@ -279,8 +328,8 @@ truncation, and doesn't need a library):
279
328
 
280
329
  A **wipe-slider** stays the narrow tool for exactly two states of the same
281
330
  layout with pixel-level tweaks. Never ship a squeezed side-by-side as the
282
- *only* view of desktop frames, and never a full-frame toggle as the only
283
- view of many variations each is one half of the pair above.
331
+ *only* view a scan-only grid is never the whole argument. The reverse is
332
+ NOT true: a Focus-only toggle is a complete fig, however many states it has.
284
333
 
285
334
  ## JavaScript is ALLOWED — use it for high-fidelity animation
286
335
 
@@ -321,6 +370,31 @@ Example — a GSAP-driven entrance for a mobile screen, pinned + allowlisted:
321
370
  </script>
322
371
  ```
323
372
 
373
+ ## Assign a reviewer — a proposal fig isn't done until someone's on it
374
+
375
+ **A link nobody was pointed at is a fig nobody saw.** Landing, publishing, and
376
+ committing all produce a URL; none of them puts the fig in front of a specific
377
+ person. Committing notifies the team in a *digest*, which is ambient — it is not
378
+ an assignment, and it is not a decision request.
379
+
380
+ So after landing any fig that **argues a change or wants a decision**, send a
381
+ feedback request as a required final step (step 8 above):
382
+
383
+ - **MCP:** `figura_request_feedback` — the fig id, the teammate, and a one-line
384
+ note saying what to look at or decide.
385
+ - **CLI:** `figura fig request-feedback <id> --from <name|email> [--note <text>]`
386
+ (use this when the MCP is bound to a different team than the fig — the CLI
387
+ reads the repo's `.figura/config.json`, the MCP may not).
388
+
389
+ They get an in-app notification, a push, and an email, and the discussion lands
390
+ in the fig's comment thread. **Name what you want from them** ("does 02 close
391
+ #2375?") rather than "thoughts?" — a request with a question attached gets
392
+ answered; a bare link gets deferred.
393
+
394
+ Ask the user who should review only when it isn't obvious from context. Skip the
395
+ request only for throwaway/exploratory figs, or when the user explicitly wants it
396
+ private.
397
+
324
398
  ## Metadata & geometry — stamped for you
325
399
 
326
400
  Every landed fig is **auto-stamped server-side** (on `create` and `generate`)
@@ -336,8 +410,11 @@ with two things you do **not** hand-write:
336
410
  search and the self-improving loop. Artifact-descriptive only — no team/brand
337
411
  ids (the block is served on the public link).
338
412
 
339
- So when you **hand-build** a fig (the default path), you do your part of the
340
- contract — but the exact rules are **fetched, not restated here**: the
413
+ With **`figura_compose_fig`** you do NOT hand-write any of the frame/safe/device
414
+ contract below Figura stamps it from your typed params. The rest of this
415
+ section applies ONLY to the **`figura_create_fig` escape hatch**, where you
416
+ hand-author the whole document: the exact rules are **fetched, not restated
417
+ here** — the
341
418
  presentation model (frameless single vs canvas composition), when to emit or
342
419
  omit the whole-fig `<meta name="figura:artboard">`, the `.fig-device` /
343
420
  `data-device` frame markup (Figura stamps the bezel, Dynamic Island, and
@@ -0,0 +1,7 @@
1
+ {
2
+ "$comment": "Skill version manifest (#752) — the source of truth for each Figura skill's OWN version line, DECOUPLED from the figura-cli package version. A release bumps the version here when a skill's content changes, then regenerates the engine seed (bun packages/core/scripts/build-skill-seed.mjs) and re-seeds. Version is a date tag (YYYY.MM.DD[.n]); releasedAt is ISO-8601. The CLI/MCP stamp this version into .figura-version at install; the engine seeds the registry from it.",
3
+ "skills": {
4
+ "fig": { "version": "2026.07.28", "releasedAt": "2026-07-28T00:00:00.000Z" },
5
+ "figura-brand": { "version": "2026.07.26", "releasedAt": "2026-07-26T00:00:00.000Z" }
6
+ }
7
+ }