npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

aem-eds-kit

v0.4.48

Published

CLI for AEM Edge Delivery Services: blocks from Figma, Framer or Google Stitch designs, AEM component migration (AEMaaCS and 6.5), Universal Editor models and QA

Readme

aem-eds-kit

npm version npm downloads CI license node

Docs · Changelog · End-to-end test · GitHub · Issues

Turn a design into an EDS block - JS, CSS and a Universal Editor model - without leaving your terminal.

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.

It lives next to @adobe/aem-cli (your local dev server) and handles everything around it.

What it does

  • 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().
  • 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.
  • 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.
  • 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.
  • 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.

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.

Install

npm install -g aem-eds-kit
Goal:
Ensure Node.js v20 or newer is installed, then install the aem-eds-kit CLI and verify it.

Check first: run `node --version`. If the major version is 20 or higher, do not install or modify Node.js.
If Node.js is missing or older than v20, install an official Node.js release (major version 20 or higher).

Install policy: use an existing package manager only if it is already installed. Do not install Homebrew,
winget, Chocolatey, Scoop, nvm, fnm, or another package manager just for this task.
- macOS: Homebrew if already installed, otherwise the official Node.js `.pkg` installer.
- Windows: winget if already installed, otherwise the official Node.js `.msi` installer.
- Linux: the system package manager if it can install v20+, otherwise NodeSource (Debian/Ubuntu, RHEL/Fedora)
  or the official Node.js standalone Linux binary. Downloads: https://nodejs.org/download/release/latest/

Verify Node: run `node --version` again; only continue if the major version is 20 or higher.
Install the CLI: run `npm install -g aem-eds-kit`. Verify: run `eds --version`.

Final step: from inside an EDS project (a folder with fstab.yaml, head.html, or scripts/scripts.js), run `eds`
for the interactive menu, or `eds init` to set up a freshly cloned Adobe EDS boilerplate.

Node.js 20 or newer. That's all the setup there is - eds now works in any folder. Step into your EDS project (anywhere with a fstab.yaml) and the CLI finds the project root for you:

cd ~/my-eds-site
eds figma setup                              # one-time: connect your AI agent to Figma
eds block from-design "<figma-url>" --name hero

eds runs on your machine, not on your site - it never adds a single byte to what your visitors download.

Try it in 30 seconds (no Figma account, no token)

From inside an EDS project (or spin a throwaway one up with eds sandbox new demo):

eds block from-design --sample --name hero --dry-run   # see the prompt - zero setup
eds block from-design --sample --name hero             # generate it with your AI agent
eds block preview hero                                  # see it live in the browser

--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.)

Ready for the real thing? Point it at the official Kiri Bloom AEM EDS Kit training file in Figma:

eds block from-design \
  "https://www.figma.com/design/vwlEKTFhcrlwuA8dRgN5SD/Kiri-Bloom-AEM-EDS-Kit-Example?node-id=0-1" \
  --name hero

Interactive mode

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.

A full A-Z reference of every command lives on the docs page: https://piotrgodycki.github.io/aem-eds-kit/docs.html

Quick start

eds                              # interactive menu

# Just cloned an EDS boilerplate? Set it up for your authoring model
eds init

# One-time: configure Figma MCP for your AI agent
eds figma setup

# Generate a block from a Figma design (wizard, or pass a URL)
eds block from-design

# Scaffold the standard UE components + blocks
eds scaffold ue
eds scaffold blocks

# Add a third-party service (GTM, GA4, chat…)
eds integrate

# QA a block before you ship it (visual regression + content/a11y)
eds block test hero
eds block check hero

# Audit your project
eds doctor

From zero to a published page

The full path, command by command. This example targets DA (da.live) authoring.

# 1. Install the CLI
npm i -g aem-eds-kit

# 2. Set up the project (pick DA, enter org + site). Writes fstab + CI + naming.
eds init --authoring da --org my-org --site my-site

# 3. Scaffold the Universal Editor config + helper functions
eds scaffold ue
eds scaffold helpers

# 4. Build blocks - from a design, from scratch, or migrated from classic AEM
eds block from-design            # paste a Figma URL, a Framer/Stitch page, or a local SVG
# eds block create hero
# eds migrate component ./components/hero

# 5. Enrich a block (optional)
eds model add hero teaser        # add reusable field-group partials
eds schema block hero --type Article   # JSON-LD structured data
eds track block hero             # dataLayer click + form-submit tracking

# 6. Generate the page (initial content) and push it to DA
eds template new homepage --blocks hero,cards --push --org my-org --site my-site

# 7. Preview, then publish
eds preview /templates/homepage
eds publish /templates/homepage

Every step is also interactive when you omit the flags - run eds for the menu.

Commands

eds init

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.

By default it also writes, out of the box:

  • a local-dev .env for aem up (AEM_OPEN=/, AEM_PORT=3007, AEM_PAGES_URL prefilled from your git remote) - never overwritten, always gitignored;
  • for Universal Editor projects, an xwalk.json enabling UE multi-field support.

Opt out with --no-env / --no-xwalk, or (re)generate either later with eds scaffold env / eds scaffold xwalk.

eds init                                           # interactive
eds init --name my-site --authoring da --org my-org --site my-site --yes

| Flag | Description | |---|---| | --name <name> | Project name (kebab-case) | | --authoring <model> | ue | da | gdrive | sharepoint | | --mountpoint <url> | Content source URL (document models) | | --org <org> / --site <site> | DA org / site (--authoring da) | | --no-ci | Skip the GitHub Actions workflow | | --no-env | Skip the local-dev .env (aem up config) | | --no-xwalk | Skip xwalk.json (UE multi-field) | | --yes | Skip prompts; apply with flags/defaults |

eds template new <name>

Generate EDS page initial content - the EDS analog of an AEM template. In the wizard you compose the blocks in order (add them one by one, repeats allowed - you control the sequence and count). It always writes templates/<name>.html, then routes by your authoring model (detected from fstab.yaml / project markers, or --authoring): DA → push to da.live (--push, org/site from .edsrc.json/flags, DA_TOKEN for protected projects); Google Docs / SharePoint → importable doc + the external steps; Universal Editor → writes templates/<name>.json (the page's initial content: an ordered list of components with default field values from each block's model) and registers the blocks so authors can add them. Interactive when flags are omitted; --blocks hero,cards,cards keeps order and repeats non-interactively.

eds template new homepage                         # interactive (blocks, DA push)
eds template new homepage --blocks hero,cards --push --org my-org --site my-site

| Flag | Description | |---|---| | --blocks <list> | Comma-separated block ids to seed | | --title / --description / --area | Page title, description, metadata label | | --path <path> | Content path (default /templates/<name>) | | --authoring <model> | Force da | gdrive | sharepoint | ue (else detected) | | --push | Push to DA (da.live) | | --org / --site | DA org / site (else from .edsrc.json) | | --yes | Skip prompts |

eds sandbox

Spin up and tear down disposable EDS sandbox repos on GitHub - a rapid, throwaway environment you can stand up and delete in seconds, straight from an empty folder.

Authorize GitHub once - no gh required: eds sandbox login runs the OAuth Device Flow (open github.com/login/device, enter the code) when an OAuth app is configured (EDS_GITHUB_CLIENT_ID), otherwise it accepts a Personal Access Token (scopes repo, delete_repo). A token in GITHUB_TOKEN/GH_TOKEN works too, and an authenticated gh is used as a fallback.

eds sandbox login                # authorize GitHub (device flow or token)
eds sandbox new my-test          # guided: boilerplate, visibility, Code Sync, eds init
eds sandbox new my-test --ue --private --yes
eds sandbox list                 # your sandboxes (tagged eds-sandbox) + preview URLs
eds sandbox rm my-test           # delete it (with confirmation)

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/.

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:

  • 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).
  • 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.

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.

eds migrate component [dir]

Start migrating a classic AEM component to EDS. Phase 1 is deterministic (no agent, no network): it parses the component's Touch UI dialog (_cq_dialog/.content.xml) and maps its Granite/Coral fields onto the 17 Universal Editor field types - selects keep their options, multifields become repeatable containers, tabs are preserved - then writes blocks/<name>/ with a UE model (_<name>.json), a decorate() stub (port the HTL logic), and a scoped CSS file to fill from the component's clientlib or its rendered page.

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>.

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 includes 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 xtypes: textfield, textarea, richtext, pathfield, numberfield, selection, smartimage, multifield (incl. ACS multifieldpanel), tabpanel and cqinclude.

eds migrate component /path/to/apps/myproject/components/hero
eds migrate component ./components/hero --name hero --clientlib ./ui.apps/.../hero/clientlib --yes
eds migrate component ./ui.apps/.../apps/wknd/components/teaser \
  --super-root ../aem-core-wcm-components/content/src/content/jcr_root   # proxy → Core Component dialog

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.

eds track block <name>

Instrument a block's decorate() with dataLayer tracking on demand: a delegated block_click (links/buttons, label from data-track or the element text) and form_submit, both pushed through trackEvent from scripts/analytics.js (created by eds integrate gtm). It adds the import, injects at the top of decorate(), is idempotent, and keeps the real parameter name. You're also offered it right after block from-design and migrate component.

eds track block hero
eds track block hero --no-submit        # clicks only

eds model add <block> [partials...]

Compose reusable field groups (partials) into a block's Universal Editor model (_<block>.json) instead of hand-writing fields. Fields are added by name, so re-running never duplicates. Partials are built from typed field helpers (each sets the right valueType), and the same catalog is referenced in the from-design / migrate prompt so the agent reuses the standard groups.

eds model add hero teaser              # image + eyebrow + title + text + cta
eds model add cards card cta-group     # pick several
eds model add hero                     # interactive checkbox picker

Built-in partials: heading, eyebrow, image, richtext, link, cta, cta-group, classes (section styles), embed, teaser, card.

eds schema block <name>

Inject a JSON-LD (schema.org) structured-data builder into a block's decorate() for SEO / rich results. Pick a type - Article, FAQPage, BreadcrumbList, Product, Organization, LocalBusiness, Event, VideoObject, Recipe, HowTo - and it appends a <script type="application/ld+json"> to the head, built from the block's content (aliased to root, so it's independent of the parameter name). Idempotent; some fields are heuristic or TODO, so review and validate with Google's Rich Results test.

eds schema block faq --type FAQPage
eds schema block article              # interactive type picker

eds ue check · eds ue open [path]

Light Universal Editor helpers - no Docker, no local AEM SDK, no local UE Service to stand up.

eds ue check is an offline readiness report: it confirms the project is UE-ready (component-definition.json + component-models.json + component-filters.json) and lists which blocks are actually editable - a block is editable when it has a model (_<name>.json or a component-models.json entry), so you catch a block that won't appear in the editor before you open it.

eds ue open [path] builds and opens the deep link into the hosted Universal Editor for your cloud author, pointing the editor canvas at your preview host (derived from the git remote as main--<repo>--<owner>.aem.page).

eds ue check
eds ue open /products
eds ue open / --org my-org --ref dev
eds ue open /blog --url main--site--owner.aem.page --no-open

Full workflow (proxy, .env, xwalk.json, editor deep link): docs/local-development.md.

eds block create <name>

Scaffolds blocks/<name>/ with <name>.js, <name>.css, and a Universal Editor model _<name>.json by default - so the block is authorable in UE the moment it's created. Validates kebab-case naming and checks for collisions.

eds block create hero-banner             # js + css + _hero-banner.json (UE model)
eds block create hero-banner --no-ue-model   # skip the UE model

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. eds block from-design picks the right field type per Figma layer automatically.

eds block from-design [design-url]

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.)

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.

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.)

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:

  1. Local file — pass a path to an SVG/PNG export (eds block from-design ./hero.svg) → read from disk, no network.
  2. Cache — a snapshot already fetched for this design (tmp/eds-cli/design-cache/) is reused.
  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.
  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.
  5. Figma MCP — fallback: the agent makes one get_design_context call and caches it.

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).

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).

| | Starter | Professional | Organization | Enterprise | |---|---|---|---|---| | REST (Tier 1: file, nodes, image), Dev / Full seat | 20 / month | 10 / min | 15 / min | 20 / min | | REST, View / Collab seat | 20 / month | 20 / month | 20 / month | 20 / month | | MCP, Dev / Full seat | 20 / month | 200 / day, 10 / min | 200 / day, 15 / min | 600 / day, 20 / min | | MCP, View / Collab seat | 20 / month | 20 / month | 20 / month | 20 / month |

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 and MCP pages.

See How Figma integration works.

eds block from-design                               # interactive wizard
eds block from-design "<figma-url>" --name hero     # non-interactive (REST if a token is set, else MCP)
eds block from-design "<framer-url>" --name hero    # Framer/Stitch → scraped with headless Chromium
eds block from-design "<stitch-url>" --name hero --auth   # gated source → log in once, reuse the session
eds block from-design ./hero.svg --name hero        # from a local export (no network)
eds block from-design --sample --name hero          # bundled Kiri Bloom sample (no token, no MCP)
eds block from-design "<figma-url>" --dry-run       # just build the prompt

| Flag | Description | |---|---| | --name <name> | Override the inferred block name | | --provider <type> | Design source: figma | framer | stitch (else detected from the URL) | | --source <type> | Content source: document | ue | cf | mixed (cf / mixed default to the sonnet model) | | --widths <list> | Design frame widths for preview breakpoints (e.g. 390,768,1440) | | --agent <type> | Force claude | cursor | codex | none (a prompt too large for one argument reaches Cursor / Codex as its file) | | --dry-run | Build the prompt only | | --no-ue-model | Skip the Universal Editor model | | --screenshot | Add a screenshot MCP call for a pixel check (opt-in — costs a call) | | --mcp | Force the Figma MCP adapter even when a REST token is set | | --auth | Scrape with a logged-in session (Framer/Stitch): log in once, reuse it | | --auth-refresh | Force a fresh scrape login even if a session is saved | | --refresh | Ignore the cached snapshot and re-fetch | | --sample | Use the bundled Kiri Bloom sample design (no URL, token or MCP) | | --serve | Auto-start the live preview (non-interactive runs only) | | --yes | Skip confirmation prompts |

Set a Figma Personal Access Token (scope file_content:read) to use the REST path — via FIGMA_TOKEN env or figma.token in config. The block generation model defaults to the fastest (Haiku); override with model in config.

The generated block records the design's frame widths in its .eds-meta.json (breakpoints), so eds block preview shows it at the real design widths rather than a fixed device set. The agent writes the widths it reads from the design; --widths overrides them, and 375/768/1280 is only the fallback.

Supported Figma URL formats:

  • figma.com/design/:fileKey/:name?node-id=...
  • figma.com/file/:fileKey/:name?node-id=... (legacy)
  • figma.com/design/:fileKey/branch/:branchKey/:name (branches)
  • figma.com/board/..., figma.com/make/..., figma.com/slides/...

eds figma setup

Interactive wizard that:

  1. Detects installed AI agents (Claude Code, Cursor, Codex)
  2. Checks if Figma MCP is configured for your agent
  3. Shows install commands if not
  4. Saves preferences to ~/.eds/config.json

eds block list

Lists all blocks in the project with their file status and Figma source info.

eds block list
eds block list --json          # machine-readable output

eds block preview [name]

Serves a live, breakpoint-switchable preview in your browser - no build, no files written to your project (the harness is served from memory). A dropdown switches widths inside a width-honest iframe, and edits live-reload the browser (the server watches the block folder).

Run it with no name for a gallery of every content block, each in its own iframe. Routes are computed per request, so a block generated while the gallery is open appears on the next reload (header/footer are skipped — they're page chrome and can't render standalone).

eds block preview                              # gallery of all blocks
eds block preview hero                         # a single block
eds block preview hero --widths 375,768,1280   # custom breakpoints
eds block preview hero --port 9000 --no-open

eds block test [name] · eds block check [name]

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.

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).

eds block test                         # all blocks vs committed baselines
eds block test hero                    # one block
eds block test hero --update           # (re)write baselines after an intended change
eds block test hero --design           # rendered block vs its Figma design image
eds block test --threshold 0.02        # allow 2% changed pixels

| Flag | Description | |---|---| | --update | (Re)write baseline screenshots instead of comparing | | --threshold <ratio> | Max changed-pixel ratio before failing (default 0.01 = 1%) | | --design | Compare the rendered block against its Figma design image (needs a token) | | --widths <list> | Breakpoint widths (e.g. 375,768,1280) | | --port <port> | Port for the internal preview server (default 8788) |

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 hrefs 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.

eds block check                        # all blocks
eds block check hero                   # one block
eds block check --strict               # fail on warnings too

| Flag | Description | |---|---| | --strict | Fail on warnings too, not just errors | | --widths <list> | Breakpoint widths (the widest is used) | | --port <port> | Port for the internal preview server (default 8789) |

eds scaffold ue · eds scaffold blocks

Scaffold Universal Editor components deterministically (no agent). eds scaffold ue writes/merges the three UE config files with the default-content components and a field-reference block showing all 17 field types + a multifield. eds scaffold blocks scaffolds standard Block Collection blocks (hero, cards, columns, accordion, embed) with UE models. Run eds scaffold for an interactive picker.

eds scaffold ue
eds scaffold blocks            # all, or: eds scaffold blocks hero cards
eds scaffold helpers           # scripts/utils.js with common EDS helpers

eds scaffold helpers writes scripts/utils.js - tick the helpers you want in an interactive checkbox (dependencies are pulled in automatically), or pass names. Available: toCamelCase, getPagePath / getLanguageRootPath, getSiteArea (top-level area like blog), getEnvironment, getContentTopic, isUEEdit / isUEPreview / isUE, and getMetadata.

eds scaffold helpers                    # interactive picker
eds scaffold helpers getSiteArea getEnvironment

eds scaffold ci

Generate a GitHub Actions workflow (.github/workflows/eds.yml) that gatekeeps every PR and push to main: it installs deps, runs the project's own lint/build (if defined), then the eds audits - doctor, audit loading, audit security - which exit non-zero on problems. No agent, deterministic. Also offered in the interactive eds scaffold picker.

eds scaffold ci

eds integrate [type]

Add a third-party service to scripts/delayed.js (the EDS-correct place - delayed phase, after LCP, zero CWV impact). Chat widgets use the facade pattern. GTM gets a scripts/analytics.js module (exports trackEvent / loadGTM, initialises the dataLayer, reads the container id from the gtm-id metadata) plus a scripts/page-meta.js (default-exported pageMetaPush); delayed.js imports both and pushes page metadata before GTM initialises.

eds integrate                  # interactive
eds integrate gtm              # gtm · ga4 · chat · cookiebot · custom
  • 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.
  • 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.
  • If the block has a .eds-meta.json with a Figma source, an Open in Figma link is shown.
  • 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).

Not to be confused with eds preview <paths...> (lower down), which previews pages via the AEM Admin API.

eds preview <path...>

Preview pages via the AEM Admin API (admin.hlx.page).

eds preview /index
eds preview /blog/my-post /about
eds preview /index --org my-org --site my-repo --ref main

eds publish <path...>

Publish pages to live via the AEM Admin API.

eds publish /index
eds publish /blog/my-post /about

Both preview and publish read org, site, and ref from .edsrc.json so you don't need to pass flags every time:

{
  "admin": {
    "org": "my-github-org",
    "site": "my-repo",
    "ref": "main"
  }
}

eds doctor

Audits your EDS project for common issues:

  • fstab.yaml exists and is valid YAML
  • head.html present
  • Every block folder has a matching .js file
  • Every block has a Universal Editor model (_<name>.json or an entry in component-models.json)
  • No console.log in block code
  • helix-query.yaml valid if present
  • Figma-generated blocks: warns if last sync > 30 days
  • CLI config present
eds doctor
eds doctor --json

eds audit loading

Audits the EDS three-phase loading strategy and LCP budget:

  • No render-blocking scripts / extra stylesheets / preload-preconnect / font preloads in head.html
  • scripts.js has loadEager / loadLazy / loadDelayed; eager phase loads only the first section
  • styles.css within the LCP budget; no @import chains
  • No heavy libraries (jquery, gsap, swiper, …) or document.write in blocks
  • Block CSS is scoped to .<block-name> (prevents style leakage / CLS)
eds audit loading
eds audit loading --json

eds audit security

Scans the project for vulnerabilities and exploit patterns - a defensive check you can run before every PR or in CI.

  • Dependency vulnerabilities - runs npm audit and summarizes critical/high/moderate/low (skipped gracefully if there's no package.json/lockfile).
  • Exploit scan - static analysis of blocks/, scripts/, and HTML for:
    • eval(), new Function(), document.write() - code-execution vectors
    • dynamic innerHTML / insertAdjacentHTML / javascript: URLs - XSS
    • hardcoded secrets, AWS keys, private keys, bearer tokens
    • http:// resources (mixed content), target="_blank" without rel="noopener" (reverse tabnabbing)
    • postMessage('*'), inline event handlers, leftover console.log

Findings are grouped by severity (CRIT / HIGH / MOD / LOW). Exits non-zero if any critical or high issue is found - wire it straight into CI.

eds audit security
eds audit security --json      # for CI / tooling

The vendored framework (scripts/aem.js / lib-franklin.js) is never flagged.

How Figma integration works

The design is fetched once into a cached snapshot, then the agent generates from it. Separating the fetch from the generation means retries and refine passes never re-fetch, and the snapshot is inspectable (tmp/eds-cli/design-cache/). eds picks the cheapest adapter available:

                    ┌─ local SVG/PNG ─┐
                    ├─ cache hit ──────┤      design.json      ┌─────────────┐   block files
 design reference ──┼─ Figma REST ─────┼───→  (cached) ───────→│ Claude Code │ ───────────────→ repo
   (url / file)     └─ Figma MCP ──────┘      snapshot          │ /Cursor/Codex│
                        (1 call, agent dumps to cache)          └─────────────┘
  • 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).
  • 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.
  • Local export (./file.svg|png) reads straight from disk — zero network, best for vector/text-heavy frames.

The prompt includes:

  • The design data (embedded spec, local file, or a single-call MCP fetch) for the target node
  • EDS block conventions (decorate(block), vanilla JS, semantic HTML, full-width/mobile-first, self-contained JS)
  • CSS custom properties from your styles/styles.css
  • Universal Editor model generation (fields mapped from design layers/variants) - on by default
  • Lighthouse performance requirements + a single structural self-verification pass
  • Output file structure

Configuration

Priority: CLI flags → env vars → .edsrc.json (project) → ~/.eds/config.json (global)

~/.eds/config.json (created by eds figma setup):

{
  "agent": "claude",
  "model": "haiku",
  "figma": { "token": "figd_…", "restPerMinute": 10 }
}

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.

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.

.edsrc.json (project root, optional):

{
  "admin": {
    "org": "my-org",
    "site": "my-site",
    "ref": "main"
  }
}

Global flags

| Flag | Description | |---|---| | --verbose | Debug-level output | | --quiet | Errors only | | --json | Machine-readable JSON output | | --no-color | Disable colors (also respects NO_COLOR env) |

Development

git clone <repo-url> && cd eds-cli
npm install

Scripts

npm run dev -- --help          # run via tsx, no build needed
npm run build                  # build to dist/
npm test                       # vitest
npm run typecheck              # tsc --noEmit
npm run lint                   # biome check
npm run lint:fix               # biome auto-fix

Testing locally

Option A - global link (recommended):

npm run build && npm link
eds --help                     # available everywhere

After code changes, re-run npm run build to update.

Option B - dev mode (no build):

npm run dev -- doctor
npm run dev -- block create my-block
npm run dev -- block from-design "https://www.figma.com/design/abc/File?node-id=1-2" --dry-run

Option C - run from a test EDS project:

The repo includes a fixture project you can use:

cd tests/fixtures/sample-eds-project

# with global link
eds doctor
eds block create my-block
eds block from-design "https://www.figma.com/design/abc/File?node-id=1-2" --dry-run --name hero

# without link
npx tsx ../../../src/bin/eds.ts doctor

Testing Figma integration (dry-run)

You don't need a real Figma file to test prompt generation:

eds block from-design "https://www.figma.com/design/abc123/Test?node-id=42-100" \
  --dry-run --name hero-banner

This parses the URL, builds the full agent prompt (with project tokens, EDS conventions), saves it to a temp file, and prints it. No agent or Figma access needed.

Running tests

npm test                       # run all tests once
npm run test:watch             # watch mode

Tests use fixtures in tests/fixtures/ - a sample EDS project and recorded Figma MCP responses.

Roadmap

  • 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). Canva/Sketch stay on the local-export path (canvas/image — not scrapeable).
  • Migration - dialogs (Touch UI and Classic UI) map deterministically today; next is HTL → decorate() and capturing CSS from the rendered page.
  • Screenshot-to-block - build a block from a plain image (harder: no structured layout/measurements, so pixel-perfect is tougher).
  • Tokens & sync - design variables → CSS custom properties, and incremental re-sync of an existing block (--update).
  • Headless & CI - a direct MCP client (no agent needed) for CI/CD, plus eds lighthouse, eds rum and eds migrate page.

Trademarks & affiliation

aem-eds-kit is an independent, community project. It is not affiliated with, endorsed by, or sponsored by Adobe or Figma. It ships none of their code - it only generates code that follows the public, open-source AEM Edge Delivery Services conventions and interoperates with tools you install yourself.

Adobe, AEM, Edge Delivery Services and Universal Editor are trademarks of Adobe Inc. Figma is a trademark of Figma, Inc. These names are used only to describe compatibility.

License

MIT - see LICENSE.