Writing a wrapper
A wrapper turns “run a process, read stdout” into function calls. The contract makes this
simple if the wrapper follows a few rules. Complete, tested wrappers are in wrappers/:
Python, TypeScript, Rust, Shell.
The algorithm
1. argv = [binary, "--output", "json", ...command path, ...options] # array, no shell
+ "--yes" for writes, only with the user's consent
2. spawn with stdin closed, a timeout (≈45 s), environment with MOSTRO_PUBKEY/RELAYS
3. if spawn failed → TransportError(may_have_acted = false)
if timeout → TransportError(may_have_acted = is_write)
4. stdout.strip() empty → TransportError(may_have_acted = is_write and exit == 1)
not one JSON object → TransportError(may_have_acted = is_write)
5. envelope.schema_version != "1" → IncompatibleError
6. return envelope (+ exit code) → caller branches on status, then error.code
Rules
- Build argv as a list. Never join a command string for a shell; values such as payment methods and invoices are user data.
- One
--output json, placed first. Never let user data land where an option is expected. For a value that could start with-, use the attached form--payment-method=<value>: the CLI never reads an output selection inside a value. - Close stdin. The CLI does not read it in JSON mode; a closed stdin makes sure a future version can never block on a prompt.
- Parse stdout, ignore stderr (log it, do not parse it).
- Branch on
status, thenerror.code. Never onmessage. Keep a default branch for codes you do not know (within v1 new commands can add codes). - Keep decimal strings as strings or parse them as big integers.
- Never retry
uncertainor a write transport failure automatically. See Recovering. - Serialise writes per data directory.
- Discover once: call
schemaat start-up; check every command you need is present with"json"inoutput_modes. - Validate optionally: the per-command output schemas are strict; validating in tests (or in debug builds) catches drift between your wrapper and a new binary.
Surfacing results to a UI
| Envelope | Show |
|---|---|
ok | The data; warnings as notes |
error + node_refused | “The node refused: <reason>” |
error (other) | A sentence per code (your own text; the CLI’s message is acceptable as a fallback) |
uncertain | “We could not confirm whether this happened. Checking…” then run the check |
Testing a wrapper without a node
Many outcomes can be produced offline:
| Outcome | How |
|---|---|
invalid_arguments (exit 2) | order list --side x |
command: null | an unknown command |
output_not_supported | any human-only command |
configuration_missing | unset MOSTRO_PUBKEY/RELAYS |
configuration_invalid | --mostro-pubkey not-a-key |
identity_missing | MCLI_DIR pointing to an empty directory, valid config |
relay_connection_failed | --relays ws://127.0.0.1:1 (with an identity, for identity commands) |
schema success | schema |
For the rest, feed your parser the files in examples/ (all 120 are real-shaped envelopes;
examples/index.json gives the exit code to pair with each).