Authoring Reference

Every file an integration author writes, field by field: the integration manifest, astryx.config, codemods, identity, and each doc type.

Astryx Integration

The astryx.integration.* manifest that sits beside an integration package's package.json. It can preserve a stable provider identity across a package rename, and points the CLI at the package's components, templates, codemods, doc topics, source themes, managed agent guidance, and issue tracker. Every field is optional.

Applies to: astryx.integration.{ts,mjs,js}

FieldTypeRequiredDescription
providerIdstringnoStable logical provider ID. Omit to use package.json#name; set it to the prior package name only when an explicit rename must preserve artifact IDs. If two packages claim the same ID, the package being authored is used, otherwise the first-loaded one, and the CLI warns about the other. Example: '@acme/widgets'.
componentsstringnoRelative path to the components/docs root (resolved to absolute). Example: './src/components'.
templatesstringnoRelative path to the templates root (resolved to absolute). Example: './src/templates'.
codemodsstringnoRelative path to the codemods root (resolved to absolute). Example: './codemods'.
docsstringnoRelative path to the reference-docs (topics) root (resolved to absolute). Every {topic}.doc.{ts,mjs,js} under it is served by astryx docs beside the built-in topics; a topic may also declare replaces or extends to take the place of a built-in one or merge onto it. Example: './docs'.
themesstringnoRelative path to a source-theme catalog root containing manifest.json plus one directory per theme slug. Installed themes appear in astryx theme list and can be copied with astryx theme add. Example: './themes'.
agentDocs{ append?: readonly string[] }noStatic package guidance appended to the end of the managed agent block. The CLI owns the section heading, package labels, bullets, target files, and writes. Example: { append: ['Run acme verify.'] }.
issuesUrlstringnoWhere to file issues/feedback for this integration. Example: 'https://github.com/acme/widgets/issues'.
Typical
js
export default {
components: './src/components',
templates: './src/templates',
codemods: './codemods',
docs: './docs',
themes: './themes',
agentDocs: {
append: ['Run acme verify before finishing.'],
},
issuesUrl: 'https://github.com/acme/widgets/issues',
};

Provider identity defaults to package.json#name. During an explicit package rename, set providerId to the prior canonical package name so existing artifact IDs remain stable. Package version always comes from package.json.

agentDocs.append may contain at most eight lines. Each line is a trimmed, non-blank string of at most 240 Unicode code points with no line separators, control characters, NUL, or Astryx/XDS managed-marker text. The configured project may contain at most 32 integration lines total.

A themes root is forward-compatible but version-gated: a CLI released before this field ignores it with a warning and continues loading every contribution kind it understands. That older CLI cannot list or add the contributed themes.

Validate the manifest with astryx doctor integration validate. At the load boundary, a known field of the wrong type is an error, issuesUrl must be a valid URL, and unknown fields become warnings so an older CLI can still load the fields it understands. Before publishing, also run templates, components, and docs under the same doctor integration group. Those leaves compare authored identities with Core and explain whether an overlap is intentional or needs a rename.

Astryx Config

The optional astryx.config.* file at your project root. Declares which integrations to load, where to route issue links, post-codemod hooks, local debug-log and gap-report handlers, and experimental layout components. All optional; {} is valid.

Applies to: astryx.config.{ts,mjs,js}

FieldTypeRequiredDescription
integrationsstring[]noPackage names of Astryx integrations to load alongside core. Example: ['@acme/astryx-widgets'].
issuesUrlstringnoURL that "report an issue" affordances link to.
hooks{ postCodemod?: PostCodemodHook[] }noLifecycle hooks.
hooks.postCodemodPostCodemodHook[]noCommands run after an upgrade applies codemods (e.g. re-run your formatter). Each hook returns a command to execute, or null to skip.
debug(event: DebugEvent) => voidnoRecord every command run. The handler is synchronous; promises are not awaited and output goes to stderr. Declare debug directly in this file so early commands can discover it. An integration can supply one too, as a debug named export from its manifest — both run; set {"astryx": {"inheritDebug": false}} in package.json to take only your own. Example: event => appendFileSync('runs.ndjson', JSON.stringify(event) + '\n').
gapReportGapReportHandlernoHandle explicit gap reports in addition to every loaded integration handler. The project handler runs first. Public handlers require caller consent; internal handlers always run. Example: { audience: 'internal', async handle(report, {signal}) { return sendGap(report, {signal}); } }.
experimental{ xle?: { components?: Record<string, XleComponent> } }noUnstable features; may change without a breaking bump.
experimental.xle.componentsRecord<string, XleComponent>noCustom components the layout expander (XLE) may emit, keyed by tag.
Minimal
js
export default {
integrations: ['@acme/astryx-widgets'],
};
Send every command run somewhere of your own
js
export default {
debug: event => appendFileSync("runs.ndjson", JSON.stringify(event) + "\n"),
};

The config is validated at load with a strict schema: unknown keys are errors, so a typo fails fast rather than being silently ignored.

Astryx Codemod

A codemod module the CLI runs during astryx upgrade. Default-export a plain object with a type discriminant: 'code' rewrites source files, 'config' rewrites the astryx.config.* file. There is no factory to call.

Applies to: the codemods/ dir of an integration

FieldTypeRequiredDescription
titlestringyesShort, human-readable title shown in upgrade output. Example: 'Rename Button prop kind to variant'.
descriptionstringnoOptional longer description.
isOptionalbooleannoWhen true, the codemod runs only when explicitly requested. Default: false.
fileExtensionsstring[]noFile extensions this codemod applies to. Code codemods only; a config codemod always targets astryx.config.*. Example: ['.tsx', '.ts'].
transform(file: AstryxCodemodFile, api: AstryxCodemodApi) => string | null | undefinedyesThe transform. Return the new source to rewrite the file, or null/undefined to leave it unchanged.
fileAstryxCodemodFilenoThe source file presented to the transform.
file.pathstringnoAbsolute path to the file being transformed.
file.sourcestringnoThe current source contents of the file.
apiAstryxCodemodApinoHelpers and context passed as the second argument.
api.jscodeshiftunknownnoA jscodeshift instance configured with a parser for the file.
api.stats(...args: unknown[]) => voidnoReport a statistic (no-op-friendly; provided for jscodeshift parity).
api.report(...args: unknown[]) => voidnoReport progress (no-op-friendly; provided for jscodeshift parity).
type'code'yesDiscriminant for the file-transforming variant. Use 'config' for a codemod that rewrites astryx.config.* instead (see notes). Example: 'code'.
Code codemod (type: code)
js
export default {
type: 'code',
title: 'Rename Button prop kind to variant',
fileExtensions: ['.tsx', '.ts'],
transform(file, api) {
const j = api.jscodeshift;
const root = j(file.source);
// ...rewrite the AST...
return root.toSource();
},
};
Config codemod (type: config)
js
export default {
type: 'config',
title: 'Move issuesUrl into astryx.config',
transform(file, api) {
// Rewrite the astryx.config.* source; return null to skip.
return null;
},
};

The config-codemod variant (type: 'config') carries the same fields as a code codemod except fileExtensions: it always targets the astryx.config.* file rather than a set of source files.

Authors write a plain object and default-export it with the type discriminant; there is no factory. The CLI validates it at the load boundary via parseCodemod with a strict schema, and isOptional defaults to false.

Provider and artifact identity

Separates stable provider/artifact identity from package instances and runtime lifecycle state.

Applies to: Compiler inputs, manifests, search, Build, and Doctor

FieldTypeRequiredDescription
ProviderIdstringyesCanonical logical provider ID. It defaults to the lowercase npm package name; Core uses the same model without publishing an integration manifest.
ArtifactIdstringyesVersioned serialization of provider ID, contribution kind, and stable artifact name. Every segment uses RFC 3986 escaping.
DocIdArtifactIdyesArtifact ID restricted to one authored doc kind. Navigation changes do not change it.
ProviderInstance{ id; providerId; packageName; packageVersion; sourceDigest }yesOne immutable package version and source/content digest. Installed, configured, loaded, selected, and healthy state is a separate runtime overlay.
AuthoredDocEntry{ id; provider; kind; stableName; source; authored }yesNormalized compiler input. Discovery supplies stableName independently from the authored display name, source paths remain package-relative, and provider provenance plus the authored snapshot are immutable.

Provider package renames require an explicit mapping: a ProviderInstance may retain its stable providerId while packageName changes. Silent identity changes are not inferred.

Authored doc graph fields

Optional placement, compatibility aliases, and audience fields shared by every authored documentation kind.

Applies to: Every supported .doc.mjs object

FieldTypeRequiredDescription
placementDocPlacementnoRequests one canonical parent. The compiler fails invalid explicit placement instead of silently using Unorganized.
placement.parentstringyesStable reference to the requested parent namespace.
placement.slotstringnoNamed slot owned by the parent namespace.
placement.ordernumbernoInteger sibling order within the slot.
aliasesstring[]noPrior names or routes retained for compatibility. Aliases do not create another identity.
audience'public' | 'internal'noBundle audience. Omit for public documentation. Default: 'public'.

ComponentDoc

The doc-type for a component directory's {Name}.doc.mjs. A discriminated union of SingleComponentDoc (props on the doc), MultiComponentDoc (a components array), and SubComponentDoc (a subComponentOf pointer). All three share the ComponentBaseDoc fields below; the variant is chosen by which of props / components / subComponentOf you set.

Applies to: {Name}.doc.mjs

FieldTypeRequiredDescription
type'component'noDoc-kind discriminant for the stamped default-export format. Optional: legacy export const docs = {...} docs omit it and the parser falls back to shape-sniffing.
namestringyesStable machine identity and directory name without the Astryx prefix, PascalCase. e.g. 'Button', 'TextInput', 'AppShell'. Change displayName, not name, to edit the visible label; registry URLs derive from this identity by default.
displayNamestringyesHuman-readable display name with spaces between words ('AppShell' → 'App Shell'). Drives the docsite gallery and sidebar label.
registryRegistryDocIdentitynoOptional public registry identity. The converter derives a stable kebab-case slug from name; set slug only to override it, and keep prior relative paths in aliases after a published rename.
importstringnoExact public package specifier consumers use to import an integration-owned component. The packed-package gate resolves this specifier and verifies it exports the component name.
keywordsstring[]noSearch keywords for CLI discovery: synonyms and related UI concepts from other design systems (MUI, Chakra, Radix, and others). Lowercase. Used by astryx component <term> fuzzy matching.
hiddenComponentsstring[]noSub-component names to hide from human-facing UI (CLI listings, docs catalogs). They stay public and importable; agents and tooling can still discover them via source.
hiddenbooleannoHide this entire component from human-facing UI. It stays public and importable. Use for shared primitives (NavIcon, NavMenu) that only make sense inside their parent.
groupstringnoOptional sidebar/docs group. Clusters related components; ungrouped components appear flat in alphabetical order.
category'Action' | 'Chat' | 'Container' | 'Content' | 'Form Controls' | 'Data Input' | 'Data Visualization' | 'Feedback & Status' | 'Layout' | 'Navigation' | 'Overlay' | 'Table & List' | 'Utility'noOverview-gallery category representing the component's functional role. Independent of group (which is for the sidebar). Data Input is a deprecated compatibility alias for Form Controls.
isHiddenFromOverviewbooleannoExclude from the categorized overview page while keeping the component in the sidebar and CLI. Use for sub-components or internal primitives.
theming{ container?: boolean; targets: ComponentThemingTarget[]; vars?: ComponentThemingVar[]; derived?: ComponentThemingDerivedVar[] }noTheming configuration: the stable selector surface (xds-* classes + data-attribute reflections) that themes target via @scope selectors in defineTheme.
theming.containerbooleannoWhen true, container padding props are mapped to container tokens by the theme pipeline instead of emitting raw CSS.
theming.targetsComponentThemingTarget[]yesSelector targets rendered by this component. Each entry corresponds to a themeProps() call in the source.
theming.varsComponentThemingVar[]noCSS custom properties exposed for theming.
theming.derivedComponentThemingDerivedVar[]noMaps standard CSS properties to internal vars for theme-pipeline expansion. Ordered by priority: earlier entries emit first.
usageUsageDocyesComponent usage documentation: concise summary, best practices, component-specific accessibility requirements, and optional visual anatomy. (Optional on SubComponentDoc, where the sub-component description is used instead.)
usage.descriptionstringyesWhat the component is and when to use it, in 2-3 short sentences.
usage.bestPracticesComponentBestPractice[]no3-4 do/don't design-guidance items ({guidance: boolean, description: string}). Never start the description with 'Do' or 'Don't'.
usage.accessibilityComponentAccessibilityRequirement[]noComponent-specific requirements rendered in the shared Accessibility tab. Write at about a grade-7 reading level with short sentences, common words, and active voice. For color contrast, put the ratio in requirement; name the exact foreground, background, state, and any overlay in description; explain exceptions in plain language; and give a human or agent enough detail to reproduce the check. Keep repository audit procedures in the wiki rubric.
usage.accessibilityThemeCoverageComponentAccessibilityThemeCoverage[]noVerified per-theme accessibility measurements rendered in the shared Accessibility tab. Record light and dark mode separately, include rendered color pairs, and mark failed measurements. Put visuals excluded from the audit in notMeasured with a short reason; do not add them as table measurements. Each theme declares applicability for measured values, informed by the component contract and never inferred from the ratio: Conditional is required only in some contexts, Supplemental adds another meaningful cue, and Decorative has no required meaning. These values do not change row status. Provide a complete breakdown when one cell summarizes multiple combinations, and protect derived values with an automated audit.
usage.anatomyComponentAnatomyElement[]noStructural/visual parts in reading order ({name, required, description}).
examplesComponentExampleDoc[]noShort code examples ({label?, code}) rendered by the CLI after the props table.
playgroundComponentPlaygroundConfignoInteractive-preview config: initial prop defaults, overlay for modal-only components, appShellMobile for components gated on AppShell mobile context, and a wrapper for context-dependent sub-components.
propsComponentPropDoc[]noSingleComponentDoc variant (required there): all public props for the one primary component. Each prop is {name, type, description, default?, required?, slotElements?}. Skip styling props like xstyle/className/style. Also present on SubComponentDoc.
components(ComponentEntry | ComponentRef)[]noMultiComponentDoc variant (required there): one entry per public component/hook exported from the directory. Each entry is a full ComponentEntry (inline: name, displayName, description, props | params+returns) or a name-only ComponentRef pointing at a sibling {Name}.doc.mjs.
subComponentOfstringnoSubComponentDoc variant (required there): the parent component's name (e.g. 'Chat'). Marks this file as a sub-component doc that inherits family fields (group, category, keywords, theming, playground) from the parent.
descriptionstringnoSubComponentDoc variant (required there): one-sentence description of the sub-component's role within the parent composition. Single/Multi docs have no top-level description; they derive their summary from usage.
SingleComponentDoc (props on the doc)
js
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
export const docs = {
name: 'Switch',
displayName: 'Switch',
category: 'Form Controls',
keywords: ['toggle', 'switch', 'on off'],
usage: {
description:
'A Switch toggles a single setting on or off. Use it for instant, binary preferences that apply immediately without a submit step.',
bestPractices: [
{guidance: true, description: 'Apply the change immediately when toggled.'},
{guidance: false, description: 'Use a Switch for actions that need confirmation; prefer a Checkbox in a form.'},
],
},
props: [
{name: 'isSelected', type: 'boolean', description: 'Whether the switch is on.', required: true},
{name: 'onChange', type: '(isSelected: boolean) => void', description: 'Called when the user toggles the switch.'},
{name: 'isDisabled', type: 'boolean', description: 'Prevents interaction and dims the control.', default: 'false'},
],
};
MultiComponentDoc (a components array)
js
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
export const docs = {
name: 'Table',
displayName: 'Table',
category: 'Table & List',
usage: {description: 'Displays rows and columns of data. Compose the sub-components to build headers, rows, and cells.'},
components: [
{name: 'Table', displayName: 'Table', description: 'The table container.', props: []},
{name: 'TableRow', displayName: 'Table Row', description: 'A row within the table body.', props: [
{name: 'isSelected', type: 'boolean', description: 'Highlights the row as selected.'},
]},
{name: 'useTableSelection', displayName: 'useTableSelection', description: 'Manages row selection state.',
params: [{name: 'rows', type: 'T[]', description: 'The rows to track.', required: true}],
returns: [{name: 'selectedIds', type: 'Set<string>', description: 'Currently selected row ids.'}]},
],
};

ComponentDoc is a discriminated union of three shapes that all extend ComponentBaseDoc. Pick the variant by which key you set: props (single), components (multi), or subComponentOf (sub).

  • SingleComponentDoc: one primary component; put props directly on the doc via props. Use for Switch, Badge, Spinner, TextInput.
  • MultiComponentDoc: a directory exporting several components/hooks; list them in components (inline ComponentEntry or name-only ComponentRef). Use for Table, Dialog, TabList.
  • SubComponentDoc: a single sub-component in its own {Name}.doc.mjs inside the parent directory; set subComponentOf to the parent name. It inherits family fields and may omit usage.
SubComponentDoc
js
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
export const docs = {
name: 'ChatComposer',
displayName: 'Chat Composer',
subComponentOf: 'Chat',
description: 'The message input row within a Chat, with an editor and send affordance.',
props: [
{name: 'onSend', type: '(text: string) => void', description: 'Called when the user submits a message.', required: true},
],
};

The stamped format is export default { type: 'component', ... }; legacy docs use export const docs = {...} and omit type (the parser shape-sniffs). A hook that is part of a component API is documented as a ComponentEntry in a MultiComponentDoc components array (with params/returns), not as a standalone HookDoc.

HookDoc

The doc-type for a standalone React hook (e.g. useMediaQuery, useFocusTrap, useOverflow) that gets its own .doc.mjs. Hooks that are part of a component API (e.g. useImperativeDialog) belong in that component's MultiComponentDoc components array instead. HookDoc is the hook-flavored view of the shared type: 'function' kind.

Applies to: {useX}.doc.mjs

FieldTypeRequiredDescription
type'function'noDoc-kind discriminant (shared with FunctionDoc). Legacy export const docs = {...} docs omit it.
namestringyesStable hook name exactly as exported, e.g. 'useMediaQuery', 'useFocusTrap'. Change displayName, not name, to edit the visible label; registry URLs derive from this identity by default.
displayNamestringyesHuman-readable display name. Hooks read better as the raw identifier ('useMediaQuery') than spaced, so keep the identifier verbatim.
registryRegistryDocIdentitynoOptional public registry identity. The converter derives a stable kebab-case slug from name; set slug only to override it, and keep prior relative paths in aliases after a published rename.
groupstringnoOptional group for sidebar/docs organization; same as ComponentDoc.group.
keywordsstring[]noSearch keywords for CLI discovery.
paramsHookParamDoc[]yesHook parameters or options-object fields.
params[].namestringyesParameter or option field name.
params[].typestringyesTypeScript type signature as a string.
params[].descriptionstringyesWhat this parameter does. 1-2 sentences.
params[].defaultstringnoDefault value as a string, if optional with a default.
params[].requiredbooleannoTrue if required. Omit if optional.
returnsHookReturnDoc[]yesReturn value documentation. For object returns, list each field; for primitive returns, use a single entry.
returns[].namestringyesField name on the returned object, or 'value' for primitive returns.
returns[].typestringyesTypeScript type.
returns[].descriptionstringyesWhat this return value provides.
usageUsageDocyesUsage documentation: description and best practices.
usage.descriptionstringyesWhat the hook is and when to use it, in 2-3 short sentences.
usage.bestPracticesComponentBestPractice[]no3-4 do/don't design-guidance items ({guidance: boolean, description: string}).
usage.accessibilityComponentAccessibilityRequirement[]noAccessibility requirements specific to using the hook ({name, description}).
usage.anatomyComponentAnatomyElement[]noStructural/visual anatomy elements (rarely used for hooks).
relatedComponentsstring[]noComponent names this hook is commonly used with. Enables cross-referencing (e.g. astryx hook useToast links back to Toast).
relatedHooksstring[]noOther hook names this hook is commonly used with.
importPathstringnoImport path, e.g. '@astryxdesign/core/hooks' or '@astryxdesign/core/Toast'.
categorystringnoCategory for grouping in listings.
A standalone hook doc
js
/** @type {import('@astryxdesign/cli/authoring').HookDoc} */
export const docs = {
type: 'function',
name: 'useMediaQuery',
displayName: 'useMediaQuery',
importPath: '@astryxdesign/core/hooks',
params: [
{name: 'query', type: 'string', description: 'A CSS media query, e.g. "(min-width: 768px)".', required: true},
],
returns: [
{name: 'matches', type: 'boolean', description: 'Whether the query currently matches.'},
],
usage: {
description:
'Subscribes to a CSS media query and re-renders when it changes. Use for responsive behavior that CSS alone cannot express, such as swapping components by breakpoint.',
},
relatedHooks: ['useIsMobile'],
};

A hook's discriminant is type: 'function': HookDoc and FunctionDoc share the generalized function kind. HookDoc is the hook-flavored view: named returns fields and a required usage block.

Only standalone hooks get their own file. A hook that is part of a component API is documented as an entry in that component's MultiComponentDoc components array (with params/returns), so it renders under the component.

FunctionDoc

The generalized type: 'function' doc-type covering both React hooks and CLI/API functions. A hook (HookDoc) is the hook-flavored view of this same kind; FunctionDoc adds the fields an API function needs (a {type, data} return envelope, thrown error codes, the wrapping CLI command). The CLI binding itself lives in a separate CommandDoc.

Applies to: api/<name>/<name>.doc.mjs

FieldTypeRequiredDescription
type'function'noDoc-kind discriminant (shared with hooks).
namestringyesExport name, e.g. 'search' | 'useMediaQuery'.
displayNamestringyesHuman-readable display name, e.g. 'search()'.
kind'hook' | 'api'noWhich flavor; drives docsite sectioning; inferred from importPath if omitted.
summarystringnoOne-line summary.
descriptionstringnoLonger description.
namespacestringnoDocs namespace path. Defaults (e.g. 'cli/api') applied by the docs index.
aliasesstring[]noAlternate slugs that also resolve to this doc.
keywordsstring[]noSearch keywords for discovery.
importPathstringnoImport path, e.g. '@astryxdesign/cli/api' | '@astryxdesign/core/hooks'.
signaturestringnoFull signature as a string, e.g. 'search(query, options?): Promise<SearchResponse>'.
paramsHookParamDoc[]yesParameters / options-object fields.
params[].namestringyesParameter or option field name.
params[].typestringyesTypeScript type signature as a string.
params[].descriptionstringyesWhat this parameter does. 1-2 sentences.
params[].defaultstringnoDefault value as a string, if optional with a default.
params[].requiredbooleannoTrue if required. Omit if optional.
returnsFunctionReturnDoc[]yesReturn documentation. Hooks list named return fields (name set); API functions list {type, data} envelope entries where type is the response discriminant and name is omitted.
returns[].namestringnoField name (hooks); omit for API envelope entries.
returns[].typestringyesTS type (hooks) or response-type discriminant (API), as a string.
returns[].descriptionstringyesWhat this return value / envelope entry provides.
throwsFunctionThrowsDoc[]noErrors the function throws (API functions), keyed to ERROR_CODES.
throws[].codestringyesThe ERROR_CODES member thrown.
throws[].whenstringyesThe condition under which it is thrown.
examplesFunctionExampleDoc[]noUsage examples.
examples[].labelstringnoOptional heading shown above the snippet.
examples[].codestringyesLanguage-level usage, e.g. "await search('button')".
examples[].resultstringnoOptional sample result.
usageUsageDocnoUsage documentation (hooks): description, best practices, accessibility requirements, and anatomy. Same shape as HookDoc.usage.
commandstringnoThe CLI command that wraps this function, e.g. 'search'.
relatedstring[]noRelated function/command names.
relatedComponentsstring[]noComponent names this is commonly used with (hooks).
relatedHooksstring[]noOther hook names this is commonly used with (hooks).
categorystringnoCategory for grouping in listings.
An API function doc (envelope return, no field name)
js
/** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
export const doc = {
type: 'function',
kind: 'api',
name: 'search',
displayName: 'search()',
namespace: 'cli/api',
importPath: '@astryxdesign/cli/api',
summary: 'Find components, hooks, docs, and templates by term.',
signature: 'search(query, options?): Promise<SearchResponse>',
params: [
{name: 'query', type: 'string', description: 'The search term.', required: true},
{name: 'options.type', type: "'component' | 'hook' | 'doc' | 'template'", description: 'Restrict results to one domain.'},
],
returns: [
{type: 'search', description: 'Envelope with the query and ranked results[].'},
],
throws: [
{code: 'ERR_INVALID_ARGUMENT', when: 'query is empty.'},
],
examples: [
{label: 'Basic', code: "await search('button')", result: "{ type: 'search', data: { results: [...] } }"},
],
command: 'search',
};

The type discriminant is 'function' for both flavors. Set kind: 'hook' or kind: 'api' to drive docsite sectioning; it is inferred from importPath when omitted.

The two flavors differ in returns: a hook lists named fields (name set), while an API function lists {type, data} envelope entries whose type is the response discriminant and whose name is omitted. throws (keyed to ERROR_CODES) applies to API functions.

The function does not know it has a CLI. The terminal binding (args, flags, exit codes) lives in a separate CommandDoc that references this function via fn; here you only note the wrapping command name via command.

CommandDoc

The doc-type for a CLI command: the terminal binding of an operation. A command is not its own behavior; it is a FunctionDoc exposed on the CLI, referenced via fn, carrying only CLI-surface facts (args, flags, subcommands, examples, exit codes). A defineCommand converter turns it into Commander config + --help.

Applies to: clients/cli/commands/<name>.doc.mjs

FieldTypeRequiredDescription
type'command'noDoc-kind discriminant. Marks the file as a command doc.
namestringyesCommand path, e.g. 'search' | 'theme build'.
displayNamestringyesHuman-readable display name, e.g. 'astryx search'.
summarystringyesOne-line summary → Commander .description() + the docs listing.
descriptionstringnoLonger help body / when-to-use.
namespacestringnoDocs namespace path. Defaults to 'cli' when applied by the docs index.
aliasesstring[]noAlternate slugs that also resolve to this doc.
fnstringnoName of the FunctionDoc (and @astryxdesign/cli/api export) this command wraps. Example: 'search'.
argsCommandArgDoc[]noPositional arguments.
args[].namestringyesArgument name.
args[].paramstringnoFunctionDoc param this arg binds to (inherits its description).
args[].descriptionstringnoOverride description (else inherited from the referenced param).
args[].requiredbooleannoWhether the positional argument must be supplied.
args[].variadicbooleannoWhether the argument collects a variable number of values.
optionsCommandOptionDoc[]noFlags/options.
options[].flagstringyesCommander flag spec, e.g. '-l, --limit <n>' | '--json'.
options[].paramstringnoFunctionDoc param this flag maps to (inherits its description).
options[].descriptionstringnoOverride/explicit description (required when cliOnly).
options[].choicesstring[]noAllowed values for the flag.
options[].defaultstringnoDefault value as a string.
options[].cliOnlybooleannoTrue for CLI-only flags with no function param (e.g. --json).
subcommandsstring[]noSubcommand names (for command groups like theme / layout).
examplesCommandExampleDoc[]noTerminal examples.
examples[].labelstringnoOptional heading shown above the invocation.
examples[].clistringyesA full terminal invocation, e.g. 'astryx search button --json'.
examples[].outputstringnoOptional sample output.
exitCodes{ code: number; when: string }[]noDocumented exit codes.
exitCodes[].codenumberyesThe process exit code.
exitCodes[].whenstringyesThe condition that produces this exit code.
relatedstring[]noRelated command names.
notesReferenceContentBlock[]noFreeform prose/notes.
A command that wraps the search() function
js
/** @type {import('@astryxdesign/cli/authoring').CommandDoc} */
export const doc = {
type: 'command',
name: 'search',
displayName: 'astryx search',
summary: 'Find components, hooks, docs, and templates.',
namespace: 'cli',
fn: 'search',
args: [{name: 'query', param: 'query', required: true}],
options: [
{flag: '--type <domain>', param: 'options.type', choices: ['component', 'hook', 'doc', 'template']},
{flag: '--json', cliOnly: true, description: 'Emit the typed JSON envelope.'},
],
examples: [{label: 'Terminal', cli: 'astryx search button --json'}],
exitCodes: [{code: 1, when: 'The --type value is not a known domain.'}],
related: ['discover'],
};

A command carries only CLI-surface facts. Behavior, parameters, returns, and thrown errors live in the FunctionDoc it points at via fn; the function does not know it has a CLI.

Bind an arg/option to a function param via param so it inherits that param's description. Use cliOnly: true for flags with no function param (e.g. --json); those must supply their own description.

EnumDoc

The doc-type for a closed vocabulary: a fixed set of literal values such as error codes or response-type discriminants. Colocated as a .doc.mjs next to the source of truth it documents.

Applies to: <enum>.doc.mjs

FieldTypeRequiredDescription
type'enum'noDoc-kind discriminant. Marks the file as an enum doc.
namestringyesURL-safe identifier, used as the docs slug within its namespace. Example: 'error-codes'.
displayNamestringyesHuman-readable title. Example: 'Error Codes'.
descriptionstringyesOne-line summary shown in listings.
namespacestringnoDocs namespace path. Defaults to 'cli' when applied by the docs index.
aliasesstring[]noAlternate slugs that also resolve to this doc.
membersEnumMemberDoc[]yesThe enumerated members: one entry per literal value.
members[].valuestringyesThe literal value, e.g. 'ERR_UNKNOWN_TOPIC' | 'component.list'.
members[].descriptionstringyesWhat the value means / when it occurs.
members[].deprecatedstringnoDeprecation reason, if deprecated.
Error-codes enum doc
js
/** @type {import('@astryxdesign/cli/authoring').EnumDoc} */
export const doc = {
type: 'enum',
name: 'error-codes',
displayName: 'Error Codes',
namespace: 'cli',
description: 'Stable error codes thrown by the CLI/API and surfaced in the JSON envelope.',
members: [
{value: 'ERR_UNKNOWN_TOPIC', description: 'The requested docs topic does not exist.'},
{value: 'ERR_INVALID_ARGUMENT', description: 'A required argument was missing or malformed.'},
{value: 'ERR_LEGACY', description: 'Old alias.', deprecated: 'Use ERR_INVALID_ARGUMENT.'},
],
};

An enum doc is the human-readable mirror of a closed vocabulary defined elsewhere in source (e.g. an ERROR_CODES map or a response-type union). Keep the members in sync with that source of truth.

Mark a value with deprecated (a migration hint) rather than deleting it, so old codes stay documented while readers are pointed at the replacement.

NamespaceDoc

Declares named navigation slots and renderer-neutral layout blocks for already-discovered docs. It never scans folders or copies child documents.

Applies to: <namespace>.doc.mjs

FieldTypeRequiredDescription
type'namespace'yesDoc-kind discriminant.
namestringyesStable provider-local identity. Moving the namespace does not change this value.
titlestringyesHuman-readable page title.
summarystringyesOne-line summary used in listings and search results.
placementDocPlacementnoOptional canonical parent request: {parent, slot?, order?}. Invalid explicit placement fails compilation instead of falling back.
aliasesstring[]noPrior names or routes that must keep resolving to this doc.
audience'public' | 'internal'noBundle audience. Defaults to 'public'. Default: 'public'.
keywordsstring[]noSearch terms not already present in the title or summary.
slotsRecord<string, NamespaceSlot>yesNamed placement and collection targets. Each slot declares a title and accepted doc kinds; configured providers require an explicit extension slot.
adoptsNamespaceAdoptionRule[]noProvider-local rules that adopt otherwise-unplaced docs from a logical discovery group. They never scan a folder.
blocksReferenceContentBlock[]noOrdered layout content. V1 adds only workflow, collection, and reference to the existing prose, heading, code, table, list, and token-ref blocks.
A CLI namespace with one adopted source group
js
/** @type {import('@astryxdesign/cli/authoring').NamespaceDoc} */
export const docs = {
type: 'namespace',
name: 'cli',
title: 'Astryx CLI',
summary: 'Commands, APIs, and integration authoring.',
slots: {
guides: {title: 'Guides', accepts: {kinds: ['namespace', 'generic']}},
reference: {
title: 'Reference',
accepts: {kinds: ['namespace', 'command']},
},
},
adopts: [{
source: {group: 'cli-commands', kinds: ['command']},
into: 'reference',
}],
blocks: [
{type: 'collection', source: {slot: 'guides'}, presentation: 'cards'},
{type: 'collection', source: {slot: 'reference'}, presentation: 'compact'},
],
};

Child docs request one canonical home with placement. Collections store and render stable references to those docs; they never create a second identity or parent.

GuidancePractices
Don't

Use a directory as implicit navigation.

Don't

Put JSX, HTML, ANSI, callbacks, or custom renderer code in a doc.

Don't

Use choice, callout, or checklist blocks before the full block-extension contract exists.

ReferenceDoc

The doc-type for a reference/topic doc: tokens, principles, theming, patterns, accessibility, migration guides. Unlike ComponentDoc it is not tied to a component: drop a .doc.mjs in the docs directory and it shows up in astryx docs. Content is built from ordered sections of mixed content blocks.

Applies to: assets/docs/<topic>.doc.mjs

FieldTypeRequiredDescription
type'generic'noDoc-kind discriminant. Stays 'generic' (the reference/topic discriminant). Legacy export const docs = {...} docs omit it.
namestringyesURL-safe identifier, used as the CLI topic name. e.g. 'tokens', 'principles'.
titlestringyesHuman-readable title. e.g. 'All Tokens'. (Reference docs use title, not displayName.)
descriptionstringyesOne-line summary shown in topic listings.
categorystringnoNavigation category: 'guide' or 'foundations'.
replacesstringnoName of an existing topic this doc takes the place of. Authored by an integration that serves its own guide instead of the built-in one: on a doc of the same name it swaps the content, and on a doc of another name it also leaves the old name as an alias so astryx docs <old> still resolves. Exclusive with extends. Example: 'getting-started'.
extendsstringnoName of an existing topic this doc merges onto, section by section: a section whose title matches one in the base replaces it, a section the base does not have is appended. For correcting or adding to a topic rather than owning it. Exclusive with replaces. Example: 'theme'.
sectionsReferenceSection[]yesOrdered sections that make up the doc. Each becomes an h2 in full output and can be retrieved via astryx docs <topic> <section>.
sections[].idstringnoStable section anchor. New docs should set this instead of relying on a mutable title.
sections[].titlestringyesSection title, e.g. "Spacing Tokens", "Light/Dark Mode".
sections[].categorystringnoNavigation category ('guide' | 'foundations'). Mirrors the parent doc's category so sections can be grouped independently.
sections[].contentReferenceContentBlock[]yesOrdered content blocks. Existing prose, heading, code, table, list, and token-ref blocks remain; V1 adds workflow, collection, and reference.
sections[].previewTypeReferenceTokenPreviewTypenoPreview type for token tables in this section. When set, the docsite renders a visual preview column from the token's computed value. Omit for non-token sections.
tokenCategorystringnoToken category for foundational docs that map to a token section (e.g. 'color'). Lets the tokens overview link to this doc for detailed guidance.
A reference doc with one section
js
/** @type {import('@astryxdesign/cli/authoring').ReferenceDoc} */
export const docs = {
type: 'generic',
name: 'spacing',
title: 'Spacing',
description: 'Spacing tokens for gap, margin, and padding.',
category: 'foundations',
tokenCategory: 'spacing',
sections: [
{
title: 'Spacing Tokens',
content: [
{type: 'prose', text: 'Use spacing tokens instead of raw pixel values so layouts stay on the 4px grid.'},
{type: 'table', headers: ['Token', 'Value'], rows: [['--spacing-4', '16px']]},
],
previewType: 'spacing-bar',
},
],
};

Each sections[].content is an ordered array of ReferenceContentBlock, a discriminated union. V1 adds only workflow, collection, and reference; choice, callout, and checklist remain invalid. The same union is reused by the notes field on SchemaDoc and CommandDoc.

ReferenceContentBlock union
ts
type ReferenceContentBlock =
| { type: 'prose'; text: string }
| { type: 'heading'; level: 3 | 4 | 5 | 6; text: string }
| { type: 'code'; lang: string; code: string; label?: string }
| { type: 'table'; headers: string[]; rows: string[][] }
| { type: 'list'; style: 'ordered' | 'unordered' | 'do' | 'dont'; items: string[] }
| { type: 'token-ref'; topic: string; section: string }
| { type: 'workflow'; title?: string; steps: WorkflowStep[] }
| { type: 'collection'; source: {slot: string}; presentation?: 'list' | 'cards' | 'compact'; whenEmpty?: 'show' | 'omit' }
| { type: 'reference'; target: string; projection?: {fields?: string[]; sections?: string[]} };

A token-ref block (e.g. {type: 'token-ref', topic: 'tokens', section: 'Color Tokens'}) references a token table in another topic; the CLI resolves and inlines it at read time so the docsite can render live theme values.

A section may set previewType to render a visual preview column for token tables: one of 'swatch' | 'shadow-box' | 'radius-box' | 'spacing-bar' | 'size-bar' | 'border-line' | 'duration-bar' | 'easing-curve' | 'font-sample'.

SchemaDoc

The doc-type for documenting an authored/received OBJECT shape (astryx.config, a codemod payload, the doc-types themselves). Colocated as a .doc.mjs next to the schema it describes. Fields nest recursively, so a whole shape is one tree.

Applies to: <schema>.doc.mjs

FieldTypeRequiredDescription
type'schema'noDoc-kind discriminant. Marks the file as a schema doc.
namestringyesURL-safe identifier, used as the docs slug within its namespace. Example: 'config'.
displayNamestringyesHuman-readable title. Example: 'Astryx Config'.
descriptionstringyesOne-line summary shown in listings.
namespacestringnoDocs namespace path (e.g. 'cli' | 'authoring'). Defaults are applied by the docs index; set explicitly to place the schema.
aliasesstring[]noAlternate slugs that also resolve to this doc (back-compat).
appliesTostringnoWhat this schema applies to, e.g. 'astryx.config.{ts,mjs,js}' | 'AstryxConfig'.
fieldsSchemaFieldDoc[]yesThe fields that make up the shape. Object-typed fields nest their members recursively via each field's own fields, so the whole shape is one tree.
fields[].namestringyesField name, or a dotted path for a nested field (e.g. 'hooks.postCodemod').
fields[].typestringyesTypeScript type signature as a string, e.g. 'string[]' | "'a' | 'b'".
fields[].descriptionstringyesWhat the field is for, in 1-2 sentences.
fields[].requiredbooleannoTrue if the field must be provided. Omit (don't set false) if optional.
fields[].defaultstringnoDefault value as a string, if any.
fields[].examplestringnoA short inline example value.
fields[].deprecatedstringnoDeprecation reason, if the field is deprecated.
fields[].fieldsSchemaFieldDoc[]noNested object fields, for object-typed fields. Recursive.
examples{ label?: string; code: string }[]noFull example objects/snippets.
examples[].labelstringnoOptional heading shown above the snippet.
examples[].codestringyesThe example source.
notesReferenceContentBlock[]noFreeform prose/notes rendered after the field table. Same block union as ReferenceDoc (prose, heading, code, table, list, token-ref).
A small schema doc with a nested object field
js
/** @type {import('@astryxdesign/cli/authoring').SchemaDoc} */
export const doc = {
type: 'schema',
name: 'integration',
displayName: 'Astryx Integration',
namespace: 'cli',
description: 'The astryx.integration.* manifest that registers the components a package provides.',
appliesTo: 'astryx.integration.{ts,mjs,js}',
fields: [
{name: 'name', type: 'string', description: 'Package name.', required: true},
{
name: 'components',
type: '{ dir: string }',
description: 'Where component sources live.',
fields: [
{name: 'components.dir', type: 'string', description: 'Glob root for XDS*.tsx files.', required: true},
],
},
],
};

SchemaFieldDoc is recursive: an object-typed field lists its members in its own fields, so an entire nested shape (including paths like hooks.postCodemod or experimental.xle.components) is documented as one tree.

Name nested fields with a dotted path from the root (e.g. hooks.postCodemod) so readers can see where each field sits in the shape.

GuidancePractices
Do

Set required: true only for mandatory fields.

Do

Keep type close to the real TS type; use single quotes for string-literal unions.

Don't

Set required: false for optional fields; omit required entirely instead.

TemplateDoc

The doc-type for template metadata. A discriminated union of PageTemplateDoc (type: 'page') for full page templates and BlockTemplateDoc (type: 'block') for editable compositions. A block can stand alone or use exampleFor to attach to one component; isShowcase requires that component ownership.

Applies to: <Name>.template.mjs

FieldTypeRequiredDescription
type'page' | 'block'yesDiscriminant selecting the variant: 'page' for a full page template, 'block' for an editable composition that may be standalone or component-owned.
namestringyesStable identifier for block templates; change displayName, not name, to edit their visible label. For page templates it is a human-readable label, while the existing template-directory/CLI slug owns the default registry path.
displayNamestringyesHuman-readable label for the gallery/CLI. Spaces out block names that mirror a PascalCase component ('ChatMessageMetadata' → 'Chat Message Metadata').
registryRegistryDocIdentitynoOptional public registry identity. The converter derives a stable kebab-case slug from name (or the existing page-template slug); set slug only to override it, and keep prior relative paths in aliases after a published rename.
descriptionstringnoOne-sentence description of what the template provides.
isReadybooleannoWhether the template is ready for use. false shows as '(WIP)' in the gallery and CLI.
scaffoldbooleannoScaffolding-only template (e.g. blank page): available via the CLI but hidden from browsable galleries.
categoryTemplateCategorynoFunctional gallery category following a 'Group - Variant' convention (e.g. 'Dashboard - Analytics', 'Table - Basic', 'Form - Wizard'). The overview groups by the text before ' - '.
isHiddenFromOverviewbooleannoOpt out of the Templates overview gallery while staying available via the CLI. Use for duplicate/experimental variants. Scaffold templates are hidden automatically.
exampleForstringnoBlock templates only: optional component ownership. Set this when the block is specifically an example of one component. Omit it for a standalone composition.
alsoExampleForstring[]noBlock templates only: additional component/hook doc pages whose Examples section should include this block.
alsoShowcaseForstring[]noBlock templates only: additional doc pages whose hero showcase should reuse this block (secondary placements; does not change the primary showcase).
aspectRationumbernoBlock templates only (required): width-to-height ratio for preview containers (e.g. 16/9, 1, 3/4).
scalenumbernoBlock templates only: scale factor for the block preview. Default: 1.
componentsUsedstring[]noBlock templates only: component names this block uses, for 'See also'/'Used in' cross-references (not primary attribution).
isShowcasebooleannoBlock templates only: when true this block is the canonical hero showcase for its exampleFor component. Requires exampleFor.
Page template
js
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
export const doc = {
type: 'page',
name: 'Dashboard',
displayName: 'Dashboard',
description: 'An analytics dashboard with KPI cards and charts.',
category: 'Dashboard - Analytics',
isReady: true,
};
Block template (component example)
js
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
export const doc = {
type: 'block',
name: 'ButtonGroupExample',
displayName: 'Button Group Example',
description: 'A row of related buttons showing primary and secondary actions.',
exampleFor: 'Button',
aspectRatio: 16 / 9,
componentsUsed: ['Button', 'HStack'],
isShowcase: true,
};

TemplateDoc is a discriminated union keyed by type. Set type: 'page' for a full page template. Set type: 'block' for an editable composition; add exampleFor only when one component owns the example.

  • PageTemplateDoc (type: 'page'): a full page template; name doubles as its display value.
  • BlockTemplateDoc (type: 'block'): an editable composition. exampleFor is optional component ownership; omit it for a standalone block. isShowcase requires exampleFor.

category uses the shared TemplateCategory taxonomy: 'Group - Variant' strings (e.g. 'Table - Bulk Actions'). Not every value maps to an existing template; unused values are reserved so authors get autocomplete for the full taxonomy.