@astryxdesign/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.
Run it one-off with the scoped package (works whether or not it's installed):
bash
npx @astryxdesign/cli --help
npx @astryxdesign/cli search button
npx @astryxdesign/cli component Button
npx @astryxdesign/cli docs tokens
npx @astryxdesign/cli docs migration
npx @astryxdesign/cli template --list
Once it's a project dependency (npm install -D @astryxdesign/cli), drop the scope and use the shorter astryx — e.g. npx astryx component Button or pnpm exec astryx component Button. Bare astryx resolves to an unrelated npm package until the CLI is installed, so prefer the scoped form above for first-run/one-off use.

Finding things: astryx 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
$ astryx search button
Results for "button" (20):
[component] Button
Button triggers an action when clicked. Use it for form submissions…
→ astryx component Button
[component] IconButton
A button that shows only an icon with no visible text…
→ astryx component IconButton
[hook] useClickableContainer
Makes a container element clickable while preserving nested…
→ astryx hook useClickableContainer
[template] Banner — Collapsible
Combine an action button, dismiss control, and expandable detail area…
→ astryx template BannerCollapsibleContent
(The CLI prints the follow-up commands with your actual runner — npx astryx … when installed, or npx @astryxdesign/cli … when run one-off.)
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

<!-- BEGIN GENERATED: commands -->
CommandDescription
blogRead the Astryx blog from the published feed
buildBuild a page: composition kit for an idea, or the workflow playbook (no args)
componentList components or print component docs
discoverDiscover external packages and components
docsPrint reference docs
doctorDiagnose your XDS setup and report problems with fixes
hookList hooks or print hook docs
initInitialize the design system in your project
layoutGenerate XDS layouts from compressed expressions (XLE/XLO)
searchSearch components, hooks, docs, and templates in one ranked list
swizzleCopy component source for customization
templateInject a page or block template
themeTheme tools — build, export, and manage themes
upgradeRun codemods to migrate between versions
validate-integrationValidate an Astryx integration package (manifest + contributions)
<!-- END GENERATED: commands --> <!-- Generated by scripts/generate-cli-readme.mjs from astryx manifest. Run pnpm -F @astryxdesign/cli readme. -->

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 AstryxError 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 '@astryxdesign/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 @astryxdesign/core
break;
default:
console.error(result.error);
}
}

Error codes

<!-- BEGIN GENERATED: error-codes -->
CodeMeaning
ERR_UNKNOWNFallback for any error without a more specific code.
ERR_UNKNOWN_COMMANDA top-level command name was not recognized (e.g. astryx bogus).
ERR_UNKNOWN_SUBCOMMANDA subcommand under a command group was not recognized (e.g. astryx theme bogus).
ERR_INVALID_OPTIONAn unknown flag/option was passed (Commander unknownOption).
ERR_INVALID_ARGUMENTAn option/argument had a value Commander's parser rejected.
ERR_MISSING_ARGUMENTA required positional argument was omitted (Commander missingArgument).
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@astryxdesign/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_AMBIGUOUS_TEMPLATEA template id matched more than one template (narrow with --type/--package).
ERR_AMBIGUOUS_COMPONENTA component name is owned by more than one package (narrow with --package).
ERR_UNKNOWN_THEMENo theme matched the requested slug (theme add).
ERR_UNKNOWN_PACKAGENo package matched the requested name (discover).
ERR_UNKNOWN_AGENTAn unrecognized --agent value was passed to 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_CODEMOD_FAILEDOne or more codemods failed during an upgrade run.
ERR_NOT_FOUNDA generic 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 requested 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_VERSION_DETECTThe current @astryxdesign/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_UNKNOWN_POSTNo blog post matched the requested slug in the feed.
ERR_FETCH_FAILEDA network fetch (RSS feed or post text) failed.
ERR_LAYOUT_PARSEA layout expression failed to parse (syntax error, with line/col).
ERR_LAYOUT_INVALIDA layout expression parsed but failed validation (unknown component/prop/enum/block).
<!-- END GENERATED: error-codes --> <!-- Generated by scripts/generate-cli-readme.mjs from the error-codes EnumDoc (== ERROR_CODES). Run pnpm -F @astryxdesign/cli readme. -->

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
astryx manifest --json # dedicated surface — type: "manifest"
astryx --json # bare invocation — embeds the same payload under data.manifest
Shape:
jsonc
{
"apiVersion": 1,
"type": "manifest",
"data": {
"name": "astryx",
"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": ["astryx component Button --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 astryx --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 astryx manifest --json.

Programmatic API

The same logic that powers astryx --json is available as importable, type-safe functions:
typescript
import {
component,
docs,
discover,
template,
hook,
search,
AstryxError,
} from '@astryxdesign/cli/api';
// Same result as: astryx --json component Button
const btn = await component('Button');
btn.type; // 'component.detail'
btn.data.name; // 'Button' (typed as ComponentDoc)
// Same result as: astryx --json component --list
const list = await component(undefined, {list: true});
list.data; // Record<string, string[]>
// Same result as: astryx --json docs principles
const principles = await docs('principles');
principles.data.title; // 'Principles'
// Same result as: astryx --json hook useMediaQuery
const useMediaQuery = await hook('useMediaQuery');
useMediaQuery.data.params; // typed as HookParamDoc[]
// Errors throw AstryxError 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 @astryxdesign/cli/api and astryx --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} from '@astryxdesign/cli/json';
import type {
ComponentDetailResponse,
ComponentListResponse,
DocsListResponse,
// ...import the response types for the commands you consume
} from '@astryxdesign/cli/json';
// parseResponse returns the structural { type, data, meta? } envelope; `data`
// is `unknown` until you narrow it. Reconstruct the union you care about from
// the per-command response types, then narrow on `type`:
type MyResponse =
ComponentDetailResponse | ComponentListResponse | DocsListResponse;
const result = parseResponse(stdout);
if (isError(result)) {
console.error(result.error);
} else {
const r = result as MyResponse;
switch (r.type) {
case 'component.detail':
r.data.name; // narrowed to ComponentDoc
break;
}
}
Prefer narrowing at the call site? Wrap assertResponse (which throws on error/mismatch) with your reconstructed union:
typescript
import {assertResponse} from '@astryxdesign/cli/json';
import type {ComponentDetailResponse} from '@astryxdesign/cli/json';
type MyResponse = ComponentDetailResponse; /* | ...others */
function assertTyped<T extends MyResponse['type']>(raw: unknown, type: T) {
return assertResponse(raw, type) as Extract<MyResponse, {type: T}>;
}
const detail = assertTyped(stdout, 'component.detail');
detail.data.name; // narrowed
Migration (removed in the structural-jsonOut release): the central CLIAnyResponse, CLIResponseType, and CLIResponseDataMap exports were removed. parseResponse / assertResponse no longer auto-narrow .data. Rebuild the union from the individual *Response types as shown above — they are all still exported from @astryxdesign/cli/json.

Type discriminators

Every response has a type discriminant. The full set is below (generated from the manifest). Each command's types are also listed in astryx manifest --json, and the matching *Response TypeScript types (e.g. ComponentDetailResponse) are exported from @astryxdesign/cli/json. Errors use CLIError, and unsupported commands use CLIUnsupportedError.
<!-- BEGIN GENERATED: response-types -->
TypeWhat data carries
component.listThe component catalog grouped by category: detail (the level — names | compact | full) and components, the grouped map of names+package, brief entries, or a full ComponentDoc per entry.
component.detailOne component's authored ComponentDoc plus ownership metadata (owner package, import specifier, and whether source is available).
component.detail.propsJust one component's props table (ComponentPropDoc[]).
component.detail.sourceOne component's source file, as {component, source}.
component.detail.showcaseOne component's showcase example, as {component, aspectRatio, source}.
component.detail.blocksOne component's example blocks, as {component, showcase, examples, related} of BlockEntry.
docs.listAll reference-doc topics as DocsListEntry[] ({topic, description}), in discovery order.
docs.detailOne topic's full ReferenceDoc, with token-ref blocks inlined.
docs.detail.sectionA single ReferenceSection of a topic — the first whose title contains the section query.
blog.listThe feed URL plus every post parsed from the RSS feed — each with slug, title, description, date, type, authors, link, and plaintext URL.
blog.detailOne post's metadata plus the feed URL and the post's full plaintext body.
discover.listThe configured external packages (name, category, components, version, description); when empty it carries meta.configured to tell "nothing configured" from "nothing discovered".
discover.detailA single external package entry, for an @scope/name query.
discover.detail.docThe validated ComponentDoc for one external component — an @scope/name/Component query, or a free-text term resolving to exactly one component.
discover.searchThe echoed query plus the matching {package, component} pairs, when a free-text term matches several components.
searchThe echoed query plus a ranked SearchResultEntry[] (domain, name, score, reason, description, follow-up command, and import path where relevant).
build.helpA marker (playbook: true) that the renderer expands into the how-to-build-a-page workflow; emitted when no query is given.
build.kitThe grouped composition kit: echoed query, hasResults/directMatch flags, the closest page templates, drop-in block patterns, idea-specific components/hooks, and the always-on frame + foundation component-name arrays.
swizzle.listThe names of swizzlable components discoverable from cwd's @astryxdesign/core.
swizzle.copyAn eject receipt: component name, owning package, output directory, files-copied count, the written file names, whether any file uses StyleX, and an optional maintainer note.
template.listEvery discovered template (page + block); each entry carries id, name, description, kind, owning package, optional category and componentsUsed, and readiness flags.
template.showThe resolved template's raw source plus its description, kind, and the component names it composes.
template.skeletonA layout skeleton (structural tags with spatial annotations) plus the template's description and the components it composes.
template.copyA scaffold receipt: template id, output directory, written file name, and file count.
hook.listThe hook catalog grouped by category: detail (the level — names | compact | full) and components, the grouped map of hook names, brief entries, or a full HookDoc per entry.
hook.detailOne hook's full authored HookDoc.
hook.detail.paramsJust one hook's parameters table (HookParamDoc[]).
theme.buildA theme build receipt: name, token- and component-override counts, output size, the written outputs {css, js, dts, and variantsDts when applicable}, and any validation warnings.
theme.build.checkThe --check receipt: theme name, an upToDate flag, the stale outputs (each {path, reason: missing | outdated}), and the full list of checked paths. Writes nothing.
theme.listEvery bundled theme as a ThemeListEntry[] — each with slug, displayName, description, and a maintained flag.
theme.addA scaffold receipt: resolved slug, displayName, maintained flag, outputDir (relative to cwd), the theme entry file, its exportName, and the files written.
upgrade.listEvery available codemod, oldest→newest, as {name, title, version, optional}; returned for --list without running anything.
upgrade.statusA short-circuit outcome with no codemods run — up_to_date, no_codemods, or config_fixable — each carrying the agent-docs summary.
upgrade.runThe run receipt: from/to versions, codemod count, integrations processed, the agent-docs summary, and (apply mode) filesChanged, transformsApplied, and per-codemod errors.
manifestThe self-describing CLI capability manifest: name, version, apiVersion, global options, the command tree (args, options, json flag, response types, examples), the jsonSupported allowlist, and the flat responseTypes index.
doctorThe health-check report: checks (each with id, label, status: pass | warn | fail | info, a message, and a fix when not passing) plus a summary of counts per status.
integration.validateThe validation result: the package name and version (both null when no local manifest is found) plus issues, an AstryxIntegrationIssue[] of {code, severity: warning | error, message}.
layout.expandThe expansion: parsed form, generated TSX code, componentsUsed, states (count of useState hooks scaffolded), todos, blocksReferenced (each {name, mode}), warnings, and written (the output path, or null when nothing was written).
layout.checkThe validation result: a valid flag, the detected form, errors (each with line/col, message, formatted text, and suggestions), warnings, and the expression re-printed in both canonical surfaces (compact and outline).
layout.grammarThe XLE/XLO grammar cheatsheet: a text field with the full reference plus an aliases map (short name → canonical component) generated from this install's registry.
<!-- END GENERATED: response-types --> <!-- Generated by scripts/generate-cli-readme.mjs from the response-types EnumDoc. Run pnpm -F @astryxdesign/cli readme. -->

Doctor

astryx 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.
$ astryx doctor
astryx doctor — diagnosing your setup
✓ Node.js version
Node v22.13.0 meets the minimum (>=22.13.0).
✓ @astryxdesign/core installed
@astryxdesign/core resolved (v0.0.14).
✓ @astryxdesign/core <-> @astryxdesign/cli alignment
@astryxdesign/core v0.0.14 is in step with @astryxdesign/cli v0.0.14.
⚠ Theme packages
No @astryxdesign/theme-* packages are installed.
→ fix: Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set astryx.theme.
ℹ astryx.config.mjs
No astryx.config.mjs found — using defaults.
ℹ AI agent docs
No agent docs (CLAUDE.md / AGENTS.md / .cursorrules) found.
→ fix: Generate agent docs with `astryx init --features agents`.
✓ @astryxdesign/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
@astryxdesign/core installedpass / fail@astryxdesign/core is resolvable from the project
Version alignmentpass / warn / infoInstalled @astryxdesign/core is in step with @astryxdesign/cli
Theme packagespass / warnAn @astryxdesign/theme-* package is installed and a theme is wired
astryx.config.mjspass / fail / infoConfig (if present) loads cleanly with a valid shape
AI agent docspass / warn / infoAgent docs exist and contain the Astryx section markers
Peer dependenciespass / warn / info@astryxdesign/core's peer deps (react, …) are installed
Package managerinfoReports the detected package manager

CI gate

The exit code is the contract: astryx 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 @astryxdesign/cli doctor
Use --json for a structured envelope ({ apiVersion, type: "doctor", data: { checks, summary } }) that AI agents and scripts can parse.

Configuration

The CLI reads an optional astryx.config.{ts,mjs,js} from your project root (a sibling of package.json). Every field is optional; with no config file the CLI runs on defaults.
typescript
export default {
integrations: ['@acme/astryx-widgets'],
issuesUrl: 'https://github.com/your-org/your-repo/issues',
};
There is no factory: write a plain object. For editor autocomplete and type-checking, annotate it with the AstryxConfig type exported from @astryxdesign/cli/authoring.
FieldTypePurpose
integrationsstring[]Integration package names to load (see Integrations).
issuesUrlstringWhere "report an issue" links point for your project. Defaults to the core issue tracker.
hooks.postCodemodPostCodemodHook[]Commands to run after astryx upgrade applies codemods (e.g. reinstall, rebuild, reformat).
experimental.xle.componentsRecord<string, XleComponent>Register app-local components so layout (XLE) expressions can reference them by name. Unstable.
The config is validated against a strict schema when the CLI loads it, so an unknown field is a hard error rather than a silent no-op. astryx doctor reports whether the config loads cleanly.

Core codemod authoring

Core codemods live under packages/cli/assets/codemods/transforms/. Released codemods are grouped by the target package version (v0.3.0, v0.3.1, ...), which is the version that first contains the breaking change.
Do not guess that version in ordinary feature PRs. Add new codemods to packages/cli/assets/codemods/transforms/next/ instead:
  • put transform modules and tests in transforms/next/;
  • maintain transforms/next/index.mjs with the run order for the staged transforms;
  • leave transforms/next/README.md in place; it documents the staging area and is never promoted.
During the Version Packages PR, pnpm version-packages runs scripts/promote-codemod-next.mjs after changeset version. The script copies all staged entries except the README into transforms/v<new-core-version>/, registers that version in packages/cli/assets/codemods/registry.mjs, and clears the promoted files from next.
This mirrors Changesets: feature PRs stage migration work without knowing the future release number; the release PR assigns the exact version.

Integrations

An integration is any npm package that contributes its own components, templates, and upgrade codemods to Astryx. The CLI surfaces them next to core's, through the same commands, so a consumer can astryx component, astryx template, and astryx upgrade across core and every integration uniformly. Use it to ship a first-party add-on, publish a third-party component library, or share an internal design-system package across apps.
The system runs on two files, each with a small typed API:
FileWritten byRole
astryx.config.{ts,mjs,js}ConsumerLists which integration packages to load.
astryx.integration.{ts,mjs,js}AuthorDeclares what a package contributes.
The consumer side is the integrations field of astryx.config. The author side is the integration manifest below.

The integration manifest

A package becomes an integration by exporting a manifest from astryx.integration.{ts,mjs,js} at its root (a sibling of package.json). The manifest points at where each kind of contribution lives; identity (name, version) comes from package.json, not the manifest.
typescript
export default {
components: './components',
templates: './templates',
codemods: './codemods',
issuesUrl: 'https://github.com/acme/widgets/issues',
};
FieldTypePurpose
componentsstringDirectory holding the package's components and their .doc.* files.
templatesstringDirectory holding the package's page/block templates.
codemodsstringDirectory holding upgrade codemods run by astryx upgrade.
issuesUrlstringWhere "report an issue" links for this package's contributions point.
Every field is optional; declare only the roots the package ships. There is no factory: write a plain object, and annotate it with the AstryxIntegration type from @astryxdesign/cli/authoring for editor autocomplete and type-checking.

How it works

Every command loads the consumer's astryx.config, resolves each listed integration's manifest from node_modules, and discovers its contributions. Everything is validated against one strict schema at the load boundary, so the CLI presents core and integration contributions through a single, uniform surface.
Discovery is resilient: a broken or misconfigured integration is skipped with a one-line warning on stderr instead of crashing the CLI, and it never corrupts a --json envelope. To inspect problems, run astryx validate-integration <package> for a detailed report on one package, or astryx doctor for an overall health check.
For the full authoring walkthrough (component doc format, template packaging and exports requirements, and codemod authoring), see the guide:
bash
astryx docs cli-integrations