Skip to content

feat(docs): emit CLI schema JSON + docs wiring for docs-builder#264

Draft
Mpdreamz wants to merge 1 commit into
elastic:mainfrom
Mpdreamz:feature/cli-schema
Draft

feat(docs): emit CLI schema JSON + docs wiring for docs-builder#264
Mpdreamz wants to merge 1 commit into
elastic:mainfrom
Mpdreamz:feature/cli-schema

Conversation

@Mpdreamz
Copy link
Copy Markdown
Member

@Mpdreamz Mpdreamz commented Apr 30, 2026

Closes elastic/agentic-interface-program#54 · Supersedes #245

What is this?

Adds elastic cli-schema — a native command that emits the full CLI structure (namespaces, commands, flags, types, defaults, enum choices) as a single checked-in JSON file: docs/cli/schema.json.

That file is the only thing docs-builder needs to generate a complete, navigable CLI reference. No docs generator to maintain in this repo.

cursorful-video-1777577780994.mp4

Why this replaces #245

PR #245 added a docs generator inside this repo — a TypeScript walker that produces Markdown and per-command JSON schema files. This PR takes a different approach:

  • One file out, not hundreds. docs/cli/schema.json is the single artifact. docs-builder turns it into a full reference site with no further involvement from this repo.
  • No docs renderer to maintain. Markdown generation, page layout, navigation cards, parameter anchors, usage line synthesis — all lives in docs-builder. This repo only needs to keep the schema accurate.
  • Always self-documenting. A CI check re-runs npm run build:schema and diffs against the committed file. Add a command, rename a flag, change a description — CI fails until the schema is regenerated. The docs can never silently lag behind the CLI.
  • Can mix generated docs with custom notes docs-builder supports injecting more context into generated docs for namespaces / commands.
  • docs-builder will know these are CLI docs, helpful for LLM output (the .md variant we always emit for each docs) and search.

What's in this PR

elastic cli-schema

A new top-level command, consistent with the existing lazy-load pattern:

elastic cli-schema

Prints the full CLI structure as JSON to stdout. npm run build:schema pipes it to docs/cli/schema.json.

Loads all namespaces eagerly and walks the Commander tree. For each leaf command it extracts:

Field Source
type OptionDefinition.type / SchemaArgDefinition.type
defaultValue Zod schema defaults
repeatable / separator acceptsArrayForm (comma-split array fields)
enumValues z.toJSONSchema() — same call used by --help --json
elementType Array item type from Zod JSON Schema
aliases Commander .aliases()

src/namespaces.ts — single source of truth

All top-level namespace registrations live here. Both cli.ts (lazy stubs) and cli-schema (eager load-all) consume the same list. Adding a new namespace requires editing one file.

docs/

docs/
├── docset.yml               # wires docs/cli/schema.json into docs-builder
├── index.md
└── cli/
    ├── schema.json          # generated — do not edit by hand
    ├── index.md
    ├── installation.md
    └── configuration.md

docset.yml tells docs-builder where the schema lives and which hand-written pages appear alongside the generated reference:

toc:
  - cli: cli/schema.json
    folder: cli
    children:
      - file: installation.md
      - file: configuration.md

Hand-written pages under docs/cli/ can inject additional context into generated namespace and command pages without touching the schema. For example, docs/cli/index.md overrides the root CLI page body, and a file named docs/cli/stack/es/search.md would inject content into the generated elastic stack es search command page — the schema-driven heading, usage block, and parameter tables still render; the file adds human context on top.

Test plan

Follow-up: CI schema-freshness check

Add to CI so the committed schema can never silently lag behind the CLI:

- name: Check CLI schema is up to date
  run: |
    npm run build:schema
    git diff --exit-code docs/cli/schema.json || \
      (echo "docs/cli/schema.json is out of date — run: npm run build:schema" && exit 1)

Adds a `cli-schema` command and `npm run build:schema` script that walk
the full Commander command tree and emit the CLI structure as a
checked-in JSON artifact (docs/cli/schema.json) conforming to the argh
CLI schema format.

Key design choices:
- `src/namespaces.ts` is the single source of truth for top-level
  namespace registration; adding a new namespace only requires one edit
- `src/factory.ts` attaches `_commandConfig` (typed options + Zod schema
  + schemaArgs) to every leaf command so the emitter can recover full
  type/validation metadata without re-parsing Commander strings
- ES API schemas are loaded eagerly only for `cli-schema`; all other
  invocations retain the existing lazy-load optimisation
- Per-parameter fields emitted: `type`, `defaultValue`, `repeatable`,
  `separator`, `enumValues` (from Zod), `elementType`, `hidden`
- Enum choices and array element types are extracted via the same
  `z.toJSONSchema()` call already used by `--help --json`

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@Mpdreamz Mpdreamz force-pushed the feature/cli-schema branch from 2c41cdc to f05dc83 Compare April 30, 2026 20:04
@github-actions
Copy link
Copy Markdown

MegaLinter analysis: Error

Descriptor Linter Files Fixed Errors Warnings Elapsed time
✅ COPYPASTE jscpd yes no no 8.47s
✅ REPOSITORY gitleaks yes no no 103.51s
✅ REPOSITORY git_diff yes no no 0.63s
✅ REPOSITORY secretlint yes no no 13.61s
✅ REPOSITORY trivy yes no no 18.83s
❌ TYPESCRIPT eslint 6 1 0 6.35s
✅ YAML yamllint 1 0 0 0.77s

Detailed Issues

❌ TYPESCRIPT / eslint - 1 error
src/cli.ts
  9:15  error  'OpaqueCommandHandle' is defined but never used  @typescript-eslint/no-unused-vars

✖ 1 problem (1 error, 0 warnings)

See detailed reports in MegaLinter artifacts
Set VALIDATE_ALL_CODEBASE: true in mega-linter.yml to validate all sources, not only the diff

MegaLinter is graciously provided by OX Security
Show us your support by starring ⭐ the repository

Comment thread src/namespaces.ts
load: (opts?: LoadOptions) => Promise<OpaqueCommandHandle>
}

export const NAMESPACES: NamespaceEntry[] = [
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice refactor. 👏

Comment thread src/cli.ts
Comment on lines +104 to +108
if (firstArg === 'cli-schema') {
const { registerCliSchemaCommand } = await import('./cli-schema.ts')
program.addCommand(await registerCliSchemaCommand(VERSION, program))
} else {
program.addCommand(defineGroup({ name: 'config', description: 'Author and maintain the elastic config file' }))
program.addCommand(defineGroup({ name: 'cli-schema', description: 'Emit the CLI structure as argh-schema JSON' }))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why this can't also live in namespaces.ts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants