edscli

@adaptavant/edscli gives you and your coding agent a faster way to work with the Earth Design System. It sets up EDS in your project, checks it against Getting started, and shows how each component is meant to be used.

You spend less time searching the docs and more time building.

Prerequisites​

@adaptavant/edscli is a private package on GitHub Packages, like the other EDS packages. To install it, you need a GitHub personal access token with the read:packages scope. Then give npm access in one of two ways. See Getting started for details.

Option 1: project .npmrc​

Add these lines to the .npmrc next to your package.json, and set the NPM_TOKEN environment variable to your token:

@adaptavant:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}

This applies to that project only. The file holds no secret, so it is safe to commit.

Option 2: scoped login​

Log in once, using your GitHub username and the token as the password:

npm login --scope=@adaptavant --registry=https://npm.pkg.github.com

npm saves the scope and the token to your user .npmrc, so every project on your machine can install @adaptavant packages.

Installation​

Install it globally:

npm install -g @adaptavant/edscli

list, search and view read the registry from the installed @adaptavant/eds-core, so run them in a project that has it installed.

Usage​

edscli <command> [options]
CommandWhat it doesWrites?
initApply the getting-started contract to the projectYes
listEvery component in the installed registryNo
search <query>Find components by name, title or descriptionNo
view <name>How a component is composed, with an exampleNo
icons [query]Every icon and symbol, with its importNo
tokens [group]The classes EDS adds to Tailwind, and the ones it removesNo
infoCheck the project against the getting-started contractNo

Every command accepts:

  • -c, --cwd <dir>: the project to inspect. Defaults to the current directory.
  • --json / --no-json: JSON envelope (default) or human output.

init​

edscli init

Sets up EDS in your project, following Getting started.

Options:

  • --brand <brand>: the brand tokens to use. Defaults to setmore-black. The prompt offers setmore-black, setmore, answer-connect and serviceforge, and accepts any other brand name typed in full.
  • --dry-run: print the plan without writing anything.
  • --no-install: skip the package install and print the command instead.
  • -y, --yes: use the defaults without asking.

It runs these steps in order, and each one first checks whether its result is already in place:

StepWhat it does
.npmrcAppends @adaptavant:registry=https://npm.pkg.github.com. Never rewrites the file
authenticationChecks project and user .npmrc for a GitHub Packages token. It never handles the token itself
dependenciesInstalls the missing EDS packages with the package manager matching the lockfile (pnpm, yarn, bun, npm)
tailwindCreates tailwind.config.ts, or adds createPreset(tokens) and the eds-core content glob to the existing config
postcssCreates postcss.config.js loading tailwindcss. Flags a config that does not load it, or still loads Tailwind v4
cssAdds the v3 @tailwind directives to the stylesheet the app entry imports. Flags any other, or one on v4 syntax
providerWrites eds-provider.tsx (to src/app, app, src or .) wrapping <Root> with brand, translations and font
entryWraps {children} in a layout.tsx, or the .render(...) argument in a Vite or CRA entry, with <EdsProvider>

Tailwind v3 only runs through PostCSS, in Vite and Next alike, so a missing config means a build that passes with no EDS styles. The postcss step writes postcss.config.cjs instead when package.json has no "type": "module", and adds autoprefixer only when the project has it. It waits until Tailwind v3 is installed. When PostCSS is already configured in package.json, a .postcssrc file or vite.config, it writes nothing: the step is satisfied when that config loads tailwindcss, and manual otherwise. A postcss key in package.json is checked before any config file, because Vite and Next read it first. In a monorepo, an app with no config of its own uses the nearest one in a folder above it, so the step checks that one rather than write a config that would replace it. It searches as far as the build tool does: up to the workspace root (pnpm-workspace.yaml, lerna.json or a workspaces key) for Vite, which reads nothing above an app outside a workspace, and all the way up for Next. A tailwindcss that is commented out does not count.

The css step first looks through every .css file in src, app and styles, as info does, and adds nothing when one already has the directives, such as an App.css the app imports. It puts them below any leading @charset, @import, @namespace or @layer statement, which CSS ignores once other rules come first, and keeps the file's line endings. Like the postcss step, it waits until Tailwind v3 is installed. A stylesheet the entry does not import is left as it is, since directives there would change nothing. So is one that already has @tailwind base, or imports tailwindcss/base the way postcss-import setups do. A stylesheet with some directives but not base is manual: every EDS color and shadow is a CSS variable that only base defines, so without it the classes render nothing.

Each step reports one of four statuses:

StatusMarkMeaning
written✓init made the change
satisfied·Already in place. Nothing to do
manual!init could not safely make the change. It prints a snippet
failed×A write or install it tried did not succeed

Two rules hold throughout init:

  1. Recognise or refuse. A file is edited only when its shape matches one init knows. Anything else becomes manual with the exact snippet to paste.
  2. Re-running changes nothing. Running init a second time is a no-op, so it is safe on a project that is already partly set up.

Translations (english) and font (inter) are fixed. Change them in the generated eds-provider.tsx if a project needs something else.

Interactive mode. When both stdin and stdout are a terminal, and neither --yes nor --dry-run is set, init:

  1. Asks for the brand, unless --brand was passed.
  2. Offers to fix each blocking step it can handle, one confirm at a time: run npm login, downgrade Tailwind v4 to v3, rewrite a v4 stylesheet, or replace a v4-only PostCSS config. File rewrites are shown in full before the confirm.
  3. Prints the dry-run plan and asks Apply this?.

If stdin or stdout is not a terminal (CI, a pipe, an agent), init never prompts. --yes alone never runs these fixes; they are always listed as manual steps instead.

init exits 1 only when a step is failed. A manual step is an instruction, not an error.

list​

edscli list [--category <category>]

Lists name, title and categories for every registry entry. An unknown category is an error (ERR_UNKNOWN_CATEGORY) that lists the known ones.

search <query>​

edscli search date --no-json
  date-picker     components
  calendar        components
  system-tagline  messaging
  qr-code         components
  table           components

→ edscli view date-picker
  • --limit <n>: maximum results (default 20, must be a positive integer).

Ranking, highest first (see packages/edscli/src/search.ts):

  1. Exact name match
  2. Name starts with the query
  3. The whole query appears in the name, title or description
  4. Every query word matches a word by equality, prefix, or (for words longer than three characters) a one-letter typo. Matches in the name rank higher.

Ties are sorted by name. A query that matches nothing returns an empty list and exits 0.

view <name>​

edscli view text-input --no-json
text-input
  The TextInput component provides a way for inputting text. This component must be nested within a Field or InlineField.

import { Field, InlineField, TextInput, TextInputPrimitive } from '@adaptavant/eds-core'

Composition
  TextInput is used inside Field or InlineField. The size and label props live on that wrapper, not on TextInput.

Example — Basic Usage
  <Field label="Label">
    <TextInput placeholder="Placeholder" />
  </Field>

Parts
  TextInput
  TextInputPrimitive  focusContainerRef, type, size

Props
  own    focusContainerRef The ref to the wrapper FocusContainer element.  Ref<HTMLDivElement> | undefined
  own    type           The type of the input.  "text" | "email" | "tel" | "url" | "search" | "password" | undefined
  ...
  html   onChange       ChangeEventHandler<HTMLInputElement, HTMLInputElement> | undefined
  html   placeholder    string | undefined
  ...

Withheld: examples (Size, Controlled, ...), 55 html/style props — edscli view text-input --dense
@adaptavant/eds-core@1.48.0

By default view shows the first example, the own props, and the html props that the component's own examples pass, such as onChange above. Each part lists the names of its props on one line. view names whatever it left out on the Withheld: line.

  • --dense: return everything in the registry item, including all examples, html and style props, each part's full props, and categories. The classNames and styles rows name the slots they accept, for example slots center, rail, railEnd, railStart for MenuItem, where the type only says TrackParts.

The import line puts the wrappers the component requires (here Field, InlineField) ahead of its own parts. A bare <TextInput /> with no wrapper is the usage the registry exists to prevent (the "Gate"). So an entry with no composition and no example is an error (ERR_REGISTRY_INCOMPLETE), never a props-only answer.

An unknown name returns suggestions:

{
  "error": "No component named \"txt-input\"",
  "code": "ERR_UNKNOWN_COMPONENT",
  "suggestions": [
    { "name": "text-input", "reason": "similar name" },
    { "name": "otp-input", "reason": "similar name" }
  ]
}

icons [query]​

edscli icons arrow left --no-json
Icons (1)
  ArrowLeftIcon

import { ArrowLeftIcon } from '@adaptavant/eds-icons/icons'
Needs @adaptavant/eds-icons in your own dependencies.

@adaptavant/eds-core@1.48.0

Lists every icon from @adaptavant/eds-icons/icons and every symbol from @adaptavant/eds-icons/symbols, the entry points the ESLint plugin asks for. Install @adaptavant/eds-icons in your project to import them. The query matches any part of the name, ignoring case and spaces, so code finds CodeIcon and QrCodeIcon, and it needs no quotes. A query that matches nothing exits 0.

tokens [group]​

edscli tokens font --no-json
font
  font-regular
  font-strong
  font-stronger

Not in EDS
  fontWeight  9 removed, e.g. font-thin, font-extralight  → font-regular, font-strong, font-stronger

With no group, tokens lists every group with its class count, and every Tailwind utility EDS removes with what to use instead. Name a group, such as text, bg or rounded, to list its classes. The list is read from Tailwind with the EDS preset, so it covers only the classes EDS adds: classes vanilla Tailwind has too, like flex or p-4, work as usual and are not listed.

EDS removes Tailwind's default radii, font sizes, font weights and shadows, and its default colors for text, backgrounds, borders, rings, dividers, fills and strokes. For example, text-sm becomes text-body-* or text-heading-*, font-bold becomes font-strong, and shadow-md becomes shadow-10 to shadow-50. This describes createPreset(tokens) with its default options, which is what edscli init writes. The shouldUseTailwindColors, shouldUseTailwindFontSize, shouldUseTailwindFontWeight, shouldUseTailwindBoxShadows and shouldUseTailwindBorderRadius options keep those Tailwind classes alongside the EDS ones. An unknown group is an error (ERR_UNKNOWN_TOKEN_GROUP) that suggests the closest one.

icons and tokens need an eds-core that ships icons.json and tokens.json. On an older one they fail with ERR_REGISTRY_OUTDATED, and every other command still works.

info​

A read-only check of the project against the getting-started contract:

Not ready.

  pass  @adaptavant/eds-core 1.47.1
  pass  @adaptavant/eds-brands 1.12.0
  pass  @adaptavant/eds-fonts 1.1.0
  pass  @adaptavant/eds-translations 1.3.0
  pass  tailwindcss 3.4.6
  pass  EDS Tailwind preset
  pass  PostCSS loads tailwindcss
  pass  @tailwind directives
  pass  <Root> mounted
  miss  .npmrc @adaptavant scope

Fix
  Neither .npmrc nor ~/.npmrc has the @adaptavant registry scope.
    → Add `@adaptavant:registry=https://npm.pkg.github.com` to .npmrc, then authenticate with a GitHub PAT.
CheckHow it is detected
The four EDS packagesInstalled version in node_modules, else the range in package.json
tailwindcssSame lookup. Must be major version 3
EDS Tailwind presetcreatePreset found in ., src or config (three levels deep)
PostCSS configA PostCSS config loads tailwindcss. See below for where it looks
@tailwind directives@tailwind base or @import "tailwindcss/base" in a .css file in src, app or styles
<Root> mounted<Root found in src, app, pages or components
.npmrc scopeThe project .npmrc or ~/.npmrc contains @adaptavant:registry

The PostCSS check reads postcss.config.*, .postcssrc*, the postcss key in package.json, and an inline css.postcss in vite.config. The package.json key alone decides when it is there, because Vite and Next read it first. Otherwise the check passes when any of them loads tailwindcss. With none in the project, it reads the nearest config in a folder above, as far as the build tool searches, which is the one a monorepo app uses. A config that exists but does not load it is a miss, with a fix.

No PostCSS config found, and a miss on the preset, directives or <Root> scans, is reported as not detected (warn), not as absent: a file scan cannot prove a negative. Ready. needs every check to pass, so a working setup info cannot see, such as a css.postcss path in vite.config or the directives in a .scss file, reads Not ready. with a warn and no fix. info exits 0 even when the project is not ready: ready: false is a result, not an error.

JSON output​

Success is written to stdout as { "type": "<command>", "data": { ... } }.

Errors are written to stderr as { "error": string, "code": string, "suggestions": [{ "name", "reason" }] }, and the command exits 1. Agents should branch on code, never on the message. Codes are append-only once published:

CodeWhen
ERR_UNKNOWN_COMPONENTview with a name not in the registry. Includes suggestions
ERR_UNKNOWN_CATEGORYlist --category with a category no entry has
ERR_REGISTRY_INCOMPLETEThe registry entry has no composition and no example (a registry bug)
ERR_CORE_NOT_FOUNDeds-core is not installed, or the installed version ships no registry
ERR_UNKNOWNThe registry exists but could not be read
ERR_REGISTRY_OUTDATEDicons or tokens on an eds-core that predates them
ERR_UNKNOWN_TOKEN_GROUPtokens with a group that does not exist. Includes suggestions

Where the registry comes from​

The registry is generated at build time and packed with eds-core, so an installed version describes exactly that version. Nothing is fetched over the network.

  • packages/eds-core/scripts/generate-registry.ts writes packages/eds-core/registry/dist/index.json and one <name>.json per item, plus icons.json and tokens.json. view serves only the names listed in index.json, so those two are never shown as components. It runs in ci:version, or by hand with pnpm --filter @adaptavant/eds-core generate:registry.
  • Hand-written curation lives in packages/eds-core/registry/<name>.json and is deep-merged over the generated data, with curation winning.
  • edscli finds eds-core by resolving @adaptavant/eds-core/package.json from --cwd, then reads registry/dist next to it.