Skip to content
Not released yet: version 0.1 is being built. See what works today.

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.

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.

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 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 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.

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.

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 brand commands work out the new files in memory and check that every path may be written.
  • init, fmt, skills sync and the templates commands 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 Sales

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.

  • 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.

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.