@xds/cli
Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.bashnpm install -D @xds/cli
bashnpx xds component --list
The CLI is the primary interface for working with the design system, for humans and machines alike. It provides component documentation, design tokens, page templates, theming tools, and upgrade codemods, all accessible via terminal commands, a typed JSON API, or programmatic imports. AI agents and build tools use the same API that powers the CLI, enabling end-to-end frontend development loops.
bashnpx xds --helpnpx xds search buttonnpx xds component Buttonnpx xds docs tokensnpx xds docs migrationnpx xds template --list
Finding things: xds search
When you don't know whether what you need is a component, a hook, a docs topic, or a template, search across all of them at once. Results are ranked by relevance (name and keyword matches outrank incidental prose mentions, with fuzzy matching for typos) and tagged with their domain plus the follow-up command to run:
bash$ npx xds search buttonResults for "button" (20):[component] ButtonButton triggers an action when clicked. Use it for form submissions…→ npx xds component Button[component] IconButtonA button that shows only an icon with no visible text…→ npx xds component IconButton[hook] useClickableContainerMakes a container element clickable while preserving nested…→ npx xds hook useClickableContainer[template] Banner — CollapsibleCombine an action button, dismiss control, and expandable detail area…→ npx xds template BannerCollapsibleContent
Options:
--type <component|hook|doc|template>: restrict to a single domain--limit <n>: cap the number of results (default 20)--detail: include the import path and the match reason/score--json: typed{ type: 'search', data: { query, results } }envelope
Commands
| Command | Description |
|---|---|
init | Initialize the design system in your project: installs packages, sets up theming, adds AI agent docs |
component | List components or print detailed docs, props, usage examples, and source |
search | Find components, hooks, docs, and templates in one ranked, cross-domain result set |
docs | Print reference documentation (tokens, theme, color, typography, spacing, etc.) |
template | Inject page or block templates into your project |
hook | List hooks and print hook documentation |
swizzle | Copy component source into your project for deep customization |
upgrade | Run codemods to migrate between versions |
theme build | Compile a defineTheme file to production CSS and JS |
discover | Discover external packages and components |
gap-report | Report a gap when a component doesn't meet your needs |
doctor | Diagnose your XDS setup and report problems with fixes (CI-friendly via exit code) |
Global options
These flags work with any command:
--json: Output as typed JSON envelope:{ type, data }(errors:{ error, code, suggestions? })--detail <level>: Detail level for list views, increasing in size:brief(names only, default for--list) <compact(names + 1-line descriptions) <full(full docs per entry). Single-item views default tofull.--zh: Output docs in Chinese Simplified--dense: Compressed format (token-efficient, useful for AI agents)--lang <locale>: Language/format shorthand (en,zh,dense)
JSON API
Every command supports --json for machine-readable output. Responses are typed envelopes:
json{"type": "component.detail", "data": {"name": "Button", ...}}
Errors:
json{"error": "No component named \"Buttn\"","code": "ERR_UNKNOWN_COMPONENT","suggestions": [{"name": "Button", "reason": "similar name"}]}
The code field is a stable, machine-readable identifier. Branch on it,
never on the human-readable error string, which changes freely as we improve
wording. Every error envelope carries a code (falling back to ERR_UNKNOWN
when no more specific code applies). The same code is exposed on thrown
XDSError instances from the programmatic API, so both surfaces agree.
Codes are append-only: once shipped, a code's meaning never changes and a code is never removed. New error conditions get new codes.
typescriptimport {isError} from '@xds/cli/json';const result = parseResponse(raw);if (isError(result)) {switch (result.code) {case 'ERR_UNKNOWN_COMPONENT':// suggest the closest matchbreak;case 'ERR_CORE_NOT_FOUND':// prompt the user to install @xds/corebreak;default:console.error(result.error);}}
Error codes
| Code | Meaning |
|---|---|
ERR_UNKNOWN | Generic fallback for any error without a more specific code. |
ERR_UNKNOWN_COMMAND | A top-level command name was not recognized (e.g. xds bogus). |
ERR_UNKNOWN_SUBCOMMAND | A subcommand under a group was not recognized (e.g. xds theme bogus). |
ERR_INVALID_OPTION | An unknown flag was passed, or --json was used on a command that doesn't support it. |
ERR_INVALID_ARGUMENT | An option/argument value was rejected, or required flags were missing. |
ERR_MISSING_ARGUMENT | A required positional argument was omitted (e.g. xds theme build with no file). |
ERR_INVALID_LANG | --lang was given a value outside its choices (en, zh, dense). |
ERR_INVALID_DETAIL | --detail was given a value outside its choices (full, compact, brief). |
ERR_NODE_VERSION | The running Node.js version is below the supported minimum. |
ERR_CORE_NOT_FOUND | @xds/core could not be located (not installed / not in a monorepo). |
ERR_UNKNOWN_COMPONENT | No component matched the requested name. |
ERR_UNKNOWN_HOOK | No hook matched the requested name. |
ERR_UNKNOWN_TOPIC | No docs topic matched the requested name. |
ERR_UNKNOWN_SECTION | A docs topic exists but the requested section within it does not. |
ERR_UNKNOWN_CATEGORY | A --category filter value did not match any known category. |
ERR_UNKNOWN_TEMPLATE | No template matched the requested name. |
ERR_UNKNOWN_PACKAGE | No package matched the requested name (discover). |
ERR_UNKNOWN_AGENT | An unrecognized --agent value was passed (agent docs / init). |
ERR_UNKNOWN_FEATURE | An unrecognized --features value was passed to init. |
ERR_UNKNOWN_CODEMOD | A --codemod value did not match any registered codemod (upgrade). |
ERR_NOT_FOUND | A discover/lookup query matched nothing in any package. |
ERR_NO_DOC | A component exists but has no typed .doc.mjs file. |
ERR_NO_SHOWCASE | No showcase exists for the requested component. |
ERR_NO_SOURCE | No source file could be located for the component/template. |
ERR_INVALID_DOC | A component's docs failed validation (malformed .doc.mjs). |
ERR_FILE_NOT_FOUND | A required input file did not exist. |
ERR_FILE_EXISTS | Refused to overwrite an existing file in non-interactive mode. |
ERR_PATH_TRAVERSAL | A path escaped its allowed root, or a name contained traversal markers. |
ERR_WRITE_FAILED | Writing output files failed (and was rolled back). |
ERR_THEME_INVALID | A theme definition was missing a required property (e.g. name). |
ERR_THEME_LOAD | A theme file could not be loaded / parsed into a defineTheme result. |
ERR_TEMPLATE_CONFIG | template.get is not configured in xds.config.mjs (fetch-by-id). |
ERR_TEMPLATE_GET | A configured template.get threw or returned an invalid value. |
ERR_VERSION_DETECT | The current @xds/core version could not be detected. |
ERR_INVALID_VERSION | A --from/--to value was not a valid semver string. |
ERR_DEP_MISSING | A required external dependency (e.g. jscodeshift) is missing. |
ERR_GH_CLI | GitHub CLI (gh) is not installed or not authenticated. |
ERR_GAP_REPORT_FAILED | Filing a gap report failed (disabled, or the integration errored). |
Capability manifest (agent discovery)
Agents don't have to scrape --help to learn the CLI. A single call returns a
self-describing manifest: every command, its arguments, flags (with types,
choices, and defaults), whether it supports --json, and the response type
discriminators each command can emit. Think of it as an OpenAPI spec for the CLI.
bashxds manifest --json # dedicated surface — type: "manifest"xds --json # bare invocation — embeds the same payload under data.manifest
Shape:
jsonc{"apiVersion": 1,"type": "manifest","data": {"name": "xds","version": "0.0.14","description": "Design system CLI — components, themes, and tooling","globalOptions": [{"flag": "--json", "type": "boolean", "description": "Output as typed JSON…"},{"flag": "--lang <locale>", "type": "enum", "choices": ["en", "zh", "dense"]},{"flag": "--detail <level>", "type": "enum", "choices": ["full", "compact", "brief"], "default": "full"}],"commands": [{"name": "component","description": "List components or print component docs","arguments": [{"name": "name", "required": false, "variadic": false, "description": ""}],"options": [{"flag": "--props", "type": "boolean", "description": "Print only the props table"}],"json": true,"responseTypes": ["component.list", "component.detail", "component.detail.props", "…"],"examples": ["xds component XDSButton --props --json"]}// …one entry per command; subcommands (e.g. `theme build`) nest under `subcommands`],"jsonSupported": ["component", "docs", "…"],"responseTypes": {"component": ["component.list", "…"], "theme build": ["theme.build"]}}}
The manifest is derived from Commander metadata (commands, arguments, options)
so it can't drift from the real command definitions. The two facts Commander
doesn't track (--json support and emitted response types) are layered on from
the JSON_SUPPORTED allowlist and a small declarative RESPONSE_TYPES map in
src/lib/manifest.mjs, guarded by a drift test (manifest.test.mjs) so adding a
command without describing it fails CI.
Backwards-compat: the bare xds --json envelope keeps type: "help" and its
original shallow fields (name, version, commands as a string[] of names,
jsonSupported); the full structured manifest is additive under data.manifest.
For the standalone manifest envelope (type: "manifest"), use xds manifest --json.
Programmatic API
The same logic that powers xds --json is available as importable, type-safe functions:
typescriptimport {component, docs, discover, template, hook, search, XDSError} from '@xds/cli/api';// Same result as: xds --json component Buttonconst btn = await component('Button');btn.type; // 'component.detail'btn.data.name; // 'Button' (typed as ComponentDoc)// Same result as: xds --json component --listconst list = await component(undefined, {list: true});list.data; // Record<string, string[]>// Same result as: xds --json docs principlesconst principles = await docs('principles');principles.data.title; // 'XDS Principles'// Same result as: xds --json hook useMediaQueryconst useMediaQuery = await hook('useMediaQuery');useMediaQuery.data.params; // typed as HookParamDoc[]// Errors throw XDSError with a stable .code and optional .suggestionstry {await component('Buttn');} catch (e) {e.message; // 'No component named "Buttn"'e.code; // 'ERR_UNKNOWN_COMPONENT' (stable; branch on this)e.suggestions; // [{ name: 'Button', reason: 'similar name' }]}
The CLI command handlers are thin wrappers around these functions: they parse args, call the API, then format the output (JSON or text). This guarantees that @xds/cli/api and xds --json always return identical data.
Consumer utilities
If you're spawning the CLI as a subprocess rather than importing the API directly:
typescriptimport {parseResponse, isError, assertResponse} from '@xds/cli/json';import type {ComponentDetailResponse, CLIResult} from '@xds/cli/json';const result = parseResponse(stdout);if (isError(result)) {console.error(result.error);} else {switch (result.type) {case 'component.detail':result.data.name; // TypeScript: ComponentDocbreak;}}// Or assert directly (throws on error/mismatch):const detail = assertResponse(stdout, 'component.detail');detail.data.name; // already narrowed
Type discriminators
Every response has a type string that uniquely identifies it:
| Command | Type | Response |
|---|---|---|
xds --json component [--list] | component.list | ComponentListResponse |
xds --json component --list --detail compact | component.brief | ComponentBriefResponse |
xds --json component --list --detail full | component.full | ComponentFullResponse |
xds --json component <name> | component.detail | ComponentDetailResponse |
xds --json component <name> --props | component.detail.props | ComponentDetailPropsResponse |
xds --json component <name> --source | component.detail.source | ComponentDetailSourceResponse |
xds --json component <name> --showcase | component.detail.showcase | ComponentDetailShowcaseResponse |
xds --json component <name> --blocks | component.detail.blocks | ComponentDetailBlocksResponse |
xds --json discover | discover.list | DiscoverListResponse |
xds --json discover @scope/name | discover.detail | DiscoverDetailResponse |
xds --json discover @scope/name/Comp | discover.detail.doc | DiscoverDetailDocResponse |
xds --json discover <search> | discover.search | DiscoverSearchResponse |
xds --json docs | docs.list | DocsListResponse |
xds --json docs <topic> | docs.detail | DocsDetailResponse |
xds --json docs <topic> <section> | docs.detail.section | DocsDetailSectionResponse |
xds --json template [--list] | template.list | TemplateListResponse |
xds --json template <name> | template.show | TemplateShowResponse |
xds --json template <name> --skeleton | template.skeleton | TemplateSkeletonResponse |
xds --json template <name> [path] | template.copy | TemplateCopyResponse |
xds --json hook [--list] | hook.list | HookListResponse |
xds --json hook --list --detail compact | hook.brief | HookBriefResponse |
xds --json hook --list --detail full | hook.full | HookFullResponse |
xds --json hook <name> | hook.detail | HookDetailResponse |
xds --json hook <name> --params | hook.detail.params | HookDetailParamsResponse |
xds --json search <query> | search | SearchResponse |
xds --json swizzle [--list] | swizzle.list | SwizzleListResponse |
xds --json swizzle <component> | swizzle.copy | SwizzleCopyResponse |
xds --json theme build <file> | theme.build | ThemeBuildResponse |
xds --json upgrade --list | upgrade.list | UpgradeListResponse |
xds --json upgrade [--apply] | upgrade.run | UpgradeRunResponse |
xds --json gap-report --list-categories | gap-report.categories | GapReportCategoriesResponse |
xds --json gap-report --component X ... | gap-report.file | GapReportFileResponse |
xds --json doctor | doctor | DoctorResponse |
| any error | — | CLIError |
| unsupported command | — | CLIUnsupportedError |
Doctor
xds doctor runs a series of health checks against your project and
environment and reports PASS / WARN / FAIL for each, with an actionable
fix for anything that isn't passing. It's read-only; it never installs or
mutates anything, so it's safe to run anywhere, including CI.
$ xds doctorxds doctor — diagnosing your setup✓ Node.js versionNode v22.13.0 meets the minimum (>=22.13.0).✓ @xds/core installed@xds/core resolved (v0.0.14).✓ @xds/core <-> @xds/cli alignment@xds/core v0.0.14 is in step with @xds/cli v0.0.14.⚠ Theme packagesNo @xds/theme-* packages are installed.→ fix: Install a theme, e.g. `npm install @xds/theme-default`, then import its CSS or set xds.theme.ℹ xds.config.mjsNo xds.config.mjs found — using defaults.ℹ AI agent docsNo agent docs (CLAUDE.md / AGENTS.md / .cursorrules) found.→ fix: Generate agent docs with `xds init --features agents`.✓ @xds/core peer dependenciesAll peer dependencies satisfied (react, react-dom).ℹ Package managerDetected package manager: yarn.Summary: 4 passed, 1 warning, 0 failures, 3 infoNo failures — but review the ⚠ warnings above when you can.
Checks
| Check | Status it can return | What it verifies |
|---|---|---|
| Node.js version | pass / fail | Running Node meets the CLI's minimum |
| @xds/core installed | pass / fail | @xds/core is resolvable from the project |
| Version alignment | pass / warn / info | Installed @xds/core is in step with @xds/cli |
| Theme packages | pass / warn | An @xds/theme-* package is installed and a theme is wired |
| xds.config.mjs | pass / fail / info | Config (if present) loads cleanly with a valid shape |
| AI agent docs | pass / warn / info | Agent docs exist and contain the XDS section markers |
| Peer dependencies | pass / warn / info | @xds/core's peer deps (react, …) are installed |
| Package manager | info | Reports the detected package manager |
CI gate
The exit code is the contract: xds doctor exits 0 when there are no
failures (warnings are fine) and 1 when any check fails. That makes it
usable directly as a CI step:
yaml- run: npx xds doctor
Use --json for a structured envelope ({ apiVersion, type: "doctor",
data: { checks, summary } }) that AI agents and scripts can parse.
Configuration
The CLI reads from an optional xds.config.mjs in your project root:
javascriptexport default {templates: {get: async id => fetchTemplateFromAPI(id),},gapReport: {url: 'https://your-api.com/gaps',},};