CLI Spec¶
Status: implemented —
omnist/cli.pyhas shipped since 2026-06-26. This document specified the command surface before implementation, the same way the model spec and the grammars preceded theirs; it's kept as the normative spec the shipped CLI is checked against. See the CLI reference for the current, example-verified command docs.
1. Command tree¶
omnist format <input> [--compact] [--arrays] [-o OUTPUT] [--json]
omnist convert <input> --from FMT --to FMT [--schema FILE] [--strict] [--report] [--result-format text|json|oml] [--compact] [--arrays] [-o OUTPUT] [--json]
omnist validate <input> --from FMT --schema FILE [--result-format text|json|oml] [--json]
omnist infer <input>... --from FMT [--compact] [--allow-any] [-o OUTPUT] [--json]
omnist check <input> --from FMT --to FMT [--strict] [--result-format text|json|oml] [--json]
omnist schema format <schema-file> [--compact] [-o OUTPUT] [--json]
omnist schema normalize <schema-file> [--compact] [-o OUTPUT] [--json]
omnist schema prune <schema-file> [--compact] [-o OUTPUT] [--json]
omnist schema is-empty <schema-file> [--result-format text|json|oml] [--json]
omnist schema lint <schema-file> [--json] [--severity info|warning]
omnist schema extract <schema-file> --keep label1,label2,... [--compact] [-o OUTPUT] [--json]
omnist schema compatible-with <a> <b> [--result-format text|json|oml] [--json]
omnist schema equivalent <a> <b> [--result-format text|json|oml] [--json]
FMT is one of json|yaml|toml|xml|oml. A schema file is always OSD
(Omnist Schema Definition; parse_schema/to_osd) — schema commands take
no --from/--to.
--json is a shared flag on every command (an argparse parent parser),
specified once in §1a rather than repeated per
command below.
1a. --json (machine mode, shared)¶
Every command accepts --json, with one uniform guarantee:
- On any error —
ParseError/SchemaError/WriteError/DocumentError/OSError, whether raised throughmain()'s top-level handler or caught in a handler for a command-specific exit code (convert --strict's WriteError→1, the--from oml --to omlguard→2,schema extract's SchemaError→1) — the command prints{"ok": false, "message": str(exc), "errors": [{"path","code", "message"}, ...]}to stdout (stderr empty).errorsisParseError.errorswhen the exception is aParseError, else[]. Exit code is unchanged from the non---jsonrun. - On success, the commands with a single structured result —
validate,check,schema is-empty/compatible-with/equivalent— emit that result as JSON on stdout, the same shape--result-format jsonproduces for that command (reusing the existing json encoders; no new shapes). The text-emitting commands (format,convert,infer,schema format/normalize/prune/extract) print their document/schema text unchanged;--jsongoverns only their error shape.
Exit codes are identical with and without --json, everywhere.
Boundary: argparse usage errors (unknown flag, missing required arg) are
not JSON-ified — they occur in parse_args, before any handler runs, and stay
argparse's own stderr message + exit 2. Those are caller bugs, not data errors.
--result-format is unchanged and retained (it additionally offers oml
encoding and does not catch parse errors); --json is the recommended machine
interface. validate --json (issue #182) and schema lint --json predate this
and keep their exact prior output — they are the same shared flag, wired to their
existing richer handlers.
2. Format handling¶
<input>/<a>/<b>may be a file path or-for stdin;-o/--outputmay be a file path or omitted for stdout.--fromis always required wherever it appears, file or stream alike — no extension-based inference.--tois always required onconvert/check— no defaulting.format/schema format/schema normalize/schema prune/schema is-empty/schema lint/schema extract/schema compatible-with/schema equivalenttake no--from/--to: each reads/writes exactly one format (OML or OSD).- Schema files conventionally use
.osd; not enforced.
3. Commands¶
omnist format <input> [--compact] [--arrays] [-o OUTPUT]¶
read_oml(text) → write_oml(node). OML only; --compact
(write_oml(node, indent=None) — single-line output) and --arrays
(write_oml(node, arrays=True) — collapses same-label runs into [...]
array syntax, issue #218) are the flags beyond -o; they combine freely.
omnist format messy.oml -o clean.oml
omnist format messy.oml --compact
omnist format messy.oml --arrays
omnist convert <input> --from FMT --to FMT [--schema FILE] [--strict] [--report] [--result-format text|json|oml] [--compact] [--arrays] [-o OUTPUT]¶
read_<from>(text, schema=...) → write_<to>(node, strict=, report=).
--schema FILE: schema-directed deserialization on read. If the input can't be made to conform, raisesParseError(every problem found), nothing written, exit2.--report: prints whatwrite_<to>adjusted to stderr (encoding per--result-format, defaulttext); the write still happens.--strict: refuses to write at all if anything would need adjusting — exit1.--compact: single-line OML output (write_oml(node, indent=None)) when--to oml; no effect for other--tovalues.--arrays: OML output collapses same-label runs into[...]array syntax (write_oml(node, arrays=True)) when--to oml; no effect for other--tovalues.--from oml --to omlwith no--schemais rejected (exit2, useformat) — a pure no-op otherwise. With--schemait's allowed: real schema-directed materialization, and the only CLI path to it (format/checkhave no--schema). Every other same-format pair (json→json, etc.) is always allowed.- One document in, one document out; no batch mode.
omnist convert order.json --from json --to oml
omnist convert order.xml --from xml --to oml --schema order.osd -o order.oml
omnist convert data.oml --from oml --to oml --schema data.osd -o data.oml
omnist convert data.json --from json --to toml --report -o data.toml
omnist convert data.json --from json --to toml --strict -o data.toml
omnist validate <input> --from FMT --schema FILE [--result-format text|json|oml] [--json]¶
Reads without schema-directed upgrading, then Schema.validate.
text(default):ValidationResult's"invalid:\n at $.path: message"(orvalid).json:{"ok": bool, "errors": [{"path": str, "message": str}, ...]}.oml: same shape, OML-encoded.
Exit 0 valid, 1 invalid, 2 read/parse error.
omnist validate order.json --from json --schema order.osd
omnist validate order.xml --from xml --schema order.osd --result-format json
--json: a distinct, more detailed machine-readable mode (issue #182), independent of--result-format. Each errors entry also carries the stable machine-readablecode(unexpected-field,cardinality,type-mismatch,null-not-allowed,shape-mismatch) fromValidationResult/ParseError.errors(the same structured listParseErrorhas exposed since v0.4.1). Unlike--result-format,--jsonalso converts read/parse errors -- normallyerror: ...on stderr -- into the same{ok, message, errors}shape on stdout:- success:
{"ok": true}. - conformance failure:
{"ok": false, "message": str, "errors": [{"path": str, "code": str, "message": str}, ...]}. - format-syntax failure (invalid input text, or a malformed schema): same shape,
"errors"always[], parse error in"message". - Exit codes are unchanged in every case (
0/1/2as above); only the printed shape and destination (stdout, not stderr, for read/parse errors) differ.
omnist validate order.json --from json --schema order.osd --json
omnist validate order.xml --from xml --schema order.osd --json
omnist infer <input>... --from FMT [--compact] [--allow-any] [-o OUTPUT]¶
All inputs same format; infer(docs), writes the result as OSD.
--compact emits a single-line schema (to_osd(schema, indent=None)).
Passing --arrays here is an error (exit code 2) — the output is
always OSD, and OSD has no array syntax; --arrays applies only to OML
output (issues #218, #220). --allow-any opts in
to opening the two infer conflict points
(object/scalar mix, multi-kind scalar) as any fields instead of erroring;
the schema still goes to stdout, and a per-field report of what was opened
and why goes to stderr (nothing printed when zero fields open). Like every
other diagnostic-producing command, --json switches that report to
structured JSON ({"opened": [{"location", "reason"}, ...]}, matching
AnyFallback) instead of the text form.
omnist infer samples/*.json --from json -o inferred.osd
omnist check <input> --from FMT --to FMT [--strict] [--result-format text|json|oml]¶
check_<to> — reports what write_<to> would adjust, never writes
anything. --from/--to may be equal (unlike convert).
- Default: exit
0regardless of result; purely informational. --strict: exit0if nothing would need adjusting,1otherwise.
omnist check data.json --from json --to toml
omnist check data.json --from json --to toml --strict
omnist schema format <schema-file> [--compact] [-o OUTPUT]¶
parse_schema → to_osd. Safe reformat only — same records, same names,
canonical whitespace/field order. No structural change (contrast
normalize). --compact emits a single-line schema (to_osd(schema,
indent=None)). Passing --arrays here is an error (exit code 2) — OSD output, no
array syntax (issues #218, #220).
omnist schema format messy.osd -o clean.osd
omnist schema format messy.osd --compact
omnist schema normalize <schema-file> [--compact] [-o OUTPUT]¶
Schema.normalize(), written back as OSD. Computes the canonical minimal
equivalent schema (partition refinement, fewest env records, unique up to
naming) — a structural change, unlike schema format. --compact emits a
single-line schema, same as schema format. Passing --arrays here is
likewise an error (exit code 2) — OSD output, no array syntax.
omnist schema prune <schema-file> [--compact] [-o OUTPUT]¶
Schema.prune(), written back as OSD. Removes everything that can never
match — unreachable records, never-emittable (max == 0) fields, and
optional fields whose type is an unsatisfiable record — without ever
merging records (that's normalize's job; normalize runs prune as its
own first step). --compact as elsewhere.
omnist schema is-empty <schema-file> [--result-format text|json|oml]¶
Schema.is_empty() — True iff the schema accepts no documents at all
(unsatisfiable root, e.g. a mandatory ref cycle). Prints true/false
(text, default), {"empty": bool} (json), or the same shape
OML-encoded (oml). Exit 0 if empty, 1 if not — the same
boolean-result convention as compatible-with/equivalent.
omnist schema lint <schema-file> [--json] [--severity info|warning]¶
lint() — non-destructive structural diagnostics for the schema itself.
Reports, never mutates (prune/normalize are the transforms). Four
checks: unsatisfiable-record (reachable but no finite document matches),
unreachable-record (defined but not reachable from root),
duplicate-record (structurally identical records under different names),
each warning; and any-field (inventory of every any-typed field),
info. Findings sort by (code, location). Text output is one
severity: code: location: message line per finding, or no findings.
--json prints {"ok": bool, "findings": [{"code","severity","location",
"message"}, ...]}, mirroring validate --json's shape. --severity
filters by minimum severity (info default keeps everything; warning
drops the any-field inventory). Exit 0 if no surviving finding is
warning-severity, 1 if any is — an info-only result always exits 0.
omnist schema extract <schema-file> --keep label1,label2,... [--compact] [-o OUTPUT]¶
Schema.extract(*labels), written back as OSD (paper Algorithm 5,
ExtractSubschema). The minimal subschema recognizing only documents built
from --keep's comma-separated labels: fields whose label isn't kept are
dropped, and anything they make unreachable is pruned away too (prune()
+ normalize() is the algorithm's own final step). --compact emits a
single-line schema, same as schema format/schema normalize.
If deleting a non-kept label would remove a mandatory (min >= 1)
field, the record that had it -- and transitively anything mandatorily
depending on it -- is invalidated; deliberately an error rather than a
silent relaxation to optional (see docs/schema.md's "Subschema
extraction" section for the rationale). If that invalidation reaches the
root, there is no valid subschema for this --keep set: SchemaError
naming the first offending label and record, printed to stderr as
error: ..., exit 1 (a definite "no," like compatible-with's False --
not the generic exit 2 for parse/usage errors).
omnist schema extract order.osd --keep quote,line,desc,price
omnist schema extract order.osd --keep quote,line,desc,price --compact
omnist schema compatible-with <a> <b> [--result-format text|json|oml]¶
a.compatible_with(b). text: true/false. json:
{"compatible": bool}. oml: compatible: true/compatible: false.
Exit 0/1.
omnist schema compatible-with v1.osd v2.osd
omnist schema equivalent <a> <b> [--result-format text|json|oml]¶
a.equivalent(b). Same output/exit convention as compatible-with.
4. Conventions¶
Exit 0 |
success / valid / compatible / equivalent / losslessly-writable |
Exit 1 |
a definite "no" — invalid, incompatible, not equivalent, or (--strict) not losslessly writable |
Exit 2 |
usage error, parse error, missing file, unsupported format, schema non-conformance |
--result-format |
text (default) / json / oml — encodes a command's own result, not Document/Schema content; convert only uses it together with --report |
-/-o |
stdin/stdout |
5. Non-goals¶
- No format auto-detection (content or extension).
- No batch/multi-document conversion.
- No alternate Schema serialization (e.g. JSON-Schema-shaped import/export).
- No
schema diff(structural diff beyond the booleancompatible-with/equivalent). - No schema editing (only whole-schema read/transform).
6. Packaging¶
[project.scripts]
omnist = "omnist.cli:main"
omnist/cli.py — argparse-based, public omnist API only. No new
required dependency.