Web UI — schema tree + form + live JSON preview |
TUI — terminal demo (asciinema) |
Sliders — integer & fractional steps, x-slider-marks
|
Range sliders — two handles, one shared track |
Choices — select, segmented, radio, switch |
Appearance — custom color picker via x-control: "color"
|
schemaui turns JSON Schema documents into interactive Web and TUI
editors — and into a structured question UI for AI agents. The browser path
ships as an embedded SPA; the terminal path is powered by ratatui,
crossterm, and jsonschema; both share the same schema → form → validate
pipeline.
The library parses rich schemas (nested sections, $ref, arrays, key/value
maps, pattern properties…) into a navigable form tree, renders it as a browser
form or a keyboard-first TUI, and validates after every edit so users always see
the full list of issues before saving.
CLI available:
schemaui-cliinstalls theschemauibinary. Prefer the CLI? Jump to CLI installation and usage.
schemaui doubles as an interactive question UI for AI coding agents
(Claude Code, Codex, zcode, …). Agent processes have no TTY, so a TUI would
render nowhere and hang — instead the agent spawns a Web UI bound to 0.0.0.0,
hands you a browser form with live validation, and reads your answers from a
stable JSON file under .schemaui/answers/.
The agent-facing skill lives in its own repository:
a2ui-ask — Interactive UI for AI
agents: turn structured questions into browser forms. It ships the SKILL.md,
prompt templates, cross-platform helper scripts (Python / Bash / PowerShell),
and five runnable example forms.
Install it any of three ways:
# 1. one line, via skills.sh (recommended)
npx skills add YuniqueUnic/a2ui-ask
# 2. or just tell your agent:
# "install the skill at https://github.com/YuniqueUnic/a2ui-ask"
# 3. or clone manually (global / per-project)
git clone https://github.com/YuniqueUnic/a2ui-ask.git ~/.claude/skills/a2ui-ask5-minute smoke test from an a2ui-ask clone, with schemaui on your PATH
(opens your browser, blocks until Save & Exit):
python3 scripts/ask.py \
--schema examples/env-schema.json \
--config examples/env-defaults.json \
--title "Deployment Config"The script prints SCHEMAUI_URL=… and SCHEMAUI_ANSWER=…, wakes your browser,
and writes the answer to .schemaui/answers/<topic>-<timestamp>.json — an audit
trail both the agent and you can re-read later. Full wiring guide:
a2ui-ask README.
| Surface | Who uses it | How to start |
|---|---|---|
| Web UI | Browser editing, AI agents, any device on the LAN | schemaui web -s schema.json -c config.yaml |
| Library API | Embed in your Rust app | schemaui::web::session (Web) or SchemaUI::run (TUI) |
| TUI (default CLI mode) | Terminal workflows, SSH, scripts | schemaui -s schema.json -c config.yaml or schemaui tui … |
The Web UI and TUI demos sit side-by-side in the header above: schema tree + form + live JSON preview in the browser, and the same pipeline running as a keyboard-first terminal UI (asciinema).
- AI-agent ready – the a2ui-ask
skill ships prompts, a
SKILL.md, and helper scripts so coding agents ask structured questions through the Web UI and read answers from files (see AI Agent Integration). - Embedded Web UI – enabling the
webfeature bundles a browser UI and exposes helpers underschemaui::web::sessionso host applications can serve the experience without reimplementing the stack. - Schema fidelity – draft-07 compatible, including
$ref,definitions,patternProperties, enums, numeric ranges, and nested objects/arrays. - Sections & overlays – top-level properties become root tabs, nested objects are flattened into sections, and complex nodes (composites, key/value collections, array entries) open dedicated overlays with their own validators.
- Immediate validation – every keystroke can trigger
jsonschema::Validator, and all errors (field-scoped + global) are collected and displayed together. - Pluggable I/O –
io::inputingests JSON/YAML/TOML (feature-gated) whileio::outputcan emit to stdout and/or multiple files in any enabled format. - Batteries-included CLI –
schemaui-clioffers the same pipeline as the library, including multi-destination output, stdin/inline specs, and aggregated diagnostics.
Enable the web feature to ship a browser UI (static assets under web/dist/
are embedded in the crate). The same schema/config pipeline used by the TUI
feeds a three-pane experience:
- Navigation – nested schema tree / breadcrumbs for multi-level objects
- Form editor – required flags, enums, numbers, arrays, live field errors
- JSON preview – pretty-printed document that updates as you edit
Screenshot: see the header above (docs/web.mix.png). Driving schemaui from an
AI agent? Use AI Agent Integration — agent processes
have no TTY, so the Web UI is the only surface that works for them.
schemaui-cli enables web by default. Same I/O flags as TUI; only host/port
are Web-specific:
# random free port on 127.0.0.1; print final JSON to stdout
schemaui web \
--schema ./schema.json \
--config ./defaults.json \
--host 127.0.0.1 --port 0 \
-o -
# fixed port + write result to disk
schemaui web -s ./schema.json -c ./config.yaml -p 8787 -o ./out.json| Flag | Default | Meaning |
|---|---|---|
--host / -l |
127.0.0.1 |
Bind address (--bind / --listen aliases) |
--port / -p |
0 |
Bind port (0 = ephemeral free port) |
Workflow in the browser: edit fields → Save keeps the session open → Save
& Exit (or Exit) shuts down the temporary server and emits the value through
configured -o destinations. Note that -o is greedy: put every other flag
before it, and pass multiple destinations space-separated in one occurrence
(-o ./out.json -) rather than repeating the flag.
Full CLI manual: docs/en/cli_usage.md.
Requires the web feature (cargo add schemaui --features web), which is why
this example is ignored — doctests build against the default feature set.
use std::net::IpAddr;
use std::time::{Duration, Instant};
use schemaui::SessionOutcome;
use schemaui::web::session::{
ServeOptions,
WebSessionBuilder,
bind_session,
};
async fn run() -> anyhow::Result<()> {
let schema = serde_json::json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"host": {"type": "string", "default": "127.0.0.1"},
"port": {"type": "integer", "default": 8080}
},
"required": ["host", "port"]
});
let config = WebSessionBuilder::new(schema)
.with_title("Service Config")
// Optional: close the session if nobody answers within 10 minutes.
.with_deadline(Some(Instant::now() + Duration::from_secs(600)))
.build()?;
let addr = ServeOptions::new(IpAddr::from([127, 0, 0, 1]), 0).socket_addr();
let session = bind_session(config, addr).await?;
println!("visit http://{}/", session.local_addr());
match session.run().await? {
SessionOutcome::Completed(value) => {
println!("final JSON: {}", serde_json::to_string_pretty(&value)?);
}
SessionOutcome::TimedOut => {
eprintln!("session timed out; nothing was saved");
}
}
Ok(())
}bind_session / serve_session spawn an Axum router serving the versioned
/api/v1/* contract (session bootstrap, validate, preview, save, exit — see
Pluggable Frontends & Theming below) plus
static assets, embedded by default. For custom HTTP stacks, reuse
session_router / WebSessionBuilder instead of the turnkey helpers. The CLI
schemaui web … command is a thin wrapper around these APIs.
Architecture notes:
docs/en/web-ui-architecture-and-refactor-spec.md.
The Web session speaks a versioned, framework-agnostic HTTP contract at
/api/v1/* — bootstrap (GET /session, GET /schema), POST /validate,
POST /preview, POST /save, POST /exit, plus GET /theme.css when a theme
is configured. The bundled React SPA is the reference client, not the only
possible one:
# reskin the bundled UI by overriding its design tokens
schemaui web -s schema.json --web-theme examples/themes/midnight.css
# swap the frontend entirely for a directory containing your own index.html
schemaui web -s schema.json --frontend examples/frontendexamples/frontend/ is a framework-free, build-step-free index.html that
drives a real session through /api/v1/* — proof the contract works for any
stack, not just the bundled one. examples/themes/midnight.css is a worked
--web-theme example; both are covered by contract tests
(src/tests/web/theme_tests.rs).
During development, run a real session on a fixed port and point web/ui's Vite
dev server at it instead of the embedded build — no CORS layer needed,
vite.config.ts proxies /api/* to http://127.0.0.1:8787:
schemaui web -s schema.json -p 8787 & # in one terminal
cd web/ui && pnpm dev # in another; edits hot-reload against itDesign record:
docs/en/web-v1-theming-and-wasm.md.
schemaui-wasm compiles the same schema → UiAst → validate → render pipeline to
WebAssembly, so it runs with no server at all — in a static site, inside a
non-Rust host, or on an edge worker. Five exports mirror the HTTP contract
byte-for-byte (a shared test fixture in src/tests/web/wasm_parity_tests.rs
asserts they agree): buildUiAst, validate, render, schemaWithDefaults,
schemaFromData, plus parseDocument for accepting JSON/YAML/TOML input with
no server to have already parsed it.
just build-wasm # -> schemaui-wasm/pkg (wasm-bindgen "web" target)
just build-playground # -> web/playground-dist (static, no server needed)Playground. The same React SPA that ships with the CLI, pointed at a
WasmBackend transport instead of an HTTP one — one frontend, two backends, so
a gap in the contract shows up as something the Playground can't do rather than
going unnoticed. Paste or drop a schema (web/ui/src/playground/), fill the
generated form entirely client-side, and export the result — no network request
leaves the tab. Deployed to GitHub Pages from
.github/workflows/deploy-playground.yml on every push to main.
Edge API. edge/ is a stateless Cloudflare Worker over the same wasm
artifact, for callers that want the contract over HTTP without a Rust dependency
or a wasm loader — schema in, UiAst/validation/rendered document out, no session
and no storage. Deploy with just deploy-edge (needs CLOUDFLARE_API_TOKEN /
CLOUDFLARE_ACCOUNT_ID); .github/workflows/deploy-edge.yml does the same on
push to main once those secrets are configured, and fails loudly rather than
silently no-op-ing when they are not.
Stored, shareable hosted sessions are an explicit non-goal for now — see the "Rejected" section of the design record for why.
Design record:
docs/en/decisions/0005-wasm-core-and-hosting.md.
When you launch the CLI with --config and omit --schema, schemaui-cli now
resolves the schema in this order:
- Explicit
--schema - A schema declaration embedded in the config document
- Fallback inference via
schema_from_data_value
Supported declarations:
- JSON: root
$schema - TOML:
#:schema ./schema.json - YAML:
# yaml-language-server: $schema=... - YAML fallback:
# @schema ...
Both local and remote schema references are supported. Relative local paths are
resolved against the config file directory, while inline/stdin configs fall back
to the current working directory. JSON $schema metadata is stripped from the
in-memory defaults before validation/output so editor hints do not leak into the
final config payload.
Remote http(s) schema loading exists only in schemaui-cli, and the CLI
enables the remote-schema feature by default. Disable it if you want a
local-only binary surface; the schemaui library crate does not enable any
remote schema loading by default.
Feature defaults are intentionally split by audience:
schemaui-clidefaults to the convenient, batteries-included path: TUI + Web + remote schema loading.schemauidefaults totui + json, so library consumers keep JSON support without pulling in Web or remote/network-related surface area by default.json,yaml, andtomlare real code-level gates. Keep at least one of them enabled; disabling all three triggers a clear compile-time error.
References:
- JSON Schema
$schema: https://json-schema.org/understanding-json-schema/reference/schema - Taplo directives (
#:schema): https://taplo.tamasfe.dev/configuration/directives.html - YAML language server modeline: https://github.com/redhat-developer/yaml-language-server
[dependencies]
schemaui = "0.16.2"
serde_json = "1"use anyhow::Result;
use schemaui::prelude::*;
use serde_json::json;
fn main() -> Result<()> {
let schema = json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Service Runtime",
"type": "object",
"properties": {
"metadata": {
"type": "object",
"properties": {
"serviceName": {"type": "string"},
"environment": {
"type": "string",
"enum": ["dev", "staging", "prod"]
}
},
"required": ["serviceName"]
},
"runtime": {
"type": "object",
"properties": {
"http": {
"type": "object",
"properties": {
"host": {"type": "string", "default": "0.0.0.0"},
"port": {"type": "integer", "minimum": 1024, "maximum": 65535}
}
}
}
}
},
"required": ["metadata", "runtime"]
});
let config = serde_json::json!({
"metadata": { "name": "demo-service" },
"runtime": { "http": { "host": "127.0.0.1", "port": 8080 } }
});
let value = SchemaUI::new(config)
.with_schema(schema)
.with_title("SchemaUI Demo")
.with_description("Edit an existing config against a validation schema")
.run(FrontendOptions::Tui(UiOptions::default()))?;
println!("{}", serde_json::to_string_pretty(&value)?);
Ok(())
}For library integrations, the main entry points are:
- High-level runtime:
SchemaUI,DocumentInput,FrontendOptions,ServeOptions, andUiOptions - TUI runtime:
crate::tui::session::TuiFrontendfor custom frontend injection viaSchemaUI::run_with_frontend - TUI state:
crate::tui::state::*(for exampleFormState,FormCommand,FormEngine,SectionState) - Schema backend:
crate::ui_ast::build_ui_asttogether withcrate::tui::model::form_schema_from_ui_ast(buildsFormSchemafrom the canonical UI AST)
┌─────────────┐ parse/merge ┌───────────────┐ layout + typing ┌───────────────┐
│ io::input ├─────────────────▶│ schema ├───────────────────────▶│ tui::state │
└─────────────┘ │ (loader / │ │ (FormState, │
│ resolver / │ │ sections, │
┌─────────────┐ emit Value │ build_form_ │ FormSchema │ reducers) │
│ io::output ◀──────────────────┴────pipeline───┘ └────────┬──────┘
└─────────────┘ focus/edits│
│
┌──────────▼──────────┐
│ tui::app::runtime │
│ (InputRouter, │
│ overlays, status) │
└──────────┬──────────┘
│ draw
┌──────────▼──────────┐
│ tui::view::* │
│ (ratatui view) │
└─────────────────────┘
This layout mirrors the actual modules under src/, making it easy to map any
code change to its architectural responsibility.
io::input::parse_document_strconverts JSON/YAML/TOML (viaserde_json,serde_yaml,toml) intoserde_json::Value. Feature flags (json,yaml,toml) keep dependencies lean, and the same gates also driveDocumentFormatparsing/probing at compile time.schema_from_data_value/strinfers schemas from live configs, injecting draft-07 metadata and defaults so UIs load pre-existing values.schema_with_defaultsmerges canonical schemas with user data, propagating defaults throughproperties,patternProperties,additionalProperties,dependencies,dependentSchemas, arrays, and$reftargets without mutating the original tree.io::output::OutputOptionsencapsulates serialization format, pretty/compact toggle, and a vector ofOutputDestination::{Stdout, File}. Multiple destinations are supported; conflicts are caught before emission.OutputOptions::renderturns the finalserde_json::Valueinto JSON/YAML/TOML text, andOutputOptions::writesends that payload to stdout/files explicitly afterSchemaUI::run*returns.
build_ui_ast resolves the schema into the canonical UI AST, and
form_schema_from_ui_ast maps that tree into FormSection/FieldSchema for
the TUI runtime:
| Schema feature | Resulting control |
|---|---|
type: string, integer, number |
Inline text editors with numeric guards |
type: boolean |
Toggle/checkbox |
enum |
Popup selector (single or multi-select for array enums) |
| Arrays | Inline list summary + overlay editor per item |
patternProperties, propertyNames, additionalProperties |
Key/Value editor with schema-backed validation |
$ref, definitions |
Resolved before layout; treated like inline schemas |
oneOf / anyOf |
Variant chooser + overlay form, keeps inactive variants out of the final payload |
Root objects spawn tabs; nested objects become sections with breadcrumb titles.
Every field records its JSON pointer (for example /runtime/http/port) so focus
management and validation can map errors back precisely.
jsonschema::validator_forcompiles the complete schema once whenSchemaUI::runbegins.- Each edit dispatches
FormCommand::FieldEdited.FormEnginerebuilds the current document viaFormState::try_build_value, runs the validator, and feeds errors back intoFieldStateor the global status line. - Overlays (composite variants, key/value maps, list entries) spin up their own validators built from the sub-schema currently being edited. Nested overlays live on a stack, so each level validates in place before changes flow back to the parent form.
┌─────────────┐ parse schema ┌─────────────────┐ inflate state ┌────────────┐
│ SchemaUI::run├────────────▶│ domain::parse ├───────────────▶│ FormState │
└─────┬───────┘ │ (schema::layout)│ └─────┬──────┘
│ validator_for() └─────────────────┘ edits │
│ ┌──────▼─────────┐
└────────────────────────────────────────────────────── ▶│ app::runtime │
│ (status, input)│
└──────┬─────────┘
│ FormCommand
┌──────▼──────────┐
│ FormEngine │
│ + jsonschema │
└─────────────────┘
App is the sole owner of FormState; even overlay edits flow through
FormEngine so validation rules stay centralized.
- Single source for shortcuts –
keymap/default.keymap.jsonlists every shortcut (context, combos, action). Theapp::keymap::keymap_source!()macro pulls this file into the binary,InputRouteruses it to classifyKeyEvents, and the runtime footer renders help text from the same data—keeping docs and behavior DRY. - Root tabs & sections – focus cycles with
Ctrl+J / Ctrl+L(roots) andCtrl+Tab / Ctrl+Shift+Tab(sections). OrdinaryTab/Shift+Tabwalk individual fields. - Fields – render labels, descriptions, and inline error messages. Enum/composite fields show the current selection; arrays summarize length and selected entry.
- Popups & overlays – pressing
Enteropens a popup for enums/oneOf selectors;Ctrl+Epushes a full-screen overlay editor for composites, key/value pairs, and array items. Overlays expose collection shortcuts (Ctrl+N,Ctrl+D,Ctrl+←/→,Ctrl+↑/↓),Ctrl+Ssaves the active level without closing, andEsc/Ctrl+Qpops a single overlay. - Status & help – the footer highlights dirty state, outstanding validation errors, and context-aware help text. When auto-validate is enabled, each edit updates these counters immediately.
| Shortcut | Action | Kind |
|---|---|---|
Tab / Down |
Next field | command |
BackTab / Up |
Previous field | command |
Ctrl+Tab |
Next section | command |
Ctrl+Shift+Tab |
Previous section | command |
Ctrl+L |
Next root tab | command |
Ctrl+J |
Previous root tab | command |
Enter |
Open popup / apply selection | command |
Ctrl+E |
Open composite editor | command |
Ctrl+S |
Save & validate (overlays stay open) | command |
Ctrl+Q / Ctrl+C |
Quit (confirm if dirty) | command |
Esc |
Cancel / clear status (overlays: pop current level) | command |
Ctrl+? / Ctrl+H |
Show help and error summary | command |
| Shortcut | Action | Kind |
|---|---|---|
Ctrl+E |
Open composite editor | command |
Ctrl+N |
Add entry | command |
Ctrl+D |
Remove entry | command |
Ctrl+Left |
Select previous entry | command |
Ctrl+Right |
Select next entry | command |
Ctrl+Up |
Move entry up | command |
Ctrl+Down |
Move entry down | command |
Ctrl+? / Ctrl+H |
Show help and error summary | command |
| Shortcut | Action | Kind |
|---|---|---|
Tab / Down |
Next field | command |
BackTab / Up |
Previous field | command |
Ctrl+N |
Add entry | command |
Ctrl+D |
Remove entry | command |
Ctrl+Left |
Select previous entry | command |
Ctrl+Right |
Select next entry | command |
Ctrl+Up |
Move entry up | command |
Ctrl+Down |
Move entry down | command |
Ctrl+S |
Save & validate (overlays stay open) | command |
Esc |
Cancel / clear status (overlays: pop current level) | command |
Ctrl+? / Ctrl+H |
Show help and error summary | command |
| Shortcut | Action | Kind |
|---|---|---|
Esc |
Close popup | command |
Up |
Select previous popup option | command |
Down |
Select next popup option | command |
Space |
Toggle popup option | command |
Enter |
Apply popup selection | command |
| Shortcut | Action | Kind |
|---|---|---|
Esc / Ctrl+H / Ctrl+? |
Close help | command |
Tab |
Next error page | command |
BackTab |
Previous error page | command |
Up / k |
Scroll shortcuts up | command |
Down / j |
Scroll shortcuts down | command |
PageUp |
Page shortcuts up | command |
PageDown |
Page shortcuts down | command |
Home |
Jump shortcuts to top | command |
End |
Jump shortcuts to bottom | command |
h |
Scroll error text left | command |
l |
Scroll error text right | command |
| Shortcut | Action | Kind |
|---|---|---|
Left |
Move cursor left | local edit |
Right |
Move cursor right | local edit |
Home |
Jump to line start | local edit |
End |
Jump to line end | local edit |
Backspace |
Delete previous character | local edit |
Delete |
Delete next character | local edit |
Ctrl+W |
Delete previous word | local edit |
Ctrl+Z |
Undo text edit | local edit |
Ctrl+Y |
Redo text edit | local edit |
| Shortcut | Action | Kind |
|---|---|---|
Left |
Step value down | local edit |
Right |
Step value up | local edit |
Shift+Left |
Fast step value down | local edit |
Shift+Right |
Fast step value up | local edit |
Backspace |
Delete previous character | local edit |
Delete |
Delete next character | local edit |
Ctrl+Z |
Undo numeric edit | local edit |
Ctrl+Y |
Redo numeric edit | local edit |
| Shortcut | Action | Kind |
|---|---|---|
Space / Left / Right |
Toggle boolean value | local edit |
Enter |
Open popup / apply selection | command |
| Shortcut | Action | Kind |
|---|---|---|
Up / Left |
Previous enum option | local edit |
Down / Right |
Next enum option | local edit |
Enter |
Open popup / apply selection | command |
| Shortcut | Action | Kind |
|---|---|---|
Enter |
Open popup / apply selection | command |
| Shortcut | Action | Kind |
|---|---|---|
Left |
Previous composite variant | local edit |
Right |
Next composite variant | local edit |
Enter |
Open popup / apply selection | command |
| Shortcut | Action | Kind |
|---|---|---|
Backspace |
Delete previous array buffer character | local edit |
Delete |
Clear array buffer | local edit |
Put every shortcut into keymap/default.keymap.json, so runtime logic, help
overlays, and generated README shortcut references all consume a single source
of truth.
-
Format – each JSON object declares an
id, human-readabledescription, bilingualdescriptionZh,contexts(any of"default","collection","overlay","popup","help","text","numeric","boolean","enum","multiSelect","composite","arrayBuffer"), anactiondiscriminated union, and a list of textualcombos. For example:{ "id": "list.move.up", "description": "Move entry up", "descriptionZh": "条目上移", "contexts": ["collection", "overlay"], "action": { "kind": "ListMove", "delta": -1 }, "combos": ["Ctrl+Up"] } -
Macro + parser –
app::keymap::keymap_source!()include_str!s the JSON,once_cell::sync::Lazyparses it once at startup, and each combo is compiled into aKeyPattern(key code, required modifiers, pretty display string). -
Integration –
InputRouter::classifydelegates tokeymap::classify_key, which returns theKeyActionembedded in the JSON.keymap::help_textfilters bindings byKeymapContext, concatenating snippets used byStatusLineand overlay instructions. -
Generated docs –
build.rsparseskeymap/default.keymap.jsonand refreshes the shortcut blocks inREADME.mdandREADME.ZH.mdusing explicit HTML markers, so normal Cargo builds keep the bilingual reference in sync with runtime behavior. -
Extending – to add a shortcut, edit the JSON, choose the contexts that should expose the help text, and wire the resulting
KeyActioninsideKeyBindingMapif a new semantic command is introduced.
| Layer | Module(s) | Responsibilities |
|---|---|---|
| Ingestion | io::input, schema::loader, schema::resolver |
Parse JSON/TOML/YAML, resolve $ref, and normalize metadata. |
| Layout typing | ui_ast::build_ui_ast, tui::model::form_schema_from_ui_ast |
Produce FormSchema (roots/sections/fields) from the canonical UI AST. |
| Form state | tui::state::{form_state, section, field} |
Track focus, pointers, dirty flags, coercions, and errors. |
| Commands & reducers | tui::state::{actions, reducers}, tui::app::validation |
Define FormCommand, mutate state, and route validation results. |
| Runtime controller | tui::app::{runtime, overlay, popup, status, keymap} |
Event loop, InputRouter dispatch, overlay lifecycle, help text, status updates. |
| Presentation | tui::view and tui::view::components::* |
Render tabs, field lists, popups, overlays, and footer via ratatui. |
Each module is kept under ~600 LOC (hard cap 800) to honor the KISS principle and make refactors manageable.
The installed binary is always named schemaui, so the normal entry point is
schemaui -c ./config.json.
Choose one of the supported channels:
Build from crates.io with Cargo.
cargo install schemaui-cliFetch prebuilt GitHub release binaries through cargo-binstall.
cargo binstall schemaui-cliInstall from the repository tap on macOS or Linux.
brew install YuniqueUnic/schemaui/schemauiInstall on Windows from the repository-hosted Scoop manifest.
scoop install https://raw.githubusercontent.com/YuniqueUnic/schemaui/main/packaging/scoop/schemaui-cli.jsonDownload the matching archive from
https://github.com/YuniqueUnic/schemaui/releases/latest, extract schemaui /
schemaui.exe, and place it on your PATH.
Use the versioned manifests in packaging/winget with
winget install --manifest <dir>, or submit them upstream to the community
repository.
The installed binary is schemaui. If you omit a mode subcommand, it defaults
to the TUI. Explicit modes:
| Command | Purpose |
|---|---|
schemaui web |
Interactive browser editor (embedded HTTP server) |
schemaui / schemaui tui |
Interactive terminal editor (default) |
schemaui web-snapshot |
Precompute Web session snapshots JSON/TS (no UI) |
schemaui tui-snapshot |
Precompute TUI FormSchema/layout artifacts (no UI) |
schemaui completion <shell> |
Shell completions (completion feature) |
# Web UI (see also Web UI Mode above)
schemaui web --schema ./schema.json --config ./defaults.yaml --port 0 -o -
# equivalent TUI launches
schemaui --schema ./schema.json --config ./defaults.yaml
schemaui tui --schema ./schema.json --config ./defaults.yamlschemaui \
--schema ./schema.json \
--config ./defaults.yaml \
-o - ./config.toml ./config.json┌────────┐ clap args ┌──────────────┐ read stdin/files ┌─────────────┐
│ CLI ├─────────────▶│ InputSource ├─────────────────▶│ io::input │
└────┬───┘ └──────┬───────┘ └────┬────────┘
│ diagnostics │ schema/default Value │
┌────▼─────────┐ ┌──────▼──────┐ |
│Diagnostic │◀───────┤ FormatHint │ │
│Collector │ └──────┬──────┘ │
└────┬─────────┘ │ pass if clean │
│ │ │
┌────▼────────┐ build options └────────────┐ │
│Output logic ├────────────────────────────▶│ OutputOptions │
└────┬────────┘ └────────────┬─────┘
│ SchemaUI::new / with_* ┌───▼────────┐
└──────────────────────────────────────────────▶│ SchemaUI │
│ (library) │
└────────────┘
- Inputs –
--schema/--configaccept file paths,file://orhttp(s)URLs (CLIremote-schema), inline payloads, or-for stdin (not both schema and config on stdin at once). Config-only runs infer a schema viaschema_from_data_value(or embedded$schema/#:schema/ YAML modeline). - Diagnostics –
DiagnosticCollectorgathers format issues, feature-flag mismatches, stdin conflicts, and existing output files before the UI runs. - Outputs –
-o/--outputtakes one or more space-separated destinations in a single occurrence and may mix file paths with-for stdout (because the flag is greedy, place it after all other flags and do not repeat it). With no destination, the tool writes to stdout; use--temp-file <PATH>for an explicit fallback file. Extensions pick the format; conflicting extensions are rejected. - Common flags –
--no-pretty(compact),--force/--yes(overwrite),--title/--description(forwarded to the UI). - Web-only flags –
--host/-l(default127.0.0.1),--port/-p(default0= free port). - Shell completion –
schemaui completion <bash|zsh|fish|powershell>(requires thecompletionfeature).
Deep dive: docs/en/cli_usage.md · Chinese:
docs/zh/cli_usage.zh.md.
| Crate | Purpose |
|---|---|
serde, serde_json, serde_yaml, toml |
Parsing and serializing schema/config data. |
schemars |
Draft-07 schema representation used by the schema module. |
jsonschema |
Runtime validation for forms and overlays. |
ratatui |
Rendering widgets, layouts, overlays, and footer. |
crossterm |
Terminal events consumed by InputRouter. |
indexmap |
Order-preserving maps for schema traversal. |
once_cell |
Lazy parsing of the keymap JSON. |
clap, clap_complete, anyhow (CLI) |
Argument parsing, shell completion, and ergonomic diagnostics. |
README.md– overview + architecture snapshot (source of truth).README.ZH.md– Chinese overview kept in sync with this README.- a2ui-ask – the AI-agent skill: prompt templates, helper scripts, examples, and tests (see AI Agent Integration).
docs/en/structure_design.md– detailed schema/layout/runtime design with flow diagrams.docs/zh/structure_design.md– Chinese mirror of the architecture guide.docs/en/cli_usage.md– CLI-specific manual (inputs, outputs, piping, TUI/Web modes, samples).docs/zh/cli_usage.zh.md– Chinese mirror of the CLI usage guide.docs/en/control-hints.md– thex-control/x-slider-marks/x-visible-whenpresentation hints that choose a field's control from the schema.docs/en/web-ui-architecture-and-refactor-spec.md– Web UI architecture.docs/en/web-v1-theming-and-wasm.md– the Web Session API v1 contract, theming, pluggable frontends, and the WASM/Playground/Edge API plan.docs/en/decisions/– the individual ADRs behind that plan (contract versioning, theming, WASM/hosting).docs/web.mix.png– Web UI screenshot (schema form + live JSON preview);web.mix.dark.pngis the dark-mode variant used automatically by the README.docs/web.controls.*.png– per-control-family screenshots (sliders, ranges, choices, appearance) captured fromexamples/controls-gallery.schema.json.
- Run
cargo fmt && cargo testregularly; most modules embed their tests byinclude!ing files fromtests/so private APIs stay covered. - Keep modules below ~600 LOC (hard cap 800). Split helpers as soon as behavior grows to keep KISS intact.
- Prefer mature crates (
serde_*,schemars,jsonschema,ratatui,crossterm) over bespoke code unless the change is trivial. - Update
docs/*whenever pipelines, shortcuts, or CLI semantics evolve so user-facing documentation stays truthful.
- parse json schema at runtime and generate a TUI
- parse json schema at runtime and generate a Web UI
- parse json schema at compile time Then generate the code for TUI, expose necessary APIs for runtime.
- parse json schema at compile time Then generate the code for Web UI, expose necessary APIs for runtime.
- parse json schema at runtime and generate a Interactive CLI
- parse json schema at compile time Then generate the code for Interactive CLI, expose necessary APIs for runtime.
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contributions are welcome! Please feel free to submit a Pull Request.
Happy hacking!



