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]
| Command | What it does | Writes? |
|---|---|---|
init | Apply the getting-started contract to the project | Yes |
list | Every component in the installed registry | No |
search <query> | Find components by name, title or description | No |
view <name> | How a component is composed, with an example | No |
icons [query] | Every icon and symbol, with its import | No |
tokens [group] | The classes EDS adds to Tailwind, and the ones it removes | No |
info | Check the project against the getting-started contract | No |
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 tosetmore-black. The prompt offerssetmore-black,setmore,answer-connectandserviceforge, 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:
| Step | What it does |
|---|---|
.npmrc | Appends @adaptavant:registry=https://npm.pkg.github.com. Never rewrites the file |
authentication | Checks project and user .npmrc for a GitHub Packages token. It never handles the token itself |
dependencies | Installs the missing EDS packages with the package manager matching the lockfile (pnpm, yarn, bun, npm) |
tailwind | Creates tailwind.config.ts, or adds createPreset(tokens) and the eds-core content glob to the existing config |
postcss | Creates postcss.config.js loading tailwindcss. Flags a config that does not load it, or still loads Tailwind v4 |
css | Adds the v3 @tailwind directives to the stylesheet the app entry imports. Flags any other, or one on v4 syntax |
provider | Writes eds-provider.tsx (to src/app, app, src or .) wrapping <Root> with brand, translations and font |
entry | Wraps {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:
| Status | Mark | Meaning |
|---|---|---|
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:
- Recognise or refuse. A file is edited only when its shape matches one
initknows. Anything else becomesmanualwith the exact snippet to paste. - Re-running changes nothing. Running
inita 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:
- Asks for the brand, unless
--brandwas passed. - 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. - 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 (default20, must be a positive integer).
Ranking, highest first (see packages/edscli/src/search.ts):
- Exact name match
- Name starts with the query
- The whole query appears in the name, title or description
- 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,htmlandstyleprops, each part's full props, and categories. TheclassNamesandstylesrows name the slots they accept, for exampleslots center, rail, railEnd, railStartforMenuItem, where the type only saysTrackParts.
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.
| Check | How it is detected |
|---|---|
| The four EDS packages | Installed version in node_modules, else the range in package.json |
tailwindcss | Same lookup. Must be major version 3 |
| EDS Tailwind preset | createPreset found in ., src or config (three levels deep) |
| PostCSS config | A 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 scope | The 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:
| Code | When |
|---|---|
ERR_UNKNOWN_COMPONENT | view with a name not in the registry. Includes suggestions |
ERR_UNKNOWN_CATEGORY | list --category with a category no entry has |
ERR_REGISTRY_INCOMPLETE | The registry entry has no composition and no example (a registry bug) |
ERR_CORE_NOT_FOUND | eds-core is not installed, or the installed version ships no registry |
ERR_UNKNOWN | The registry exists but could not be read |
ERR_REGISTRY_OUTDATED | icons or tokens on an eds-core that predates them |
ERR_UNKNOWN_TOKEN_GROUP | tokens 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.tswritespackages/eds-core/registry/dist/index.jsonand one<name>.jsonper item, plusicons.jsonandtokens.json.viewserves only the names listed inindex.json, so those two are never shown as components. It runs inci:version, or by hand withpnpm --filter @adaptavant/eds-core generate:registry.- Hand-written curation lives in
packages/eds-core/registry/<name>.jsonand is deep-merged over the generated data, with curation winning. edsclifinds eds-core by resolving@adaptavant/eds-core/package.jsonfrom--cwd, then readsregistry/distnext to it.