@xds/cli

Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.

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.

bash
npx xds --help
npx xds search button
npx xds component Button
npx xds docs tokens
npx xds docs migration
npx 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 button
Results for "button" (20):
[component] Button
Button triggers an action when clicked. Use it for form submissions…
→ npx xds component Button
[component] IconButton
A button that shows only an icon with no visible text…
→ npx xds component IconButton
[hook] useClickableContainer
Makes a container element clickable while preserving nested…
→ npx xds hook useClickableContainer
[template] Banner — Collapsible
Combine 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

CommandDescription
initInitialize the design system in your project: installs packages, sets up theming, adds AI agent docs
componentList components or print detailed docs, props, usage examples, and source
searchFind components, hooks, docs, and templates in one ranked, cross-domain result set
docsPrint reference documentation (tokens, theme, color, typography, spacing, etc.)
templateInject page or block templates into your project
hookList hooks and print hook documentation
swizzleCopy component source into your project for deep customization
upgradeRun codemods to migrate between versions
theme buildCompile a defineTheme file to production CSS and JS
discoverDiscover external packages and components
gap-reportReport a gap when a component doesn't meet your needs
doctorDiagnose 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 to full.
  • --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.

typescript
import {isError} from '@xds/cli/json';
const result = parseResponse(raw);
if (isError(result)) {
switch (result.code) {
case 'ERR_UNKNOWN_COMPONENT':
// suggest the closest match
break;
case 'ERR_CORE_NOT_FOUND':
// prompt the user to install @xds/core
break;
default:
console.error(result.error);
}
}
Error codes
CodeMeaning
ERR_UNKNOWNGeneric fallback for any error without a more specific code.
ERR_UNKNOWN_COMMANDA top-level command name was not recognized (e.g. xds bogus).
ERR_UNKNOWN_SUBCOMMANDA subcommand under a group was not recognized (e.g. xds theme bogus).
ERR_INVALID_OPTIONAn unknown flag was passed, or --json was used on a command that doesn't support it.
ERR_INVALID_ARGUMENTAn option/argument value was rejected, or required flags were missing.
ERR_MISSING_ARGUMENTA 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_VERSIONThe 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_COMPONENTNo component matched the requested name.
ERR_UNKNOWN_HOOKNo hook matched the requested name.
ERR_UNKNOWN_TOPICNo docs topic matched the requested name.
ERR_UNKNOWN_SECTIONA docs topic exists but the requested section within it does not.
ERR_UNKNOWN_CATEGORYA --category filter value did not match any known category.
ERR_UNKNOWN_TEMPLATENo template matched the requested name.
ERR_UNKNOWN_PACKAGENo package matched the requested name (discover).
ERR_UNKNOWN_AGENTAn unrecognized --agent value was passed (agent docs / init).
ERR_UNKNOWN_FEATUREAn unrecognized --features value was passed to init.
ERR_UNKNOWN_CODEMODA --codemod value did not match any registered codemod (upgrade).
ERR_NOT_FOUNDA discover/lookup query matched nothing in any package.
ERR_NO_DOCA component exists but has no typed .doc.mjs file.
ERR_NO_SHOWCASENo showcase exists for the requested component.
ERR_NO_SOURCENo source file could be located for the component/template.
ERR_INVALID_DOCA component's docs failed validation (malformed .doc.mjs).
ERR_FILE_NOT_FOUNDA required input file did not exist.
ERR_FILE_EXISTSRefused to overwrite an existing file in non-interactive mode.
ERR_PATH_TRAVERSALA path escaped its allowed root, or a name contained traversal markers.
ERR_WRITE_FAILEDWriting output files failed (and was rolled back).
ERR_THEME_INVALIDA theme definition was missing a required property (e.g. name).
ERR_THEME_LOADA theme file could not be loaded / parsed into a defineTheme result.
ERR_TEMPLATE_CONFIGtemplate.get is not configured in xds.config.mjs (fetch-by-id).
ERR_TEMPLATE_GETA configured template.get threw or returned an invalid value.
ERR_VERSION_DETECTThe current @xds/core version could not be detected.
ERR_INVALID_VERSIONA --from/--to value was not a valid semver string.
ERR_DEP_MISSINGA required external dependency (e.g. jscodeshift) is missing.
ERR_GH_CLIGitHub CLI (gh) is not installed or not authenticated.
ERR_GAP_REPORT_FAILEDFiling 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.

bash
xds 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:

typescript
import {component, docs, discover, template, hook, search, XDSError} from '@xds/cli/api';
// Same result as: xds --json component Button
const btn = await component('Button');
btn.type; // 'component.detail'
btn.data.name; // 'Button' (typed as ComponentDoc)
// Same result as: xds --json component --list
const list = await component(undefined, {list: true});
list.data; // Record<string, string[]>
// Same result as: xds --json docs principles
const principles = await docs('principles');
principles.data.title; // 'XDS Principles'
// Same result as: xds --json hook useMediaQuery
const useMediaQuery = await hook('useMediaQuery');
useMediaQuery.data.params; // typed as HookParamDoc[]
// Errors throw XDSError with a stable .code and optional .suggestions
try {
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:

typescript
import {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: ComponentDoc
break;
}
}
// 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:

CommandTypeResponse
xds --json component [--list]component.listComponentListResponse
xds --json component --list --detail compactcomponent.briefComponentBriefResponse
xds --json component --list --detail fullcomponent.fullComponentFullResponse
xds --json component <name>component.detailComponentDetailResponse
xds --json component <name> --propscomponent.detail.propsComponentDetailPropsResponse
xds --json component <name> --sourcecomponent.detail.sourceComponentDetailSourceResponse
xds --json component <name> --showcasecomponent.detail.showcaseComponentDetailShowcaseResponse
xds --json component <name> --blockscomponent.detail.blocksComponentDetailBlocksResponse
xds --json discoverdiscover.listDiscoverListResponse
xds --json discover @scope/namediscover.detailDiscoverDetailResponse
xds --json discover @scope/name/Compdiscover.detail.docDiscoverDetailDocResponse
xds --json discover <search>discover.searchDiscoverSearchResponse
xds --json docsdocs.listDocsListResponse
xds --json docs <topic>docs.detailDocsDetailResponse
xds --json docs <topic> <section>docs.detail.sectionDocsDetailSectionResponse
xds --json template [--list]template.listTemplateListResponse
xds --json template <name>template.showTemplateShowResponse
xds --json template <name> --skeletontemplate.skeletonTemplateSkeletonResponse
xds --json template <name> [path]template.copyTemplateCopyResponse
xds --json hook [--list]hook.listHookListResponse
xds --json hook --list --detail compacthook.briefHookBriefResponse
xds --json hook --list --detail fullhook.fullHookFullResponse
xds --json hook <name>hook.detailHookDetailResponse
xds --json hook <name> --paramshook.detail.paramsHookDetailParamsResponse
xds --json search <query>searchSearchResponse
xds --json swizzle [--list]swizzle.listSwizzleListResponse
xds --json swizzle <component>swizzle.copySwizzleCopyResponse
xds --json theme build <file>theme.buildThemeBuildResponse
xds --json upgrade --listupgrade.listUpgradeListResponse
xds --json upgrade [--apply]upgrade.runUpgradeRunResponse
xds --json gap-report --list-categoriesgap-report.categoriesGapReportCategoriesResponse
xds --json gap-report --component X ...gap-report.fileGapReportFileResponse
xds --json doctordoctorDoctorResponse
any errorCLIError
unsupported commandCLIUnsupportedError

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 doctor
xds doctor — diagnosing your setup
✓ Node.js version
Node 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 packages
No @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.mjs
No xds.config.mjs found — using defaults.
ℹ AI agent docs
No agent docs (CLAUDE.md / AGENTS.md / .cursorrules) found.
→ fix: Generate agent docs with `xds init --features agents`.
✓ @xds/core peer dependencies
All peer dependencies satisfied (react, react-dom).
ℹ Package manager
Detected package manager: yarn.
Summary: 4 passed, 1 warning, 0 failures, 3 info
No failures — but review the ⚠ warnings above when you can.
Checks
CheckStatus it can returnWhat it verifies
Node.js versionpass / failRunning Node meets the CLI's minimum
@xds/core installedpass / fail@xds/core is resolvable from the project
Version alignmentpass / warn / infoInstalled @xds/core is in step with @xds/cli
Theme packagespass / warnAn @xds/theme-* package is installed and a theme is wired
xds.config.mjspass / fail / infoConfig (if present) loads cleanly with a valid shape
AI agent docspass / warn / infoAgent docs exist and contain the XDS section markers
Peer dependenciespass / warn / info@xds/core's peer deps (react, …) are installed
Package managerinfoReports 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:

javascript
export default {
templates: {
get: async id => fetchTemplateFromAPI(id),
},
gapReport: {
url: 'https://your-api.com/gaps',
},
};