aem-eds-kit 0.4.30 → 0.4.48

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/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
  ## [Unreleased]
8
8
 
9
9
  ### Changed
10
+ - **README rewritten for the npm page** (EDS-047): absolute links (relative
11
+ ones break on npmjs.com), a docs / changelog / e2e links row, a "What it does"
12
+ list, a wider package description and keywords.
13
+ - **Figma REST specs are embedded as compact JSON** (part of EDS-020), as
14
+ scraped specs already were - fewer prompt tokens, same data.
15
+ - **`eds block test` screenshots just the block** (EDS-021). Baselines used to
16
+ be full-page shots of a 900 px tall viewport, so a short block carried
17
+ hundreds of pixels of white and a height change inside them went unnoticed.
18
+ Existing baselines need one `eds block test --update`; a run against an old
19
+ baseline says so instead of reporting a size change.
10
20
  - **`block from-design` now fetches the design once into a cached snapshot,
11
21
  then generates from it** (fetch → cache → generate). The CLI resolves the
12
22
  design via the cheapest adapter - local SVG/PNG export, a cached snapshot
@@ -17,7 +27,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
17
27
  - **MCP usage cut to one call per block.** The prompt now uses only
18
28
  `get_design_context` (dropped `get_variable_defs`; `get_screenshot` is opt-in
19
29
  via `--screenshot`) - the Figma Dev Mode MCP server meters tool calls per plan
20
- (Starter/View/Collab: ~6-20/month). Set a Figma token to skip MCP via REST.
30
+ (20/month on Starter or with a View/Collab seat; 200-600/day with Dev/Full on
31
+ paid plans). Set a Figma token to fetch via REST instead, on its own budget.
21
32
  - **Block generation defaults to the fastest model (Haiku)**, overridable with
22
33
  `model` in config - block codegen is structured, bounded work.
23
34
  - **`eds block preview` no longer auto-launches.** After `from-design` you get a
@@ -31,6 +42,28 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
31
42
  bundled binary (`dist/bin/eds.js` 265 kB → ~170 kB).
32
43
 
33
44
  ### Fixed
45
+ - **Figma limits described correctly** (EDS-040). Code comments, README, docs
46
+ and site said REST had a "far larger quota on every plan". Its Tier 1
47
+ endpoints are 20 calls/month on Starter or with a View/Collab seat, and
48
+ 10-20/min with Dev/Full on paid plans. The README now has the REST and MCP
49
+ limits table, with links to Figma's pages.
50
+ - **A Figma REST rate limit no longer spends your MCP budget** (EDS-039). A 429
51
+ used to fall back to Figma MCP silently. Now a short `Retry-After` (≤ 60 s)
52
+ is waited out and retried once; a plan cap stops with when it resets, the
53
+ plan tier, and the options (`--mcp`, a PNG/SVG export). `--design` reports
54
+ the same.
55
+ - **Content Fragment blocks are generated as Content Fragment blocks**
56
+ (EDS-019). With `--source cf` / `mixed`, Claude Code now defaults to
57
+ `sonnet` (a `model` in config still wins) and the prompt makes the CF field
58
+ plus an async, fetching `decorate()` a hard requirement. On the fast default
59
+ model both sources produced plain document blocks.
60
+ - **Migrated checkboxes no longer default to ticked** (EDS-032). The default
61
+ now comes from `checked`, not from `value` (which is the value a ticked box
62
+ submits), for Touch UI and Classic UI dialogs.
63
+ - **Cursor and Codex get a large prompt through its file** (EDS-017). They
64
+ take the prompt as one command-line argument; past 96 KB (a scraped page of
65
+ ~30 sections) it's replaced by a short instruction to read the saved prompt
66
+ file, so the spawn can't hit Linux's 128 KB per-argument limit (E2BIG).
34
67
  - **Block preview blank-screen cases.** The harness now (a) reduces a full-HTML
35
68
  `_<name>.preview.html` mock to its `<body>` contents, (b) reveals the body past
36
69
  the EDS `body.appear` visibility gate (which `scripts.js` would normally flip),
@@ -46,6 +79,29 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
46
79
  is a CLI, exposed via its `bin`.
47
80
 
48
81
  ### Added
82
+ - **Changelog page on the site** (EDS-048): `site/changelog.html` is generated
83
+ from this file by `scripts/site-changelog.mjs` in the Pages workflow, with
84
+ EDS-NNN references linked to their issues. It's in the nav on every page,
85
+ the docs sidebar, the footers and the sitemap.
86
+ - **The interactive menu stays open** (EDS-042). `eds` with no command now
87
+ returns to the menu after each action (or its failure). Arrows move, Enter
88
+ runs, **Esc** goes back to the menu from any prompt and quits from the menu
89
+ itself (or pick *Exit*); Ctrl+C quits anywhere. A preview opened from the
90
+ menu stops on Esc / Enter and returns. Commands run with arguments still run
91
+ once and exit.
92
+ - **Figma REST calls are paced per minute** (EDS-041). All `eds` runs in a
93
+ project share a call log (`tmp/eds-cli/figma-rest-calls.json`) and wait for a
94
+ free slot, so generating many blocks at once doesn't trip Tier 1's per-minute
95
+ limit. `figma.restPerMinute` in config, default 10 (Professional; 15
96
+ Organization, 20 Enterprise). Twelve parallel runs: ten fetch at once, the
97
+ last two at ~61 s, no 429.
98
+ - **`eds migrate component` reads Classic UI dialogs** (EDS-018). A component
99
+ with only a `dialog.xml` (AEM 6.5) is mapped from its ExtJS xtypes - text,
100
+ textarea, richtext (incl. the foundation `rte.*` include), pathfield,
101
+ numberfield, selection (checkbox / select / radio, static options or
102
+ datasource → text), smartimage, multifield (plain multi-value, or ACS
103
+ `multifieldpanel` → container), tabpanel and `cqinclude`. Tested on the 22
104
+ Classic UI dialogs of ACS Commons 3.14: 21 migrate, 63 valid fields.
49
105
  - **`eds block preview` with no name → a live gallery of every content block**,
50
106
  each in its own width-honest iframe. Routes are computed per request, so a
51
107
  block generated while the gallery is open appears on the next reload
package/README.md CHANGED
@@ -5,15 +5,27 @@
5
5
  [![npm version](https://img.shields.io/npm/v/aem-eds-kit.svg)](https://www.npmjs.com/package/aem-eds-kit)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/aem-eds-kit.svg)](https://www.npmjs.com/package/aem-eds-kit)
7
7
  [![CI](https://github.com/piotrgodycki/aem-eds-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/piotrgodycki/aem-eds-kit/actions/workflows/ci.yml)
8
- [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/piotrgodycki/aem-eds-kit/blob/main/LICENSE)
9
9
  [![node](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org)
10
10
 
11
+ **[Docs](https://piotrgodycki.github.io/aem-eds-kit/docs.html)** · **[Changelog](https://piotrgodycki.github.io/aem-eds-kit/changelog.html)** · **[End-to-end test](https://piotrgodycki.github.io/aem-eds-kit/e2e-example.html)** · **[GitHub](https://github.com/piotrgodycki/aem-eds-kit)** · **[Issues](https://github.com/piotrgodycki/aem-eds-kit/issues)**
12
+
11
13
  **Turn a design into an EDS block** - JS, CSS and a Universal Editor model - without leaving your terminal.
12
14
 
13
15
  `aem-eds-kit` gives you the `eds` command. Paste a design link - a Figma URL, or a Framer / Google Stitch page - and it hands the design to the AI agent you already use (Claude Code, Cursor or Codex), which writes production-ready code straight into your repo. Figma is read through MCP or the REST API; Framer and Stitch render real HTML, so they're **scraped with headless Chromium** (no token needed). It also scaffolds blocks, previews them in your browser, and keeps your project healthy with a few quick audits.
14
16
 
15
17
  It lives next to `@adobe/aem-cli` (your local dev server) and handles everything around it.
16
18
 
19
+ ## What it does
20
+
21
+ - **Blocks from a design.** A Figma frame (REST or MCP), a Framer / Google Stitch page (scraped with headless Chromium) or a local PNG / SVG export becomes `blocks/<name>/`: JS, CSS, a Universal Editor model and a preview mock. Works with Claude Code, Cursor and Codex. Content Fragment blocks (`--source cf` / `mixed`) get an async, fetching `decorate()`.
22
+ - **Migration from AEM.** `eds migrate component` turns a classic component's dialog into a block and a UE model, with no agent involved. It handles AEM as a Cloud Service proxy components (via `sling:resourceSuperType`, e.g. onto Core Components) and AEM 6.5 Classic UI `dialog.xml` files.
23
+ - **Within Figma's limits.** One REST or MCP call per block, then a cache. REST calls are paced per minute across parallel runs. A rate limit stops with the reset time; it never spends your MCP budget behind your back. [The limits](#how-figma-integration-works).
24
+ - **QA before review.** `eds block test` does visual regression on the block itself, plus a design↔render diff. `eds block check` covers content and a11y, including preview mocks nested deeper than real EDS markup. There are also `doctor`, loading and security audits.
25
+ - **An interactive menu that stays open.** Run `eds`, then use the arrows, Enter, and Esc to go back. Every command also runs non-interactively for CI.
26
+
27
+ Proof: the whole example screen (11 blocks) was generated from a scraped page and from Figma exports, screenshotted next to the design, and 163 + 22 real AEM components were migrated. See the [end-to-end test](https://piotrgodycki.github.io/aem-eds-kit/e2e-example.html).
28
+
17
29
  ## Install
18
30
 
19
31
  ```bash
@@ -78,7 +90,7 @@ eds block from-design \
78
90
 
79
91
  ## Interactive mode
80
92
 
81
- Run `eds` with no command and it opens an **interactive menu** that walks you through every feature (set up the project, generate a block from a design, migrate a classic AEM component, scaffold, create a block, add an integration, preview, Admin API, audits, Figma setup). Most commands are also interactive on their own when you omit arguments - e.g. `eds init`, `eds block from-design` (wizard), `eds migrate component`, `eds scaffold`, `eds integrate`, `eds preview`. Flags and subcommands still work for CI / power users.
93
+ Run `eds` with no command and it opens an **interactive menu** that stays open between actions (arrows to move, Enter to run, **Esc** to go back - or to quit from the menu itself; Ctrl+C quits anywhere). It walks you through every feature (set up the project, generate a block from a design, migrate a classic AEM component, scaffold, create a block, add an integration, preview, Admin API, audits, Figma setup). Most commands are also interactive on their own when you omit arguments - e.g. `eds init`, `eds block from-design` (wizard), `eds migrate component`, `eds scaffold`, `eds integrate`, `eds preview`. Flags and subcommands still work for CI / power users.
82
94
 
83
95
  A full A-Z reference of every command lives on the docs page: **https://piotrgodycki.github.io/aem-eds-kit/docs.html**
84
96
 
@@ -223,9 +235,13 @@ Start migrating a **classic AEM component** to EDS. Phase 1 is deterministic (no
223
235
 
224
236
  It also pulls the component's **clientlib CSS** off disk into the block's CSS when it can find one (auto-detected, or `--clientlib <dir>`): it honours `css.txt` ordering (and `#base=`), concatenates the `.css`, and flags any LESS/SCSS that needs a build. Treat it as a starting point - review the selectors and scope them under `.<block>`.
225
237
 
238
+ **AEM as a Cloud Service and AEM 6.5.** A *proxy component* (the usual AEMaaCS setup: no dialog of its own, just `sling:resourceSuperType` pointing at a Core Component) inherits its parent's dialog. The parent is looked up in the component's own `jcr_root`, and in any folder passed with `--super-root` (e.g. a checkout of `adobe/aem-core-wcm-components`, `content/src/content/jcr_root`). Granite `include`s are resolved from disk the same way. A component with only a **Classic UI** dialog (`dialog.xml`, still found on AEM 6.5) is read from its ExtJS `xtype`s: `textfield`, `textarea`, `richtext`, `pathfield`, `numberfield`, `selection`, `smartimage`, `multifield` (incl. ACS `multifieldpanel`), `tabpanel` and `cqinclude`.
239
+
226
240
  ```bash
227
241
  eds migrate component /path/to/apps/myproject/components/hero
228
242
  eds migrate component ./components/hero --name hero --clientlib ./ui.apps/.../hero/clientlib --yes
243
+ eds migrate component ./ui.apps/.../apps/wknd/components/teaser \
244
+ --super-root ../aem-core-wcm-components/content/src/content/jcr_root # proxy → Core Component dialog
229
245
  ```
230
246
 
231
247
  Dialogs map cleanly because Granite's `sling:resourceType` set is finite. The most faithful CSS actually comes from the **rendered page** (resolves LESS vars + cascade) - that capture lands in a later phase; for now the clientlib source gets you most of the way.
@@ -275,7 +291,7 @@ eds ue open / --org my-org --ref dev
275
291
  eds ue open /blog --url main--site--owner.aem.page --no-open
276
292
  ```
277
293
 
278
- Full workflow (proxy, `.env`, `xwalk.json`, editor deep link): [`docs/local-development.md`](docs/local-development.md).
294
+ Full workflow (proxy, `.env`, `xwalk.json`, editor deep link): [`docs/local-development.md`](https://github.com/piotrgodycki/aem-eds-kit/blob/main/docs/local-development.md).
279
295
 
280
296
  ### `eds block create <name>`
281
297
 
@@ -286,7 +302,7 @@ eds block create hero-banner # js + css + _hero-banner.json (UE mode
286
302
  eds block create hero-banner --no-ue-model # skip the UE model
287
303
  ```
288
304
 
289
- The generated `_<name>.json` uses the EDS block-plugin (xwalk) format (`definitions` / `models` / `filters`) with starter fields (title + heading-level select + rich text). Extend it from the **full catalog of all 17 Universal Editor field types** - see [`docs/universal-editor-fields.md`](docs/universal-editor-fields.md). `eds block from-design` picks the right field type per Figma layer automatically.
305
+ The generated `_<name>.json` uses the EDS block-plugin (xwalk) format (`definitions` / `models` / `filters`) with starter fields (title + heading-level select + rich text). Extend it from the **full catalog of all 17 Universal Editor field types** - see [`docs/universal-editor-fields.md`](https://github.com/piotrgodycki/aem-eds-kit/blob/main/docs/universal-editor-fields.md). `eds block from-design` picks the right field type per Figma layer automatically.
290
306
 
291
307
  ### `eds block from-design [design-url]`
292
308
 
@@ -294,22 +310,36 @@ Translates a design - **a Figma URL, a Framer / Google Stitch page, or a local S
294
310
 
295
311
  **Figma vs. web sources.** Figma paints to a WebGL canvas, so it's read through the **REST API** (with a token) or **MCP**. Framer and Google Stitch render **real HTML**, so `eds` **scrapes** them with headless Chromium — reading computed layout, typography, colours and geometry straight off the page (no token, no MCP). Canva and Sketch are canvas/image-based and aren't scrapeable — export a frame and pass it as a local SVG/PNG. The provider is detected from the URL host, or set with `--provider`.
296
312
 
313
+ **Gated sources (`--auth`).** A Google Stitch project (and a private Framer page) sits behind a login, so an anonymous scrape only sees the sign-in shell. Pass `--auth` and `eds` opens a **headed** browser once — you log in by hand, it saves the session, and every later scrape reuses it. The session (cookies/localStorage) is stored owner-only under `~/.eds/auth/<provider>.json`; re-login with `--auth-refresh`. (The saved session is a secret — it's never written to the repo.)
314
+
297
315
  **Fetch → cache → generate.** `eds` resolves the design **once** into a snapshot, then the agent generates from it — so retries and refine passes never re-fetch. It picks the cheapest adapter automatically:
298
316
 
299
317
  1. **Local file** — pass a path to an SVG/PNG export (`eds block from-design ./hero.svg`) → read from disk, no network.
300
318
  2. **Cache** — a snapshot already fetched for this design (`tmp/eds-cli/design-cache/`) is reused.
301
319
  3. **Web scrape (Framer / Stitch)** — the live page is rendered in headless Chromium and its computed layout/styles are extracted. Needs Playwright + a browser: `npx playwright install chromium`.
302
- 4. **Figma REST** — if a Figma token is set, `eds` fetches the node itself and embeds a compact design spec. Separate, generous quota; **does not touch the MCP limit**.
320
+ 4. **Figma REST** — if a Figma token is set, `eds` fetches the node itself and embeds a compact design spec. Its own budget, separate from MCP (see the limits below); **does not spend MCP calls**.
303
321
  5. **Figma MCP** — fallback: the agent makes **one** `get_design_context` call and caches it.
304
322
 
305
323
  The agent then generates pixel-perfect code (JS + CSS) **and its Universal Editor model** by default, with lightweight **placeholders** for not-yet-authored content. You see a **live, colour-coded log** with per-phase timings. On success you get a **menu** — refine the block with another pass, open the live preview, or finish (preview is **opt-in**, never auto-launched).
306
324
 
307
- > **Figma MCP limits:** the Dev Mode MCP server is metered per plan (Starter / View / Collab seats: ~6–20 **tool calls a month**). `eds` uses **one** MCP call per block and caches it; set a Figma token to skip MCP entirely via REST. See [How Figma integration works](#how-figma-integration-works).
325
+ > **Figma limits.** Figma meters both paths, and the limit follows the **plan that owns the file** (a Full seat elsewhere doesn't help a file in a Starter team).
326
+ >
327
+ > | | Starter | Professional | Organization | Enterprise |
328
+ > |---|---|---|---|---|
329
+ > | **REST** (Tier 1: file, nodes, image), Dev / Full seat | 20 / month | 10 / min | 15 / min | 20 / min |
330
+ > | **REST**, View / Collab seat | 20 / month | 20 / month | 20 / month | 20 / month |
331
+ > | **MCP**, Dev / Full seat | 20 / month | 200 / day, 10 / min | 200 / day, 15 / min | 600 / day, 20 / min |
332
+ > | **MCP**, View / Collab seat | 20 / month | 20 / month | 20 / month | 20 / month |
333
+ >
334
+ > `eds` spends **one** call per block (REST `file nodes`, or MCP `get_design_context`) and caches it; `--design` adds one REST `image` call, `--screenshot` one MCP call. A REST 429 with a short `Retry-After` is waited out; a plan cap stops instead of spending MCP calls. A local PNG/SVG export uses no quota. Figma can change these numbers - see its [REST](https://developers.figma.com/docs/rest-api/rate-limits/) and [MCP](https://developers.figma.com/docs/figma-mcp-server/rate-limits-access/) pages.
335
+ >
336
+ > See [How Figma integration works](#how-figma-integration-works).
308
337
 
309
338
  ```bash
310
339
  eds block from-design # interactive wizard
311
340
  eds block from-design "<figma-url>" --name hero # non-interactive (REST if a token is set, else MCP)
312
341
  eds block from-design "<framer-url>" --name hero # Framer/Stitch → scraped with headless Chromium
342
+ eds block from-design "<stitch-url>" --name hero --auth # gated source → log in once, reuse the session
313
343
  eds block from-design ./hero.svg --name hero # from a local export (no network)
314
344
  eds block from-design --sample --name hero # bundled Kiri Bloom sample (no token, no MCP)
315
345
  eds block from-design "<figma-url>" --dry-run # just build the prompt
@@ -319,13 +349,15 @@ eds block from-design "<figma-url>" --dry-run # just build the prompt
319
349
  |---|---|
320
350
  | `--name <name>` | Override the inferred block name |
321
351
  | `--provider <type>` | Design source: `figma` \| `framer` \| `stitch` (else detected from the URL) |
322
- | `--source <type>` | Content source: `document` \| `ue` \| `cf` \| `mixed` |
352
+ | `--source <type>` | Content source: `document` \| `ue` \| `cf` \| `mixed` (`cf` / `mixed` default to the `sonnet` model) |
323
353
  | `--widths <list>` | Design frame widths for preview breakpoints (e.g. `390,768,1440`) |
324
- | `--agent <type>` | Force `claude` \| `cursor` \| `codex` \| `none` |
354
+ | `--agent <type>` | Force `claude` \| `cursor` \| `codex` \| `none` (a prompt too large for one argument reaches Cursor / Codex as its file) |
325
355
  | `--dry-run` | Build the prompt only |
326
356
  | `--no-ue-model` | Skip the Universal Editor model |
327
357
  | `--screenshot` | Add a screenshot MCP call for a pixel check (opt-in — costs a call) |
328
358
  | `--mcp` | Force the Figma MCP adapter even when a REST token is set |
359
+ | `--auth` | Scrape with a logged-in session (Framer/Stitch): log in once, reuse it |
360
+ | `--auth-refresh` | Force a fresh scrape login even if a session is saved |
329
361
  | `--refresh` | Ignore the cached snapshot and re-fetch |
330
362
  | `--sample` | Use the bundled Kiri Bloom sample design (no URL, token or MCP) |
331
363
  | `--serve` | Auto-start the live preview (non-interactive runs only) |
@@ -375,7 +407,7 @@ eds block preview hero --port 9000 --no-open
375
407
 
376
408
  Two deterministic QA passes over a block's **rendered** DOM (no agent) - one block, or **all** of them when you omit the name. Both render the block in headless Chromium (Playwright), so install the browser once: `npx playwright install chromium`. Wire them into CI next to the audits - both exit non-zero on failure.
377
409
 
378
- **`eds block test`** - **visual regression.** Screenshots each block at every breakpoint and diffs it (pixelmatch) against a committed baseline in `blocks/<name>/__snapshots__/`; it fails when the changed-pixel ratio crosses the threshold (default 1%) and writes the diff image to `tmp/eds-cli/visual-diff/`. `--update` (re)writes the baselines after an intended change. `--design` does a **design↔render** diff instead - the rendered block against its Figma design image (needs a Figma token and a Figma source in the block's `.eds-meta.json`).
410
+ **`eds block test`** - **visual regression.** Screenshots each block (the block element only, not the empty viewport) at every breakpoint and diffs it (pixelmatch) against a committed baseline in `blocks/<name>/__snapshots__/`; it fails when the changed-pixel ratio crosses the threshold (default 1%) and writes the diff image to `tmp/eds-cli/visual-diff/`. `--update` (re)writes the baselines after an intended change. `--design` does a **design↔render** diff instead - the rendered block against its Figma design image (needs a Figma token and a Figma source in the block's `.eds-meta.json`).
379
411
 
380
412
  ```bash
381
413
  eds block test # all blocks vs committed baselines
@@ -393,7 +425,7 @@ eds block test --threshold 0.02 # allow 2% changed pixels
393
425
  | `--widths <list>` | Breakpoint widths (e.g. `375,768,1280`) |
394
426
  | `--port <port>` | Port for the internal preview server (default `8788`) |
395
427
 
396
- **`eds block check`** - **content, link & a11y.** Renders each block (after `decorate()`) and flags missing image `alt`, missing image dimensions (CLS risk), links/buttons with no accessible name, empty `href`s and inline event handlers. Checks run at the widest breakpoint; it exits non-zero on any error, or on any warning with `--strict`.
428
+ **`eds block check`** - **content, link & a11y.** Renders each block (after `decorate()`) and flags missing image `alt`, missing image dimensions (CLS risk), links/buttons with no accessible name, empty `href`s and inline event handlers. It also warns when the preview mock (`_<name>.preview.html`) nests cell content one `<div>` deeper than real EDS markup (row > cell > content), which leaves `decorate()` written against the mock misreading real content. Checks run at the widest breakpoint; it exits non-zero on any error, or on any warning with `--strict`.
397
429
 
398
430
  ```bash
399
431
  eds block check # all blocks
@@ -544,7 +576,7 @@ The design is fetched **once** into a cached snapshot, then the agent generates
544
576
  ```
545
577
 
546
578
  - **No token needed** for the MCP path — the agent uses its own Figma MCP connection and makes **one** `get_design_context` call per block (then caches it).
547
- - **Set a Figma token** (`FIGMA_TOKEN` or `figma.token`) to use the **REST** path instead: `eds` fetches the node over HTTPS and embeds a compact spec — a **separate, far larger quota** than the MCP limit, so it works comfortably on Starter / Collab seats.
579
+ - **Set a Figma token** (`FIGMA_TOKEN` or `figma.token`) to use the **REST** path instead: `eds` fetches the node over HTTPS and embeds a compact spec, on REST's own budget instead of MCP's. With a Dev / Full seat on a paid plan that's a per-minute limit (10–20 / min) instead of MCP's daily cap; on Starter, or with a View / Collab seat, both are 20 calls a month.
548
580
  - **Local export** (`./file.svg|png`) reads straight from disk — zero network, best for vector/text-heavy frames.
549
581
 
550
582
  The prompt includes:
@@ -564,11 +596,13 @@ Priority: CLI flags → env vars → `.edsrc.json` (project) → `~/.eds/config.
564
596
  {
565
597
  "agent": "claude",
566
598
  "model": "haiku",
567
- "figma": { "token": "figd_…" }
599
+ "figma": { "token": "figd_…", "restPerMinute": 10 }
568
600
  }
569
601
  ```
570
602
  `model` sets the block-generation model (default: fastest / Haiku). `figma.token` (or the `FIGMA_TOKEN` env var) enables the REST fetch path — a Personal Access Token with scope `file_content:read`. Tokens are secrets; keep them out of the repo.
571
603
 
604
+ `figma.restPerMinute` paces Figma REST calls across every `eds` run in the project (they share `tmp/eds-cli/figma-rest-calls.json`), so a batch of blocks waits for a free slot instead of hitting a 429. Default **10** (Tier 1, Dev / Full seat on Professional); use 15 on Organization, 20 on Enterprise. On Starter or with a View / Collab seat the cap is monthly (20), so pacing can't help there.
605
+
572
606
  **`.edsrc.json`** (project root, optional):
573
607
  ```json
574
608
  {
@@ -664,7 +698,8 @@ Tests use fixtures in `tests/fixtures/` - a sample EDS project and recorded Figm
664
698
 
665
699
  ## Roadmap
666
700
 
667
- - **Design sources** - Figma (REST/MCP), Framer and Google Stitch (headless-Chromium scrape) are wired; next is deepening the scrape extractor (multi-breakpoint capture, component/variant detection) and a generic `eds setup` in place of `eds figma setup`. Canva/Sketch stay on the local-export path (canvas/image — not scrapeable).
701
+ - **Design sources** - Figma (REST/MCP), Framer and Google Stitch (headless-Chromium scrape) are wired; next is deepening the scrape extractor (multi-breakpoint capture, component/variant detection), a generic `eds setup` in place of `eds figma setup`, and an end-to-end run against a real Figma file ([EDS-020](https://github.com/piotrgodycki/aem-eds-kit/issues/20)). Canva/Sketch stay on the local-export path (canvas/image — not scrapeable).
702
+ - **Migration** - dialogs (Touch UI and Classic UI) map deterministically today; next is HTL → `decorate()` and capturing CSS from the rendered page.
668
703
  - **Screenshot-to-block** - build a block from a plain image (harder: no structured layout/measurements, so pixel-perfect is tougher).
669
704
  - **Tokens & sync** - design variables → CSS custom properties, and incremental re-sync of an existing block (`--update`).
670
705
  - **Headless & CI** - a direct MCP client (no agent needed) for CI/CD, plus `eds lighthouse`, `eds rum` and `eds migrate page`.
@@ -677,4 +712,4 @@ Adobe, AEM, Edge Delivery Services and Universal Editor are trademarks of Adobe
677
712
 
678
713
  ## License
679
714
 
680
- MIT - see [LICENSE](LICENSE).
715
+ MIT - see [LICENSE](https://github.com/piotrgodycki/aem-eds-kit/blob/main/LICENSE).