a2ui-ask — collect user input via a browser form (file output)
Interactive UI for AI agents: turn structured questions into browser forms. The
rendering engine is the schemaui binary (schemaui web) from
YuniqueUnic/schemaui; this skill
wraps it with scripts that handle binding, browser wakeup, timeout, and the
file-output contract.
Prerequisite: the schemaui engine
Check for the binary first: command -v schemaui (or just run the ask script —
it exits with code 3 when the engine is missing).
If missing, offer to install it — you can do this yourself, unattended:
bash scripts/install.sh # macOS / Linux / FreeBSD: auto-detect & install
pwsh scripts/install.ps1 # Windows / PowerShell 7+
Both default to a prebuilt-binary download (no toolchain needed) and support
--dry-run / -DryRun to preview. They fetch from GitHub first and fall back
to the Gitee mirror — same tags, same
asset names — which is what makes them work from mainland China. If a user
reports a stalled or failed install, re-run with the mirror pinned rather than
retrying GitHub:
bash scripts/install.sh --source gitee
pwsh scripts/install.ps1 -Source gitee
The brew / scoop / winget manifests still hardcode GitHub download URLs, so on a
blocked network prefer --source gitee or cargo install schemaui-cli. Full
channel list: see install.md in this repository.
Non-negotiables
- Web form only, via
schemaui web. Terminal prompts are forbidden — your
process has no TTY, so a TUI renders nowhere and blocks forever.
- Bind
0.0.0.0 so the form is reachable from localhost, LAN, SSH tunnels, and
port-forwards.
- Output to a FILE under
.schemaui/answers/ — stdout-only is forbidden.
- Tell the user the URL and the answer file path in your response, in the same
turn as the tool call.
- Block on the subprocess; read the answer file when it exits.
- Fall back to plain text on any failure — never abort the task.
-o is greedy: every other flag goes BEFORE -o; extra destinations are
space-separated in the same -o (-o answer.json -), never repeated.
Asking well (grill-me discipline, form edition)
Explore before asking. If the codebase, git history, or
.schemaui/answers/ can answer the question, answer it yourself. The form is
for decisions only the user can make.
One form per decision cluster. Batch the questions of one topic (e.g.
"deployment config") into a single form; do not spawn ten forms for ten
questions, and do not interrogate serially in chat either.
Every question ships a recommended answer. Put your recommendation in the
field's default so the user can confirm with one click instead of typing.
Say in chat what you recommended and why.
Keep the recommendation out of the option label. default is the one
place the recommendation lives; the label is what lands in the answer file.
Suffixing options with (推荐) / (recommended) duplicates the hint and
then leaks it into the data, so every downstream consumer has to strip it.
Explain the recommendation in the field's description instead ("推荐
X:因为…"), where it stays readable without contaminating the value.
Speak the user's language. Write every title and description in the
language the user is chatting in, and say in the description what the answer
changes downstream ("drives whether we need rate limiting").
Pick the control, don't settle for text boxes. A slider for a scale, a
two-handle range for a window, a colour picker for a colour, a segmented
control for a handful of short options, x-visible-when for anything that
only matters sometimes. Choosing from the gallery is what makes the form
pleasant to fill in — see
Picking the control.
Always leave an escape hatch, and keep it out of the way. Your options and
defaults are guesses, so every select gets an 其他/other option plus a
sibling <field>_custom text input. Gate that input with x-visible-when so
it only shows up once the user actually picks the escape option — a
permanently visible empty box reads as a question they still owe you an answer
to:
"delivery_form": { "enum": ["单个 HTML 文件", "其他"], "default": "单个 HTML 文件" },
"delivery_form_custom": {
"type": "string",
"title": "交付形式 · 自定义说明",
"description": "选了「其他」时填写:你想要的交付形式。",
"x-visible-when": { "field": "delivery_form", "op": "equals", "value": "其他" }
}
Use "op": "contains" when the controlling field is a multi-select (the
option list has to contain the escape value). The rule must name a sibling
of the same object: a typo is rejected when the form loads rather than hiding
the field forever. The controlling field's default must not be the escape
value, or the input would be visible from the start.
Question type → schema cheat sheet
| You need |
Schema shape |
Renders as |
| short text input |
{"type": "string", "minLength": …, "pattern": …} |
inline text field |
| long text input |
{"type": "string", "x-multiline": true} |
multi-line text area |
| number |
{"type": "integer", "minimum": …, "maximum": …} |
numeric field with guards |
| number on a scale |
{"type": "…", "minimum": …, "maximum": …, "x-control": "slider"} |
slider with a live readout |
| slider with labels |
slider + "x-slider-marks": [{"value": …, "label": …}, …] |
slider with labelled stops |
| two-number interval |
{"type": "array", "items": {"type": "number"}, "minItems": 2, "maxItems": 2, "minimum": …, "maximum": …, "x-control": "range"} |
two-handle range slider |
| colour |
{"type": "string", "x-control": "color"} |
colour picker, not a text box |
| few short options |
{"type": "string", "enum": [...], "x-control": "segmented"} |
inline segmented control |
| few verbose options |
{"type": "string", "enum": [...], "x-control": "radio"} |
stacked radio group |
| yes/no |
{"type": "boolean"} |
toggle |
| yes/no, part of a set |
{"type": "boolean", "x-control": "checkbox"} |
checkbox |
| single select |
{"type": "string", "enum": [...]} |
popup selector |
| multi select |
{"type": "array", "items": {"enum": [...]}, "uniqueItems": true} |
checkbox list (one per option) |
| select + escape hatch |
enum: [..., "其他"] plus a sibling "<field>_custom" carrying x-visible-when |
selector, then a text field once 「其他」 is picked |
| conditional field |
"x-visible-when": {"field": <sibling>, "op": "equals"|"contains", "value": …} |
hidden until the sibling matches |
| pick-one-with-config |
{"oneOf": [{"title": "A", …}, {"title": "B", …}]} |
variant chooser + subform |
| grouped fields |
{"type": "object", "properties": {…}} |
nested section |
| list of records |
{"type": "array", "items": {"type": "object", "properties": {…}}} |
list + per-entry overlay |
| free-form key/value |
{"type": "object", "additionalProperties": {"type": "string"}} |
key/value editor |
x-visible-when is not only for escape hatches: use it whenever a question only
applies conditionally (has_changes → change_notes, cache.enabled →
cache.ttl_seconds). x-multiline is for anything you would expect the user to
write more than one line into — a goal, a description, a change log.
Picking the control: use the gallery
A form of plain text boxes makes the user do the translating. Reach for the
control that matches the value — the gallery is the catalogue, and using it is
what turns a wall of inputs into something people actually want to fill in:
- a percentage, a count on a scale, a weight →
"slider" (add
"x-slider-marks" when the stops have names);
- a window with two ends (hours, price, days) →
"range" — an array of two with
minItems/maxItems: 2 and minimum/maximum on the array itself;
- a colour →
"color"; a 2-4 option enum → "segmented"; options with longer
labels → "radio"; a long list → leave it a select;
- a boolean that reads as "one of the things I'm choosing" →
"checkbox"; one
that flips a mode right now → leave it a switch;
- a paragraph-length answer →
x-multiline (or "textarea"), never a one-line
box;
- anything that only matters sometimes →
x-visible-when, on the same object.
Don't make every field a special case either: a hint earns its place when it
removes typing or removes a wrong answer, not for decoration.
x-control is a request, and bounds stay validation keywords: a slider needs
minimum/maximum on the value, and a hint the engine cannot honour falls back
to that shape's default control rather than failing. Slider/range/colour/
segmented/radio hints need schemaui ≥ 0.14 / schemaui-cli ≥ 0.8; an older
engine ignores unknown x- keywords, so the form still runs — it just shows
plain inputs everywhere.
Read these before writing your own: examples/web-research-brief.schema.json
(中文 — the worked reference: every control in the gallery, escape hatches,
conditional fields, oneOf, a record list, a key/value map),
examples/feature-brief.schema.json (English, every control type) and
examples/invoice-reimbursement.schema.json (中文, escape hatches on every
select). The engine's own gallery,
schemaui/examples/controls-gallery.schema.json,
shows every hint value side by side.
Steps
Generate a draft-07 JSON Schema for the question (top-level type: object;
per-field title / description / default; enum, minimum / maximum,
nested properties as needed — see the cheat sheet). A form with an 其他
option and no x-visible-when on its companion field, or a paragraph-length
answer squeezed into a one-line input, is an unfinished form.
Run the helper script from this skill's directory — it spawns the server,
prints the URL, opens the user's browser, and writes the answer file. The
timeout is handed to the engine, which counts it down on screen and ends the
session itself:
cat > /tmp/question.json <<'EOF'
{ "...": "your generated schema" }
EOF
python3 scripts/ask.py \
--schema /tmp/question.json \
--topic <topic> \
--title "<question summary>" \
--description "<one-line context>" \
--timeout 300
No Python? Use the twin for your platform — same flags, same stdout contract:
bash scripts/ask.sh --schema … (macOS/Linux) or
pwsh scripts/ask.ps1 -Schema … (Windows / PowerShell 7+).
Prefer piping the schema straight in (--schema -) when you just generated
it — the script persists it under .schemaui/schemas/<topic>-<ts>.json for
the audit trail.
In your response text, relay what the script prints:
Form ready at http://localhost:8787 — I'll wait for you to fill it in. Your
answers will be saved to .schemaui/answers/<file>.json.
Say how long they have. The form shows a live countdown next to its title and
turns amber, then red, as the deadline nears — but the user has not seen it
yet when they read your message, so name the budget (--timeout 300 → "about
5 minutes") and note that the form closes itself when it runs out. Raise
--timeout for a long form rather than letting it expire mid-fill: nothing
is saved on timeout, by design.
On remote/headless runs pass --no-open and relay SCHEMAUI_LAN_URL (or the
forwarded URL) instead of opening a browser locally.
The script blocks until the user clicks Save & Exit. On exit 0 it prints
the answer JSON and SCHEMAUI_RESULT=<path>. Read the answer file and
continue the task, citing field paths:
Per .schemaui/answers/deploy-config-20260916-101500.json:
environment = staging, replicas = 3.
Honor the escape hatches: when a field's value is 其他/other, the real
answer is in the sibling <field>_custom — use that, not the literal
"other".
Exit codes and fallback
| Code |
Meaning |
Your action |
| 0 |
answer written |
read the file, continue |
| 3 |
schemaui not found |
offer to run scripts/install.sh / install.ps1 and retry; --source gitee if github.com is blocked |
| 4 |
timeout (default 5m) |
the form showed a countdown and closed itself; nothing was saved. Fall back to plain text, tell the user |
| 5 |
cancelled / failed |
fall back to plain text, tell the user |
| 6 |
bad schema/config |
fix the schema or fall back, tell the user |
Never hard-fail the task because a question could not be asked.
Raw CLI (when the scripts are unavailable)
mkdir -p .schemaui/schemas .schemaui/answers
schemaui web \
--host 0.0.0.0 --port 8787 \
--schema .schemaui/schemas/<topic>-<timestamp>.json \
--title "<question summary>" \
--timeout 300 \
--force \
-o .schemaui/answers/<topic>-<timestamp>.json
The server announces <title> schemaui UI available at http://<addr>/ on
stderr, followed by Session closes automatically in <budget> when a
--timeout was given — that second line is how you learn the deadline without
parsing the UI. Port 8787 busy → retry once with --port 0 and parse the
http://… line from stderr. A session that hits its deadline exits 4 and
writes nothing; drop --timeout (or pass 0) for no deadline at all. Optional
stdout echo: append - after the answer path (-o <file> -) — only when your
runtime shows tool output live and the JSON is small; default is file-only.
Sensitive input
Use --host 127.0.0.1 and tell remote users to tunnel first:
ssh -L 8787:localhost:8787 <host>.
Past answers
Before asking, check .schemaui/answers/ — the user may have already answered
something similar. Reuse a prior answer as defaults via --config.
Examples
Six runnable forms live in examples/ (each with a .defaults.json carrying
the recommended answers):
| Example |
Scenario |
env-schema.json |
minimal 4-field deploy form — first smoke test |
web-research-brief.schema.json |
research: scope a web-research task — every control in the gallery (中文) |
feature-brief.schema.json |
12-question requirements brief, every control type + both hints (EN) |
invoice-reimbursement.schema.json |
office: invoice & expense reimbursement (中文, escape hatches) |
ecommerce-main-image.schema.json |
design: e-commerce hero image specs — sizes, fonts, colors, oneOf backgrounds (中文) |
seo-diagnosis.schema.json |
SEO triage: site, issues, keywords, competitors (中文) |
python3 scripts/ask.py \
--schema examples/web-research-brief.schema.json \
--config examples/web-research-brief.defaults.json \
--title "联网调研任务确认"
1---2name: a2ui-ask3description: When the user must choose among options or fill in structured config, spawn a browser form (powered by schemaui Web UI, bound to 0.0.0.0), open the user's browser, and write the result to .schemaui/answers/. Never use a terminal/TUI prompt — the agent process has no TTY attachment. Trigger phrases: "give me a form", "ask me via form", "configure", "pick a deploy environment", "I need to choose", "a2ui", "给我个表单", "配置一下", "用表单问我", "问我几个问题".4---56# a2ui-ask — collect user input via a browser form (file output)78Interactive UI for AI agents: turn structured questions into browser forms. The9rendering engine is the `schemaui` binary (`schemaui web`) from10[YuniqueUnic/schemaui](https://github.com/YuniqueUnic/schemaui); this skill11wraps it with scripts that handle binding, browser wakeup, timeout, and the12file-output contract.1314## Prerequisite: the schemaui engine1516Check for the binary first: `command -v schemaui` (or just run the ask script —17it exits with code 3 when the engine is missing).1819If missing, offer to install it — you can do this yourself, unattended:2021```bash22bash scripts/install.sh # macOS / Linux / FreeBSD: auto-detect & install23pwsh scripts/install.ps1 # Windows / PowerShell 7+24```2526Both default to a prebuilt-binary download (no toolchain needed) and support27`--dry-run` / `-DryRun` to preview. They fetch from GitHub first and fall back28to the [Gitee mirror](https://gitee.com/Credhat/schemaui) — same tags, same29asset names — which is what makes them work from mainland China. If a user30reports a stalled or failed install, re-run with the mirror pinned rather than31retrying GitHub:3233```bash34bash scripts/install.sh --source gitee35pwsh scripts/install.ps1 -Source gitee36```3738The brew / scoop / winget manifests still hardcode GitHub download URLs, so on a39blocked network prefer `--source gitee` or `cargo install schemaui-cli`. Full40channel list: see `install.md` in this repository.4142## Non-negotiables43441. Web form only, via `schemaui web`. Terminal prompts are forbidden — your45 process has no TTY, so a TUI renders nowhere and blocks forever.462. Bind `0.0.0.0` so the form is reachable from localhost, LAN, SSH tunnels, and47 port-forwards.483. Output to a FILE under `.schemaui/answers/` — stdout-only is forbidden.494. Tell the user the URL and the answer file path in your response, in the same50 turn as the tool call.515. Block on the subprocess; read the answer file when it exits.526. Fall back to plain text on any failure — never abort the task.537. `-o` is greedy: every other flag goes BEFORE `-o`; extra destinations are54 space-separated in the same `-o` (`-o answer.json -`), never repeated.5556## Asking well (grill-me discipline, form edition)5758- **Explore before asking.** If the codebase, git history, or59 `.schemaui/answers/` can answer the question, answer it yourself. The form is60 for decisions only the user can make.61- **One form per decision cluster.** Batch the questions of one topic (e.g.62 "deployment config") into a single form; do not spawn ten forms for ten63 questions, and do not interrogate serially in chat either.64- **Every question ships a recommended answer.** Put your recommendation in the65 field's `default` so the user can confirm with one click instead of typing.66 Say in chat what you recommended and why.67- **Keep the recommendation out of the option label.** `default` is the one68 place the recommendation lives; the label is what lands in the answer file.69 Suffixing options with `(推荐)` / `(recommended)` duplicates the hint and70 then leaks it into the data, so every downstream consumer has to strip it.71 Explain the recommendation in the field's `description` instead ("推荐72 X:因为…"), where it stays readable without contaminating the value.73- **Speak the user's language.** Write every `title` and `description` in the74 language the user is chatting in, and say in the description what the answer75 changes downstream ("drives whether we need rate limiting").76- **Pick the control, don't settle for text boxes.** A slider for a scale, a77 two-handle range for a window, a colour picker for a colour, a segmented78 control for a handful of short options, `x-visible-when` for anything that79 only matters sometimes. Choosing from the gallery is what makes the form80 pleasant to fill in — see81 [Picking the control](#picking-the-control-use-the-gallery).82- **Always leave an escape hatch, and keep it out of the way.** Your options and83 defaults are guesses, so every select gets an `其他`/`other` option plus a84 sibling `<field>_custom` text input. Gate that input with `x-visible-when` so85 it only shows up once the user actually picks the escape option — a86 permanently visible empty box reads as a question they still owe you an answer87 to:8889 ```json90 "delivery_form": { "enum": ["单个 HTML 文件", "其他"], "default": "单个 HTML 文件" },91 "delivery_form_custom": {92 "type": "string",93 "title": "交付形式 · 自定义说明",94 "description": "选了「其他」时填写:你想要的交付形式。",95 "x-visible-when": { "field": "delivery_form", "op": "equals", "value": "其他" }96 }97 ```9899 Use `"op": "contains"` when the controlling field is a multi-select (the100 option list has to _contain_ the escape value). The rule must name a sibling101 of the same object: a typo is rejected when the form loads rather than hiding102 the field forever. The controlling field's `default` must not be the escape103 value, or the input would be visible from the start.104105## Question type → schema cheat sheet106107| You need | Schema shape | Renders as |108| --------------------- | -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |109| short text input | `{"type": "string", "minLength": …, "pattern": …}` | inline text field |110| long text input | `{"type": "string", "x-multiline": true}` | multi-line text area |111| number | `{"type": "integer", "minimum": …, "maximum": …}` | numeric field with guards |112| number on a scale | `{"type": "…", "minimum": …, "maximum": …, "x-control": "slider"}` | slider with a live readout |113| slider with labels | slider + `"x-slider-marks": [{"value": …, "label": …}, …]` | slider with labelled stops |114| two-number interval | `{"type": "array", "items": {"type": "number"}, "minItems": 2, "maxItems": 2, "minimum": …, "maximum": …, "x-control": "range"}` | two-handle range slider |115| colour | `{"type": "string", "x-control": "color"}` | colour picker, not a text box |116| few short options | `{"type": "string", "enum": [...], "x-control": "segmented"}` | inline segmented control |117| few verbose options | `{"type": "string", "enum": [...], "x-control": "radio"}` | stacked radio group |118| yes/no | `{"type": "boolean"}` | toggle |119| yes/no, part of a set | `{"type": "boolean", "x-control": "checkbox"}` | checkbox |120| single select | `{"type": "string", "enum": [...]}` | popup selector |121| multi select | `{"type": "array", "items": {"enum": [...]}, "uniqueItems": true}` | checkbox list (one per option) |122| select + escape hatch | `enum: [..., "其他"]` plus a sibling `"<field>_custom"` carrying `x-visible-when` | selector, then a text field once 「其他」 is picked |123| conditional field | `"x-visible-when": {"field": <sibling>, "op": "equals"\|"contains", "value": …}` | hidden until the sibling matches |124| pick-one-with-config | `{"oneOf": [{"title": "A", …}, {"title": "B", …}]}` | variant chooser + subform |125| grouped fields | `{"type": "object", "properties": {…}}` | nested section |126| list of records | `{"type": "array", "items": {"type": "object", "properties": {…}}}` | list + per-entry overlay |127| free-form key/value | `{"type": "object", "additionalProperties": {"type": "string"}}` | key/value editor |128129`x-visible-when` is not only for escape hatches: use it whenever a question only130applies conditionally (`has_changes` → `change_notes`, `cache.enabled` →131`cache.ttl_seconds`). `x-multiline` is for anything you would expect the user to132write more than one line into — a goal, a description, a change log.133134## Picking the control: use the gallery135136A form of plain text boxes makes the user do the translating. Reach for the137control that matches the value — the gallery is the catalogue, and using it is138what turns a wall of inputs into something people actually want to fill in:139140- a percentage, a count on a scale, a weight → `"slider"` (add141 `"x-slider-marks"` when the stops have names);142- a window with two ends (hours, price, days) → `"range"` — an array of two with143 `minItems`/`maxItems: 2` and `minimum`/`maximum` on the array itself;144- a colour → `"color"`; a 2-4 option enum → `"segmented"`; options with longer145 labels → `"radio"`; a long list → leave it a `select`;146- a boolean that reads as "one of the things I'm choosing" → `"checkbox"`; one147 that flips a mode right now → leave it a `switch`;148- a paragraph-length answer → `x-multiline` (or `"textarea"`), never a one-line149 box;150- anything that only matters sometimes → `x-visible-when`, on the same object.151152Don't make every field a special case either: a hint earns its place when it153removes typing or removes a wrong answer, not for decoration.154155`x-control` is a _request_, and bounds stay validation keywords: a slider needs156`minimum`/`maximum` on the value, and a hint the engine cannot honour falls back157to that shape's default control rather than failing. Slider/range/colour/158segmented/radio hints need `schemaui` ≥ 0.14 / `schemaui-cli` ≥ 0.8; an older159engine ignores unknown `x-` keywords, so the form still runs — it just shows160plain inputs everywhere.161162Read these before writing your own: `examples/web-research-brief.schema.json`163(中文 — the worked reference: every control in the gallery, escape hatches,164conditional fields, `oneOf`, a record list, a key/value map),165`examples/feature-brief.schema.json` (English, every control type) and166`examples/invoice-reimbursement.schema.json` (中文, escape hatches on every167select). The engine's own gallery,168[`schemaui/examples/controls-gallery.schema.json`](https://github.com/YuniqueUnic/schemaui/blob/main/examples/controls-gallery.schema.json),169shows every hint value side by side.170171## Steps1721731. Generate a draft-07 JSON Schema for the question (top-level `type: object`;174 per-field `title` / `description` / `default`; `enum`, `minimum` / `maximum`,175 nested `properties` as needed — see the cheat sheet). A form with an `其他`176 option and no `x-visible-when` on its companion field, or a paragraph-length177 answer squeezed into a one-line input, is an unfinished form.1781792. Run the helper script from this skill's directory — it spawns the server,180 prints the URL, opens the user's browser, and writes the answer file. The181 timeout is handed to the engine, which counts it down on screen and ends the182 session itself:183184 ```bash185 cat > /tmp/question.json <<'EOF'186 { "...": "your generated schema" }187 EOF188 python3 scripts/ask.py \189 --schema /tmp/question.json \190 --topic <topic> \191 --title "<question summary>" \192 --description "<one-line context>" \193 --timeout 300194 ```195196 No Python? Use the twin for your platform — same flags, same stdout contract:197 `bash scripts/ask.sh --schema …` (macOS/Linux) or198 `pwsh scripts/ask.ps1 -Schema …` (Windows / PowerShell 7+).199200 Prefer piping the schema straight in (`--schema -`) when you just generated201 it — the script persists it under `.schemaui/schemas/<topic>-<ts>.json` for202 the audit trail.2032043. In your response text, relay what the script prints:205206 > Form ready at http://localhost:8787 — I'll wait for you to fill it in. Your207 > answers will be saved to `.schemaui/answers/<file>.json`.208209 Say how long they have. The form shows a live countdown next to its title and210 turns amber, then red, as the deadline nears — but the user has not seen it211 yet when they read your message, so name the budget (`--timeout 300` → "about212 5 minutes") and note that the form closes itself when it runs out. Raise213 `--timeout` for a long form rather than letting it expire mid-fill: nothing214 is saved on timeout, by design.215216 On remote/headless runs pass `--no-open` and relay `SCHEMAUI_LAN_URL` (or the217 forwarded URL) instead of opening a browser locally.2182194. The script blocks until the user clicks **Save & Exit**. On exit 0 it prints220 the answer JSON and `SCHEMAUI_RESULT=<path>`. Read the answer file and221 continue the task, citing field paths:222223 > Per `.schemaui/answers/deploy-config-20260916-101500.json`:224 > `environment = staging`, `replicas = 3`.225226 Honor the escape hatches: when a field's value is `其他`/`other`, the real227 answer is in the sibling `<field>_custom` — use that, not the literal228 "other".229230## Exit codes and fallback231232| Code | Meaning | Your action |233| ---- | -------------------- | -------------------------------------------------------------------------------------------------------- |234| 0 | answer written | read the file, continue |235| 3 | schemaui not found | offer to run `scripts/install.sh` / `install.ps1` and retry; `--source gitee` if github.com is blocked |236| 4 | timeout (default 5m) | the form showed a countdown and closed itself; nothing was saved. Fall back to plain text, tell the user |237| 5 | cancelled / failed | fall back to plain text, tell the user |238| 6 | bad schema/config | fix the schema or fall back, tell the user |239240Never hard-fail the task because a question could not be asked.241242## Raw CLI (when the scripts are unavailable)243244```bash245mkdir -p .schemaui/schemas .schemaui/answers246schemaui web \247 --host 0.0.0.0 --port 8787 \248 --schema .schemaui/schemas/<topic>-<timestamp>.json \249 --title "<question summary>" \250 --timeout 300 \251 --force \252 -o .schemaui/answers/<topic>-<timestamp>.json253```254255The server announces `<title> schemaui UI available at http://<addr>/` on256stderr, followed by `Session closes automatically in <budget>` when a257`--timeout` was given — that second line is how you learn the deadline without258parsing the UI. Port 8787 busy → retry once with `--port 0` and parse the259`http://…` line from stderr. A session that hits its deadline exits **4** and260writes nothing; drop `--timeout` (or pass `0`) for no deadline at all. Optional261stdout echo: append `-` after the answer path (`-o <file> -`) — only when your262runtime shows tool output live and the JSON is small; default is file-only.263264## Sensitive input265266Use `--host 127.0.0.1` and tell remote users to tunnel first:267`ssh -L 8787:localhost:8787 <host>`.268269## Past answers270271Before asking, check `.schemaui/answers/` — the user may have already answered272something similar. Reuse a prior answer as defaults via `--config`.273274## Examples275276Six runnable forms live in `examples/` (each with a `.defaults.json` carrying277the recommended answers):278279| Example | Scenario |280| ----------------------------------- | ------------------------------------------------------------------------------------ |281| `env-schema.json` | minimal 4-field deploy form — first smoke test |282| `web-research-brief.schema.json` | research: scope a web-research task — **every control in the gallery** (中文) |283| `feature-brief.schema.json` | 12-question requirements brief, every control type + both hints (EN) |284| `invoice-reimbursement.schema.json` | office: invoice & expense reimbursement (中文, escape hatches) |285| `ecommerce-main-image.schema.json` | design: e-commerce hero image specs — sizes, fonts, colors, oneOf backgrounds (中文) |286| `seo-diagnosis.schema.json` | SEO triage: site, issues, keywords, competitors (中文) |287288```bash289python3 scripts/ask.py \290 --schema examples/web-research-brief.schema.json \291 --config examples/web-research-brief.defaults.json \292 --title "联网调研任务确认"293```