aem-eds-kit 0.4.33 → 0.4.54

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.
Files changed (4) hide show
  1. package/CHANGELOG.md +138 -30
  2. package/README.md +170 -38
  3. package/dist/bin/eds.js +185 -120
  4. package/package.json +11 -3
package/README.md CHANGED
@@ -5,15 +5,38 @@
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
- `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.
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, or a local model through Ollama / oMLX), 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, or a [local model](#local-models-ollama-omlx) through Ollama or oMLX. 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
+
29
+ <p align="center"><img src="https://raw.githubusercontent.com/piotrgodycki/aem-eds-kit/main/.github/assets/readme/design-vs-block.webp" alt="A Google Stitch hero design next to the EDS block eds generated from it: same layout, type scale, colours and buttons, with placeholder copy" width="100%"></p>
30
+
31
+ <details>
32
+ <summary><strong>The whole screen</strong>: design vs. the 11 generated blocks</summary>
33
+
34
+ <p align="center"><img src="https://raw.githubusercontent.com/piotrgodycki/aem-eds-kit/main/.github/assets/readme/design-vs-page.webp" alt="The full example page design next to the page built from 11 generated EDS blocks" width="100%"></p>
35
+
36
+ Generated blocks ship with placeholder copy (and empty image slots) until authors fill them in; the layout, spacing, type and colours come from the design.
37
+
38
+ </details>
39
+
17
40
  ## Install
18
41
 
19
42
  ```bash
@@ -58,7 +81,7 @@ eds block from-design "<figma-url>" --name hero
58
81
 
59
82
  ## Try it in 30 seconds (no Figma account, no token)
60
83
 
61
- From inside an EDS project (or spin a throwaway one up with `eds sandbox new demo`):
84
+ From inside an EDS project (or clone a fresh one with `eds new demo && cd demo`):
62
85
 
63
86
  ```bash
64
87
  eds block from-design --sample --name hero --dry-run # see the prompt - zero setup
@@ -68,6 +91,14 @@ eds block preview hero # see it live in the bro
68
91
 
69
92
  `--sample` uses a **bundled "Kiri Bloom" hero design**, read straight from disk - **no Figma token, no MCP call, no network**. (`--dry-run` needs no AI agent either - it just prints the prompt.)
70
93
 
94
+ A real run (Claude Code): the agent's live log, a verification pass over the files it wrote, then a menu to refine, preview or finish.
95
+
96
+ <p align="center"><img src="https://raw.githubusercontent.com/piotrgodycki/aem-eds-kit/main/.github/assets/readme/cli-fd.png" alt="Terminal: eds block from-design --sample --name promo, showing the design source, the agent's live log, the files it wrote, a verification table and the refine / preview / finish menu" width="760"></p>
97
+
98
+ `eds block preview` serves the block at the design's own widths, with live reload:
99
+
100
+ <p align="center"><img src="https://raw.githubusercontent.com/piotrgodycki/aem-eds-kit/main/.github/assets/readme/block-preview.webp" alt="Browser: the eds block preview harness showing the hero block at 1440 px with a breakpoint dropdown" width="100%"></p>
101
+
71
102
  Ready for the real thing? Point it at the official **Kiri Bloom AEM EDS Kit** training file in Figma:
72
103
 
73
104
  ```bash
@@ -78,7 +109,11 @@ eds block from-design \
78
109
 
79
110
  ## Interactive mode
80
111
 
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.
112
+ 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: new project (clone the boilerplate), set up project, page template, sandbox repo, block from design, migrate component, scaffold UE, empty block, integration, model fields, analytics, structured data, Universal Editor, preview block, pages (preview / publish), audits and Figma MCP.
113
+
114
+ <p align="center"><img src="https://raw.githubusercontent.com/piotrgodycki/aem-eds-kit/main/.github/assets/readme/cli-menu.png" alt="Terminal: the eds interactive menu with Block from design selected" width="560"></p>
115
+
116
+ 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
117
 
83
118
  A full A-Z reference of every command lives on the docs page: **https://piotrgodycki.github.io/aem-eds-kit/docs.html**
84
119
 
@@ -87,6 +122,9 @@ A full A-Z reference of every command lives on the docs page: **https://piotrgod
87
122
  ```bash
88
123
  eds # interactive menu
89
124
 
125
+ # Start from the latest boilerplate (DA, Universal Editor or Commerce)
126
+ eds new my-site
127
+
90
128
  # Just cloned an EDS boilerplate? Set it up for your authoring model
91
129
  eds init
92
130
 
@@ -119,7 +157,8 @@ The full path, command by command. This example targets **DA (da.live)** authori
119
157
  # 1. Install the CLI
120
158
  npm i -g aem-eds-kit
121
159
 
122
- # 2. Set up the project (pick DA, enter org + site). Writes fstab + CI + naming.
160
+ # 2. Clone the latest boilerplate, then set it up (pick DA, enter org + site)
161
+ eds new my-site && cd my-site
123
162
  eds init --authoring da --org my-org --site my-site
124
163
 
125
164
  # 3. Scaffold the Universal Editor config + helper functions
@@ -148,6 +187,32 @@ Every step is also interactive when you omit the flags - run `eds` for the menu.
148
187
 
149
188
  ## Commands
150
189
 
190
+ ### `eds new [dir]`
191
+
192
+ Clone the **current EDS boilerplate** into a local folder. No GitHub auth needed, and nothing is created on GitHub (for a repo on GitHub, see [`eds sandbox`](#eds-sandbox)). Choose Document Authoring / DA (`adobe/aem-boilerplate`), Universal Editor / crosswalk (`adobe-rnd/aem-boilerplate-xwalk`), the Adobe Commerce storefront (`hlxsites/aem-boilerplate-commerce`), or any repo or git URL.
193
+
194
+ By default the copy starts a **fresh git history**, with one initial commit naming the boilerplate commit it came from. `--keep-history` keeps the boilerplate's history with its remote named `upstream`, so `git pull upstream main` brings in later boilerplate changes. It refuses a folder that isn't empty, then offers `npm install` and `eds init`, with Universal Editor preselected for the crosswalk boilerplate.
195
+
196
+ ```bash
197
+ eds new # guided
198
+ eds new my-site --yes # DA boilerplate, fresh history
199
+ eds new my-site --ue --keep-history # crosswalk, with an upstream remote
200
+ eds new shop --boilerplate commerce --install
201
+ eds new my-site --remote git@github.com:me/my-site.git
202
+ ```
203
+
204
+ | Flag | Description |
205
+ |---|---|
206
+ | `--boilerplate <repo>` | `da` \| `ue` \| `commerce`, `owner/repo` on GitHub, or any git URL / path |
207
+ | `--ue` | The crosswalk / Universal Editor boilerplate |
208
+ | `--ref <ref>` | Branch or tag to clone |
209
+ | `--keep-history` | Keep the boilerplate's history (remote renamed to `upstream`) |
210
+ | `--remote <url>` | Set `origin` to your own repo |
211
+ | `--install` | Run `npm install` after cloning |
212
+ | `--yes` | Skip the guided prompts |
213
+
214
+ <p align="center"><img src="https://raw.githubusercontent.com/piotrgodycki/aem-eds-kit/main/.github/assets/readme/cli-new-picker.png" alt="Terminal: eds new asking for a folder and offering the DA, Universal Editor and Commerce boilerplates" width="660"></p>
215
+
151
216
  ### `eds init`
152
217
 
153
218
  Adapt a freshly cloned EDS boilerplate to your setup in one interactive wizard. Pick the authoring model - **Universal Editor (crosswalk), DA (da.live), Google Drive or SharePoint** - and `eds` sets the project name (`package.json` + boilerplate placeholders), writes `fstab.yaml` **only when the model needs it** (UE/crosswalk doesn't), scaffolds the crosswalk config + `paths.json` for UE, optionally adds the CI workflow, then walks the **external steps** (AEM Code Sync app, content-source share, Sidekick) as a clickable checklist.
@@ -159,15 +224,18 @@ By default it also writes, out of the box:
159
224
 
160
225
  Opt out with `--no-env` / `--no-xwalk`, or (re)generate either later with `eds scaffold env` / `eds scaffold xwalk`.
161
226
 
227
+ **Non-interactive (`--yes`).** The project name defaults to the folder name (or `package.json`'s, unless that's still the boilerplate's own). The authoring model is detected from the `fstab.yaml` mountpoint (an AEM author → Universal Editor, `content.da.live` → DA, Google Drive, SharePoint) or a crosswalk `paths.json`, and preselected in the interactive picker. When it can't be told (the DA boilerplate ships no `fstab.yaml`), `--yes` stops with exit 1 and asks for `--authoring`.
228
+
162
229
  ```bash
163
230
  eds init # interactive
231
+ eds init --yes # detect everything (e.g. right after `eds new --ue`)
164
232
  eds init --name my-site --authoring da --org my-org --site my-site --yes
165
233
  ```
166
234
 
167
235
  | Flag | Description |
168
236
  |---|---|
169
237
  | `--name <name>` | Project name (kebab-case) |
170
- | `--authoring <model>` | `ue` \| `da` \| `gdrive` \| `sharepoint` |
238
+ | `--authoring <model>` | `ue` \| `da` \| `gdrive` \| `sharepoint` (else detected from `fstab.yaml` / `paths.json`) |
171
239
  | `--mountpoint <url>` | Content source URL (document models) |
172
240
  | `--org <org>` / `--site <site>` | DA org / site (`--authoring da`) |
173
241
  | `--no-ci` | Skip the GitHub Actions workflow |
@@ -204,18 +272,21 @@ Spin up and tear down **disposable EDS sandbox repos** on GitHub - a rapid, thro
204
272
  eds sandbox login # authorize GitHub (device flow or token)
205
273
  eds sandbox new my-test # guided: boilerplate, visibility, Code Sync, eds init
206
274
  eds sandbox new my-test --ue --private --yes
275
+ eds sandbox new shop --boilerplate hlxsites/aem-boilerplate-commerce # any template repo
207
276
  eds sandbox list # your sandboxes (tagged eds-sandbox) + preview URLs
208
277
  eds sandbox rm my-test # delete it (with confirmation)
209
278
  ```
210
279
 
211
- `sandbox new` creates the repo from a boilerplate (Document Authoring `adobe/aem-boilerplate` or Universal Editor `adobe/aem-boilerplate-xwalk`) via the GitHub API, clones it, tags it `eds-sandbox`, then walks you through opening the AEM Code Sync install page and running `eds init`. The preview URL is `https://main--<repo>--<owner>.aem.page/`.
280
+ `sandbox new` creates the repo from a boilerplate (Document Authoring `adobe/aem-boilerplate`, Universal Editor `adobe-rnd/aem-boilerplate-xwalk`, or any template repo with `--boilerplate <owner/repo>`) via the GitHub API, clones it, tags it `eds-sandbox`, then walks you through opening the AEM Code Sync install page and running `eds init`. The preview URL is `https://main--<repo>--<owner>.aem.page/`.
212
281
 
213
282
  Sandboxes are **throwaway test repos** - stand one up to debug or try an integration, then delete it; they work fine on a personal account. Two safety guarantees:
214
283
 
215
284
  - **Never overwrites an existing repo.** `sandbox new` checks first and refuses if `<owner>/<name>` already exists (and the GitHub template API never clobbers either way).
216
285
  - **Least privilege.** Scope the token to just your sandboxes - a fine-grained token limited to one repo, or a dedicated sandbox org used via `--org`. `eds sandbox login` links you straight to the token page with the minimal permissions. See the [Secure GitHub sandboxes guide](https://piotrgodycki.github.io/aem-eds-kit/secure-github-sandboxes.html).
217
286
 
218
- The token is stored owner-only (mode `0600`) in `~/.eds/github.json`; `eds sandbox logout` removes it. `list` only ever shows `eds-sandbox` repos and `rm` deletes only the named repo, with confirmation.
287
+ The token is stored owner-only (mode `0600`) in `~/.eds/github.json`; `eds sandbox logout` removes it. `list` only ever shows `eds-sandbox` repos and `rm` deletes only the named repo (under `--org <org>` when it lives in an org), with confirmation.
288
+
289
+ Just want the code on your machine, no GitHub repo? Use [`eds new`](#eds-new-dir).
219
290
 
220
291
  ### `eds migrate component [dir]`
221
292
 
@@ -223,11 +294,17 @@ Start migrating a **classic AEM component** to EDS. Phase 1 is deterministic (no
223
294
 
224
295
  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
296
 
297
+ **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`.
298
+
226
299
  ```bash
227
300
  eds migrate component /path/to/apps/myproject/components/hero
228
301
  eds migrate component ./components/hero --name hero --clientlib ./ui.apps/.../hero/clientlib --yes
302
+ eds migrate component ./ui.apps/.../apps/wknd/components/teaser \
303
+ --super-root ../aem-core-wcm-components/content/src/content/jcr_root # proxy → Core Component dialog
229
304
  ```
230
305
 
306
+ <p align="center"><img src="https://raw.githubusercontent.com/piotrgodycki/aem-eds-kit/main/.github/assets/readme/cli-mig.png" alt="Terminal: eds migrate component on the WKND teaser proxy component; the dialog is inherited from the Core Components teaser and its 13 fields are mapped to Universal Editor field types" width="760"></p>
307
+
231
308
  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.
232
309
 
233
310
  ### `eds track block <name>`
@@ -275,7 +352,7 @@ eds ue open / --org my-org --ref dev
275
352
  eds ue open /blog --url main--site--owner.aem.page --no-open
276
353
  ```
277
354
 
278
- Full workflow (proxy, `.env`, `xwalk.json`, editor deep link): [`docs/local-development.md`](docs/local-development.md).
355
+ 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
356
 
280
357
  ### `eds block create <name>`
281
358
 
@@ -286,11 +363,11 @@ eds block create hero-banner # js + css + _hero-banner.json (UE mode
286
363
  eds block create hero-banner --no-ue-model # skip the UE model
287
364
  ```
288
365
 
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.
366
+ 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
367
 
291
368
  ### `eds block from-design [design-url]`
292
369
 
293
- Translates a design - **a Figma URL, a Framer / Google Stitch page, or a local SVG/PNG export** - into an EDS block. Run with **no URL** for a step-by-step wizard (design source, name, link, content source, UE model, screenshot, run mode). It builds a prompt with EDS conventions + your project's design tokens and hands it to your AI agent (Claude Code, Cursor, or Codex). *(This was `from-figma` before it went design-agnostic; Figma is the most battle-tested source and stays pixel-perfect.)*
370
+ Translates a design - **a Figma URL, a Framer / Google Stitch page, or a local SVG/PNG export** - into an EDS block. Run with **no URL** for a step-by-step wizard (design source, name, link, content source, UE model, screenshot, run mode). It builds a prompt with EDS conventions + your project's design tokens and hands it to your AI agent (Claude Code, Cursor or Codex, or a [local model](#local-models-ollama-omlx)). *(This was `from-figma` before it went design-agnostic; Figma is the most battle-tested source and stays pixel-perfect.)*
294
371
 
295
372
  **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
373
 
@@ -301,12 +378,23 @@ Translates a design - **a Figma URL, a Framer / Google Stitch page, or a local S
301
378
  1. **Local file** — pass a path to an SVG/PNG export (`eds block from-design ./hero.svg`) → read from disk, no network.
302
379
  2. **Cache** — a snapshot already fetched for this design (`tmp/eds-cli/design-cache/`) is reused.
303
380
  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`.
304
- 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**.
381
+ 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**.
305
382
  5. **Figma MCP** — fallback: the agent makes **one** `get_design_context` call and caches it.
306
383
 
307
384
  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).
308
385
 
309
- > **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).
386
+ > **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).
387
+ >
388
+ > | | Starter | Professional | Organization | Enterprise |
389
+ > |---|---|---|---|---|
390
+ > | **REST** (Tier 1: file, nodes, image), Dev / Full seat | 20 / month | 10 / min | 15 / min | 20 / min |
391
+ > | **REST**, View / Collab seat | 20 / month | 20 / month | 20 / month | 20 / month |
392
+ > | **MCP**, Dev / Full seat | 20 / month | 200 / day, 10 / min | 200 / day, 15 / min | 600 / day, 20 / min |
393
+ > | **MCP**, View / Collab seat | 20 / month | 20 / month | 20 / month | 20 / month |
394
+ >
395
+ > `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.
396
+ >
397
+ > See [How Figma integration works](#how-figma-integration-works).
310
398
 
311
399
  ```bash
312
400
  eds block from-design # interactive wizard
@@ -322,9 +410,10 @@ eds block from-design "<figma-url>" --dry-run # just build the prompt
322
410
  |---|---|
323
411
  | `--name <name>` | Override the inferred block name |
324
412
  | `--provider <type>` | Design source: `figma` \| `framer` \| `stitch` (else detected from the URL) |
325
- | `--source <type>` | Content source: `document` \| `ue` \| `cf` \| `mixed` |
413
+ | `--source <type>` | Content source: `document` \| `ue` \| `cf` \| `mixed` (`cf` / `mixed` default to the `sonnet` model) |
326
414
  | `--widths <list>` | Design frame widths for preview breakpoints (e.g. `390,768,1440`) |
327
- | `--agent <type>` | Force `claude` \| `cursor` \| `codex` \| `none` |
415
+ | `--agent <type>` | Force `claude` \| `cursor` \| `codex` \| `ollama` \| `omlx` \| `local` \| `none` (a prompt too large for one argument reaches Cursor / Codex as its file; see [Local models](#local-models-ollama-omlx)) |
416
+ | `--model <name>` | Model to run: a Claude alias / id, or a local model such as `qwen2.5-coder:14b` |
328
417
  | `--dry-run` | Build the prompt only |
329
418
  | `--no-ue-model` | Skip the Universal Editor model |
330
419
  | `--screenshot` | Add a screenshot MCP call for a pixel check (opt-in — costs a call) |
@@ -349,7 +438,7 @@ The generated block records the **design's frame widths** in its `.eds-meta.json
349
438
  ### `eds figma setup`
350
439
 
351
440
  Interactive wizard that:
352
- 1. Detects installed AI agents (Claude Code, Cursor, Codex)
441
+ 1. Detects installed AI agents (Claude Code, Cursor, Codex) and running local model servers (Ollama, oMLX)
353
442
  2. Checks if Figma MCP is configured for your agent
354
443
  3. Shows install commands if not
355
444
  4. Saves preferences to `~/.eds/config.json`
@@ -376,11 +465,18 @@ eds block preview hero --widths 375,768,1280 # custom breakpoints
376
465
  eds block preview hero --port 9000 --no-open
377
466
  ```
378
467
 
468
+ - A dropdown switches the block between breakpoints inside an `<iframe>`, so the block's media queries fire against the chosen width - a **width-honest** comparison that isn't distorted by your monitor size.
469
+ - Breakpoints come from the block's `.eds-meta.json` (`breakpoints`, marking which widths have a real Figma frame → pixel-perfect) and fall back to `375 / 768 / 1280` or `--widths`.
470
+ - If the block has a `.eds-meta.json` with a Figma source, an **Open in Figma** link is shown.
471
+ - Authored sample content is read from `blocks/<name>/_<name>.preview.html` if present; otherwise a placeholder is used (and the chrome tells you to add one).
472
+
473
+ > Not to be confused with `eds preview [paths...]` (lower down), which previews **pages** via the AEM Admin API.
474
+
379
475
  ### `eds block test [name]` · `eds block check [name]`
380
476
 
381
477
  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.
382
478
 
383
- **`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`).
479
+ **`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`).
384
480
 
385
481
  ```bash
386
482
  eds block test # all blocks vs committed baselines
@@ -390,6 +486,11 @@ eds block test hero --design # rendered block vs its Figma design imag
390
486
  eds block test --threshold 0.02 # allow 2% changed pixels
391
487
  ```
392
488
 
489
+ A CSS change that moved the hero's buttons and text, caught at 1440 px, with the diff image it wrote:
490
+
491
+ <p align="center"><img src="https://raw.githubusercontent.com/piotrgodycki/aem-eds-kit/main/.github/assets/readme/cli-test.png" alt="Terminal: eds block test hero reporting 2.48% changed pixels at 1440px and the diff PNG path" width="760"></p>
492
+ <p align="center"><img src="https://raw.githubusercontent.com/piotrgodycki/aem-eds-kit/main/.github/assets/readme/visual-diff.webp" alt="Diff image: the changed pixels of the hero block highlighted in red and yellow" width="760"></p>
493
+
393
494
  | Flag | Description |
394
495
  |---|---|
395
496
  | `--update` | (Re)write baseline screenshots instead of comparing |
@@ -398,7 +499,7 @@ eds block test --threshold 0.02 # allow 2% changed pixels
398
499
  | `--widths <list>` | Breakpoint widths (e.g. `375,768,1280`) |
399
500
  | `--port <port>` | Port for the internal preview server (default `8788`) |
400
501
 
401
- **`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`.
502
+ **`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`.
402
503
 
403
504
  ```bash
404
505
  eds block check # all blocks
@@ -406,6 +507,8 @@ eds block check hero # one block
406
507
  eds block check --strict # fail on warnings too
407
508
  ```
408
509
 
510
+ <p align="center"><img src="https://raw.githubusercontent.com/piotrgodycki/aem-eds-kit/main/.github/assets/readme/cli-check.png" alt="Terminal: eds block check hero warning that the preview mock nests cell content one div deeper than EDS markup" width="760"></p>
511
+
409
512
  | Flag | Description |
410
513
  |---|---|
411
514
  | `--strict` | Fail on warnings too, not just errors |
@@ -446,28 +549,23 @@ eds integrate # interactive
446
549
  eds integrate gtm # gtm · ga4 · chat · cookiebot · custom
447
550
  ```
448
551
 
449
- - A dropdown switches the block between breakpoints inside an `<iframe>`, so the block's media queries fire against the chosen width - a **width-honest** comparison that isn't distorted by your monitor size.
450
- - Breakpoints come from the block's `.eds-meta.json` (`breakpoints`, marking which widths have a real Figma frame → pixel-perfect) and fall back to `375 / 768 / 1280` or `--widths`.
451
- - If the block has a `.eds-meta.json` with a Figma source, an **Open in Figma** link is shown.
452
- - Authored sample content is read from `blocks/<name>/_<name>.preview.html` if present; otherwise a placeholder is used (and the chrome tells you to add one).
552
+ ### `eds preview [paths...]`
453
553
 
454
- > Not to be confused with `eds preview <paths...>` (lower down), which previews **pages** via the AEM Admin API.
455
-
456
- ### `eds preview <path...>`
457
-
458
- Preview pages via the AEM Admin API (`admin.hlx.page`).
554
+ Preview pages via the AEM Admin API (`admin.hlx.page`). Run it with no paths for a wizard (pick the action, the paths and the org / site / ref).
459
555
 
460
556
  ```bash
557
+ eds preview # wizard
461
558
  eds preview /index
462
559
  eds preview /blog/my-post /about
463
560
  eds preview /index --org my-org --site my-repo --ref main
464
561
  ```
465
562
 
466
- ### `eds publish <path...>`
563
+ ### `eds publish [paths...]`
467
564
 
468
- Publish pages to live via the AEM Admin API.
565
+ Publish pages to live via the AEM Admin API (a wizard when run with no paths).
469
566
 
470
567
  ```bash
568
+ eds publish # wizard
471
569
  eds publish /index
472
570
  eds publish /blog/my-post /about
473
571
  ```
@@ -544,12 +642,13 @@ The design is fetched **once** into a cached snapshot, then the agent generates
544
642
  ┌─ local SVG/PNG ─┐
545
643
  ├─ cache hit ──────┤ design.json ┌─────────────┐ block files
546
644
  design reference ──┼─ Figma REST ─────┼───→ (cached) ───────→│ Claude Code │ ───────────────→ repo
547
- (url / file) └─ Figma MCP ──────┘ snapshot │ /Cursor/Codex│
645
+ (url / file) ├─ web scrape ─────┤ snapshot │ Cursor/Codex│
646
+ └─ Figma MCP ──────┘ │ Ollama/oMLX │
548
647
  (1 call, agent dumps to cache) └─────────────┘
549
648
  ```
550
649
 
551
650
  - **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).
552
- - **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.
651
+ - **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.
553
652
  - **Local export** (`./file.svg|png`) reads straight from disk — zero network, best for vector/text-heavy frames.
554
653
 
555
654
  The prompt includes:
@@ -560,6 +659,35 @@ The prompt includes:
560
659
  - Lighthouse performance requirements + a single structural self-verification pass
561
660
  - Output file structure
562
661
 
662
+ ## Local models (Ollama, oMLX)
663
+
664
+ Keep designs and code on your machine: `eds block from-design` can run a local model instead of Claude Code / Cursor / Codex.
665
+
666
+ ```bash
667
+ ollama pull qwen2.5-coder:14b && ollama serve # or: omlx start (Apple Silicon)
668
+ eds block from-design "<framer-url>" --name hero --agent ollama
669
+ eds block from-design ./hero.png --name hero --agent ollama --model qwen2.5vl:7b # PNG → vision model
670
+ eds block from-design ./hero.svg --name hero --agent omlx
671
+ ```
672
+
673
+ | Agent | Server (default) | API |
674
+ |---|---|---|
675
+ | `ollama` | `http://127.0.0.1:11434` | native `/api/chat` (so `num_ctx` can be raised) |
676
+ | `omlx` | `http://127.0.0.1:8000/v1` | OpenAI-compatible `/v1/chat/completions` |
677
+ | `local` | `local.baseUrl` | any OpenAI-compatible server (LM Studio, llama.cpp, vLLM…) |
678
+
679
+ - **No tools, so the CLI does the I/O.** A local model can't read files or call MCP. `eds` sends the whole prompt - a scraped page or Figma REST spec embedded, an SVG export inlined, a PNG / JPG attached as an image - and asks for every file in a `=== FILE: … ===` format. It then writes those files itself, **only inside `blocks/<name>/`**. The raw reply is kept in `tmp/eds-cli/local-reply-*.md`.
680
+ - **Figma needs REST or an export.** A Figma link without a token would need MCP, which a local model can't call: set `FIGMA_TOKEN`, or export the frame as PNG/SVG.
681
+ - **Model.** `--model`, else `local.model`, else a coder model the server has (`*coder*`, Codestral/Devstral, Qwen, DeepSeek…). A PNG/JPG design needs a vision model.
682
+ - **Context.** A design prompt is ~20-40k tokens. Ollama is asked for `local.contextTokens` (default **32768**, raised automatically when the prompt needs more); for other servers set the context length on the server. `eds` warns when the prompt won't fit.
683
+ - **Auto-detect.** With no CLI agent installed, `eds` uses a running Ollama or oMLX server. Refine passes work too: the model gets the block's current files and returns the changed ones.
684
+
685
+ ```json
686
+ { "agent": "ollama", "local": { "model": "qwen2.5-coder:14b", "contextTokens": 49152 } }
687
+ ```
688
+
689
+ `local.baseUrl` points at a different host or port, and `local.apiKey` is sent as a bearer token for servers that want one.
690
+
563
691
  ## Configuration
564
692
 
565
693
  Priority: CLI flags → env vars → `.edsrc.json` (project) → `~/.eds/config.json` (global)
@@ -569,10 +697,13 @@ Priority: CLI flags → env vars → `.edsrc.json` (project) → `~/.eds/config.
569
697
  {
570
698
  "agent": "claude",
571
699
  "model": "haiku",
572
- "figma": { "token": "figd_…" }
700
+ "figma": { "token": "figd_…", "restPerMinute": 10 },
701
+ "local": { "model": "qwen2.5-coder:14b", "contextTokens": 49152 }
573
702
  }
574
703
  ```
575
- `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.
704
+ `agent` picks the agent (`claude`, `cursor`, `codex`, `ollama`, `omlx`, `local`); `model` sets the block-generation model (default: fastest / Haiku). `local.*` configures a [local model](#local-models-ollama-omlx). `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.
705
+
706
+ `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.
576
707
 
577
708
  **`.edsrc.json`** (project root, optional):
578
709
  ```json
@@ -591,13 +722,13 @@ Priority: CLI flags → env vars → `.edsrc.json` (project) → `~/.eds/config.
591
722
  |---|---|
592
723
  | `--verbose` | Debug-level output |
593
724
  | `--quiet` | Errors only |
594
- | `--json` | Machine-readable JSON output |
725
+ | `--json` | Machine-readable JSON output (`doctor`, `audit loading`, `audit security`, `block list`) |
595
726
  | `--no-color` | Disable colors (also respects `NO_COLOR` env) |
596
727
 
597
728
  ## Development
598
729
 
599
730
  ```bash
600
- git clone <repo-url> && cd eds-cli
731
+ git clone https://github.com/piotrgodycki/aem-eds-kit.git && cd aem-eds-kit
601
732
  npm install
602
733
  ```
603
734
 
@@ -669,8 +800,9 @@ Tests use fixtures in `tests/fixtures/` - a sample EDS project and recorded Figm
669
800
 
670
801
  ## Roadmap
671
802
 
672
- - **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).
673
- - **Screenshot-to-block** - build a block from a plain image (harder: no structured layout/measurements, so pixel-perfect is tougher).
803
+ - **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).
804
+ - **Migration** - dialogs (Touch UI and Classic UI) map deterministically today; next is HTL → `decorate()` and capturing CSS from the rendered page.
805
+ - **Image-only designs** - a plain PNG/JPG already works (`from-design ./hero.png`, or a vision model with `--agent ollama --model qwen2.5vl:7b`); next is measuring it (no structured layout in a bitmap, so pixel-perfect is harder than from Figma or a scraped page).
674
806
  - **Tokens & sync** - design variables → CSS custom properties, and incremental re-sync of an existing block (`--update`).
675
807
  - **Headless & CI** - a direct MCP client (no agent needed) for CI/CD, plus `eds lighthouse`, `eds rum` and `eds migrate page`.
676
808
 
@@ -682,4 +814,4 @@ Adobe, AEM, Edge Delivery Services and Universal Editor are trademarks of Adobe
682
814
 
683
815
  ## License
684
816
 
685
- MIT - see [LICENSE](LICENSE).
817
+ MIT - see [LICENSE](https://github.com/piotrgodycki/aem-eds-kit/blob/main/LICENSE).