Output
With --json, every mb command prints one JSON document to stdout and nothing
else. Logs go to stderr. The document has the same shape for every command, and
for success, refusal and misuse alike, so a script reads ok and the exit code
and knows where it stands.
An example
Section titled “An example”A refusal from examples/ecommerce/, captured by running
mb brand name "Northwind Outdoor Group Sales and Margin Book" --dry-run --json.
The name is 45 characters and the limit is 40. The exit code is 1.
{ "command": "brand name", "ok": false, "changed": [], "checks": [ { "code": "name_length", "field": "brand.name", "value": "45 characters", "rule": "1 to 40 characters", "passed": false, "suggestion": "Northwind Outdoor Group Sales and Margin" }, { "code": "name_control_character", "field": "brand.name", "value": "none", "rule": "no control characters", "passed": true, "suggestion": null } ], "refusals": [ { "code": "name_length", "field": "brand.name", "value": "45 characters", "rule": "1 to 40 characters", "suggestion": "Northwind Outdoor Group Sales and Margin" } ], "dry_run": true, "diff": ""}The suggestion is a value that would pass. An agent can run the command again
with it.
Fields of the document
Section titled “Fields of the document”| Field | Type | Meaning |
|---|---|---|
command |
string | The command that ran, as in brand name or templates install. A usage error at the top level has mb. |
ok |
boolean | true when refusals is empty. |
changed |
list of strings | The files the command wrote, relative to the project and with forward slashes. With --dry-run, the files it would write. Empty when the command refused. |
checks |
list of checks | Every check the command ran, passed or not, each with the value it measured. Empty when the command stopped before it could check anything. |
refusals |
list of refusals | Why the command refused. Empty when ok is true. |
dry_run |
boolean | Whether this was a --dry-run. Present on the commands that take --dry-run. |
diff |
string | The change as a unified diff, one section per file. Empty when the command refused. |
error |
string | One sentence that says what stopped the command. Present when the command stopped on a single problem, such as a usage error, no project found, or a file the system refused to read or write. |
A command can add fields of its own after these. mb dev logs adds service and
log, mb templates list adds templates, and mb secrets run adds
exit_code. Run the command with --json to see them. The fields above are the
same everywhere.
A check
Section titled “A check”A check is one rule and the number it measured. It is listed whether it passed or not, so an agent sees how close a passing value came to the limit.
| Field | Type | Meaning |
|---|---|---|
code |
string | The refusal code the check would raise. |
field |
string | What was checked, such as brand.name. |
value |
string, number or null |
What was measured, such as 45 characters. |
rule |
string | The rule the value is held to, such as 1 to 40 characters. |
passed |
boolean | Whether the value met the rule. |
suggestion |
string or null |
A value that would pass, or null when there is none. |
A refusal
Section titled “A refusal”A refusal is a failed check, or a problem found before any check ran. It has the
same fields as a check, without passed.
| Field | Type | Meaning |
|---|---|---|
code |
string | A stable code to match on. The list is on the refusal codes page. |
field |
string | What was wrong: a setting, a flag, an argument or a path. Empty when no single field is to blame. |
value |
string, number or null |
What was found. |
rule |
string | What the value had to satisfy. |
suggestion |
string or null |
What to do, or a value that would pass. null when there is none. |
Match on code. The rule and suggestion are written for a reader and their
wording can change.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
| 0 | Done. ok is true. |
| 1 | Refused, or a check failed. ok is false. |
| 2 | Used wrongly: flags that conflict, a missing argument, an unknown option, or no project or repository to run in. ok is false. |
Every command prints ok and exits 0 or non-zero to match, so either can be
trusted. mb secrets run is the one exception: it exits with the exit code of
the command it ran, and puts that code in exit_code.
Dry runs
Section titled “Dry runs”A command that changes files takes --dry-run. It runs every check, shows the
change in diff, lists the files it would write in changed, and writes
nothing.
- The
brandcommands work out the new files in memory and check that every path may be written. init,fmt,skills syncand thetemplatescommands run the real change on a copy of the project in a temporary folder, then diff the copy against the project. The diff is what the real run writes, because the same code wrote it. The copy leaves out.git,node_modules,.venv, caches and.metrics_book/state, so none of those appear in a diff.
A refusal under --dry-run is the same refusal the real run would give, with the
same code and exit code 1.
A successful dry run of mb brand name "Northwind Sales" from the same project
prints ok: true, changed: ["stack.yml"], dry_run: true and this diff:
--- stack.yml+++ stack.yml@@ -18,3 +18,6 @@ model: text-embedding-3-large checks: off: []++brand:+ name: Northwind SalesAll or none
Section titled “All or none”A change that touches several files is checked as a whole before the first file
is written. If any check refuses, nothing is written and changed is empty.
mb brand apply is the clearest case: it sets the logo, mark, colours and
settings from one document, and either all of it lands or none of it does. Each
file is written to a new file in the same folder and then renamed over the old
one, so a reader never sees half a file.
Without --json
Section titled “Without --json”- A command that succeeds prints a short text to stdout and nothing to stderr.
- A command that refuses prints one sentence per refusal to stderr, in the form
field: value breaks rule (code); suggestion, and nothing to stdout.
Usage errors
Section titled “Usage errors”A usage error is a refusal like any other. With --json, stdout holds one
document, the exit code is 2, and the single refusal has the code usage:
{ "command": "brand name", "ok": false, "changed": [], "checks": [], "refusals": [ { "code": "usage", "field": "value", "value": null, "rule": "Missing argument 'value'.", "suggestion": "mb brand name --help" } ], "error": "Missing argument 'value'."}field names the argument or option at fault, rule is the parser’s message,
and suggestion is the help command for that command. A mistake before any
command is named, such as an unknown top-level option, has command set to mb.
Without --json, a usage error is the parser’s own text on stderr, stdout is
empty, and the exit code is still 2.
A command run outside a project gives the same shape with the code
project.unusable and exit code 2. Outside a repository, check, fmt and
skills sync give repository.not_found and exit code 2.