# 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`) are both checked against one typed
catalog by the site build, and a drift on either face fails that build.

## 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:{artifact,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 artifact: 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:

```
$ CORPUS=node_modules/epcis.dev/golden-corpus

$ npx epcis.dev validate $CORPUS/valid-standard/object-event-shipping.json
valid	schema=epcis-json-schema.json	version=2.0.1
$ echo $?
0

$ npx epcis.dev validate $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`.

---

**Proof, not adjectives.** This build could not verify the gateway's test suite green, so no sentence on this page claims a passing run — the last verified run is recorded in the repository's own CI, not here. Live on this origin, no key: POST /translate, /validate, /hash. No conformance attestation has ever been issued. The dated ledger is /what-ships-today/.
