CLI
One binary, a closed verb set, machine-stable outputs. Everything runs in-process on your machine; the only network-touching verb is conformance run --target, and it talks only to the endpoint the descriptor names. This page and the AGENTS.md shipped inside the tarball (node_modules/epcis.dev/AGENTS.md) render from the same typed source, so they cannot disagree.
The contract
- Verbs (closed set): translate, validate, hash, capture, mcp, conformance run, conformance rejudge, pins, version. Bare npx epcis.dev prints the orientation. Unknown verbs are a typed USAGE refusal with a hint, exit 2.
- --json on every verb. Deterministic machine JSON — same data, same exit code as text mode. Text mode is token-cheap TSV-style lines, never colored, never wrapped.
- stdout is payload-only. Errors are typed on stderr: {"error":{"code","message","hint"?}} in json mode, error<TAB>code=…<TAB>message=… in text mode. JSON.parse(stdout) never sees a half-written object.
- Error code strings are stable across releases — match on code, never on prose: USAGE, PARSE, NOT_FOUND, INTERNAL.
Exit codes (stable)
| exit | meaning |
|---|---|
| 0 | ok |
| 1 | fail — a negative domain verdict: invalid document, failed translation, rejected capture, failed suite run, rejudge mismatch |
| 2 | usage |
| 3 | not-found — unreadable input |
| 4 | internal |
Distinct exits distinguish "the fail-closed path worked" from a crash.
Verb reference (input → output → exit)
| verb | input | text output | --json output | |
|---|---|---|---|---|
| validate <f> [--project] | EPCISDocument or bare event JSON | valid line, or per-path schema-error lines | {valid, schema:{artefact,version,sha256}, errors:[{path,keyword,message}]} | |
| hash <f> | event, event array, or EPCISDocument | hash line per event with the ni URI | {algorithm, count, hashes:[…]} | |
| translate <f.xml> [--out o] | EPCIS 1.1/1.2/2.0 XML | summary line; document JSON on line 2 (or wrote line) | full result incl. document, fidelity, schemaErrors | |
| `capture <f.json\ | f.xml>` | document (XML is translated first) | accepted + eventID lines, or capture-error lines | {accepted, job, events} — events read back through the query door, stamps visible |
| mcp | — | MCP server on stdio; stdout carries only the wire | — | |
| `conformance run --self\ | --target d.json [--out o]` | — / target descriptor | advisory notice, per-check verdicts, state line | {mode:"advisory", notice, record} |
| conformance rejudge <run.json> [--signing-key b64] | a run file / evidence bundle | signature line, per-check verdicts, rejudge line | {signature, replayed, identical, missingChecks, flips, ok} | |
| pins | — | one pin line per artefact: sha256 + source URL | the PINS record verbatim | |
| version | — | package, EPCIS, CBV and suite versions | {package, version, epcis, cbv, suite} |
Hazard worth knowing before you pipe: translate in text mode without --out prints the summary first and the full document JSON as line 2 of stdout — use --json or --out when piping.
Proof — the taxonomy, exercised
Real runs against the published epcis.dev@0.1.0, using fixtures shipped in the tarball:
$ npx epcis.dev validate golden-corpus/valid-standard/object-event-shipping.json
valid schema=epcis-json-schema.json version=2.0.1
$ echo $?
0
$ npx epcis.dev validate golden-corpus/invalid/object-event-missing-action.json
schema-error /epcisBody/eventList/0 must have required property 'action'
schema-error /epcisBody/eventList/0 must match "then" schema
schema-error / must match "then" schema
invalid errors=3
$ echo $?
1
$ npx epcis.dev frobnicate
error code=USAGE message=unknown verb "frobnicate" hint=run `npx epcis.dev` with no arguments for the verb map
$ echo $?
2
$ npx epcis.dev validate no-such-file.json
error code=NOT_FOUND message=cannot read document: ENOENT: no such file or directory, open 'no-such-file.json'
$ echo $?
3
The verb table and exit taxonomy above are additionally diffed against the shipped AGENTS.md by the catalog's drift check — the run for this page reported drift: none — aligned.
We answer in writing. We take at most five conversations a month, only when you ask for one, and only after you already have the written read.