CLI
CLI
This is what mentu-recipes --help prints in version 0.5.0:
Mentu Recipes
New here? Run `mentu-recipes setup`: it shows what is on this Mac, places an
example recipe in the workspace, runs it, and shows you the record.
Usage:
mentu-recipes setup [--yes] [--json] [--no-run]
mentu-recipes init
mentu-recipes check <recipe-or-path>
mentu-recipes run <recipe-or-path> [--workspace PATH] [--backend NAME] [--model MODEL] [--cloud] [--max-parallel N] [--var KEY=VALUE]
mentu-recipes resume <run-id> [--workspace PATH]
mentu-recipes retry-step <run-id> <step-label> [--workspace PATH]
mentu-recipes report <run-id> [--format markdown|json|csv]
mentu-recipes doctor <recipe-or-path> [--format markdown|json|csv] [--strict]
mentu-recipes analyze-runs [--workspace PATH] [--format markdown|json|csv] [--export-jsonl PATH]
mentu-recipes adapters [--json|--explain NAME]
mentu-recipes vault <set|get|list|delete> ...
mentu-recipes scan [path] [--artifact PATH]
mentu-recipes --version--help, -h, and help print that text. --version, -V, and version
print mentu-recipes 0.5.0. Running the binary with no arguments prints
the help.
Recipe arguments
A recipe argument is either a name or a path. A name is resolved to
<name>.json under <workspace>/.mentu/recipes first and ~/.mentu/recipes
second. A path, relative, absolute, or starting with ~/, is accepted only if
the file exists and resolves inside one of those two roots after symlinks are
followed. Anything else fails with Recipe not found.
The workspace is the current directory unless the command takes --workspace.
Exit codes
| Code | When |
|---|---|
| 0 | The command finished and, for run, resume, and retry-step, the recipe outcome was ok. |
| 1 | Any error: a failed recipe, an invalid recipe, a missing run record, a doctor finding at error level (or warning level with --strict), a scan finding, a missing secret, an unknown backend. The message prints on stderr as mentu-recipes: <reason>. |
| 2 | Unknown command. Unknown command: <name> goes to stderr and the help text to stdout. |
setup
mentu-recipes setupThe first-run wizard. It lists the backends the binary knows and whether each
one is on this Mac, lists the credential names stored in the Keychain, writes
the bundled hello-justifiable recipe into .mentu/recipes if it is not
already there, asks Run it now? [Y/n], runs it with the shell backend, and
prints the record path and the commands to try next.
Mentu Recipes 0.3.0 · first run
Workspace /Users/you/recipe-demo
Backends on this Mac
✓ shell available (shell)
· openai not found (llm-http)
· openai-chat not found (llm-http)
· deepseek not found (llm-http)
✓ ollama available (llm-http)
✓ claude available (agent-cli)
✓ codex available (agent-cli)
Keychain no provider keys stored; add one later with `mentu-recipes vault set <name>`
Example /Users/you/recipe-demo/.mentu/recipes/hello-justifiable.json
Two shell steps. The first writes examples/.work/hello.md inside its declared boundary;
the second proves the file says what the first claimed. No credentials needed.
Run it now? [Y/n]
░░░░░░░░░░░░░░░░░░░░ 0% 0/2 steps
⏵ 1/2 produce shell 0s running
PRODUCE_COMPLETE
✓ 1/2 produce shell 0s ok
██████████░░░░░░░░░░ 50% 1/2 steps
⏵ 2/2 prove shell 0s running
PROVE_COMPLETE
✓ 2/2 prove shell 0s ok
✓ hello-justifiable · 2 step(s) · ok
Record /Users/you/recipe-demo/.mentu/runs/run_20260902181805_3B76D7E7/run.json
Next
mentu-recipes run hello-justifiable
mentu-recipes run hello-justifiable --backend claude
mentu-recipes doctor hello-justifiable
Docs https://docs.mentu.ai/quick-startWhich backends show as available depends on the Mac. The --backend claude
suggestion appears only when an agent CLI is on the PATH.
| Option | Effect |
|---|---|
--yes |
Skip the Run it now? prompt and run the example. |
--no-run |
Stop after writing the example recipe. |
--json |
Print one JSON report instead of the text above, and make no interactive decisions. Implies --yes. The report has workspace, backends, keychainKeys, exampleRecipe, exampleWritten, ran, outcome, runRecord, and next. |
setup always works in the current directory. It creates .mentu/recipes and
.mentu/prompts the same way init does.
init
mentu-recipes initInitialized .mentu/recipes and .mentu/promptsCreates the two directories in the current directory and nothing else. No
recipe is written; setup is the command that writes the example.
check
mentu-recipes check hello-justifiable✓ hello-justifiable · 2 step(s) · /Users/you/recipe-demo/.mentu/recipes/hello-justifiable.jsonLoads and validates the recipe: JSON shape, required fields, step label syntax, prompt presence, and the dependency graph. It takes no options and resolves names against the current directory. It exits 1 and names the problem when the file cannot run:
mentu-recipes: Invalid recipe: step 'join' depends on unknown step 'nope'mentu-recipes: Recipe not found: nope
Run `mentu-recipes list` to see this workspace's recipes, or `mentu-recipes setup` to place an example here.run
mentu-recipes run hello-justifiable░░░░░░░░░░░░░░░░░░░░ 0% 0/2 steps
⏵ 1/2 produce shell 0s running
PRODUCE_COMPLETE
✓ 1/2 produce shell 0s ok
██████████░░░░░░░░░░ 50% 1/2 steps
⏵ 2/2 prove shell 0s running
PROVE_COMPLETE
✓ 2/2 prove shell 0s ok
✓ hello-justifiable · 2 step(s) · ok
Run record: /Users/you/recipe-demo/.mentu/runs/run_20260902181805_3B76D7E7/run.json| Option | Effect |
|---|---|
--workspace PATH |
Run against a workspace other than the current directory. Recipe names resolve there. |
--backend NAME |
Backend for steps that do not name one. It overrides the recipe's top-level backend, never a step's own. |
--model MODEL |
Model for steps that do not name one, with the same precedence. |
--max-parallel N |
Cap concurrent steps in a layer, or concurrent child recipes. Overrides the recipe's max_parallel. |
--var KEY=VALUE |
Define a variable. Repeatable. |
--cloud |
Turn on the cloud run hooks. They activate only when MENTU_API_KEY is set in the environment or the Keychain; without a key the record says local-only. |
--quiet |
Drop the progress bar and per-step lines. The outcome and record path still print. |
Exits 1 with mentu-recipes: Recipe failed on stderr if the outcome is not
ok. The last line always names the run record.
A --var value is substituted into the prompt wherever $KEY or ${KEY}
appears, and is also exported into the step environment, so a shell step can
read it either way:
{
"name": "varsdemo",
"steps": [
{
"label": "echo",
"backend": "shell",
"prompt": "echo target=$TARGET; echo VAR_COMPLETE",
"completion_keyword": "VAR_COMPLETE"
}
]
}mentu-recipes run varsdemo --var TARGET=docs░░░░░░░░░░░░░░░░░░░░ 0% 0/1 steps
⏵ 1/1 echo shell 0s running
target=docs
VAR_COMPLETE
✓ 1/1 echo shell 0s ok
✓ varsdemo · 1 step(s) · ok
Run record: /Users/you/recipe-demo/.mentu/runs/run_20260902181913_CBA20458/run.jsonA failed shell step looks like this:
⏵ 1/1 boom shell 0s running
starting
✗ 1/1 boom shell 0s failed · exit 3
mentu-recipes: Recipe failed
✗ failing · 1 step(s) · failed
Run record: /Users/you/recipe-demo/.mentu/runs/run_20260902181840_28E81E89/run.jsonplan
mentu-recipes plan <recipe-or-path> [--workspace PATH] [--backend NAME] [--model MODEL] [--var KEY=VALUE] [--max-parallel N] [--cloud]Resolves the recipe tree and prints a JSON review artifact: a SHA-256 digest of
every input the runner will read, one digest per step with its backend and
model, the child plans, and unbound_environment, the list of session-scoped
variables the digest deliberately leaves out. Prompts and environment values
are never written into the artifact, only their hashes. plan resolves vault
credentials for the selected backends, so it can touch the login Keychain.
{
"digest" : "f05a3202…",
"recipe" : "hello-justifiable",
"steps" : [
{ "label" : "produce", "backend" : "shell", "digest" : "30a5a155…" },
{ "label" : "prove", "backend" : "shell", "digest" : "23b35b17…" }
],
"unbound_environment" : [ "Apple_PubSub_Socket_Render", "COLORTERM", "…" ],
"version" : 1
}Pass the digest back to start an admitted run:
mentu-recipes run hello-justifiable --plan-digest <digest> --request-key nightly-1--plan-digest and --request-key go together, on run, resume and
retry-step. If any bound input changed since plan, the command fails with
Execution plan changed before any hook runs or any state is written. The same
request key repeated returns the same run instead of starting another; a key
reused for a different plan is refused. One admitted operation runs per
workspace at a time. An interrupted admitted run leaves
.mentu/runs/.admission/active.json behind and later admitted operations refuse
to start until you confirm nothing is still running and remove that file.
Legacy runs without the two flags are unaffected. Full contract:
Admitted execution.
resume
mentu-recipes resume run_20260902181839_DEA6BBF8⊘ 1/3 probe-a already complete
⊘ 2/3 probe-b already complete
⊘ 3/3 join already complete
✓ fanout · okReads .mentu/runs/<run-id>/state.json, skips every step whose state is
success or warn_bookkeeping, and reruns the rest, including steps that
failed. Variables come from the saved state, not the command line. New events
are appended to the run's existing events.jsonl, starting with a
run_resumed event, and run.json is rewritten.
resume accepts --workspace, --backend, --model, --max-parallel,
--cloud, and --quiet with the same meaning as run. It does not accept
--var.
retry-step
mentu-recipes retry-step run_20260902181839_DEA6BBF8 probe-a⊘ 2/3 probe-b already complete
███████░░░░░░░░░░░░░ 33% 1/3 steps
⏵ 1/3 probe-a shell 0s running
A_COMPLETE
✓ 1/3 probe-a shell 0s ok
⊘ 3/3 join already complete
✓ fanout · okMarks the named step pending in state.json and then behaves as resume.
Steps that depend on the retried step are not rerun if they already completed.
The step's attempts count in state.json goes up by one. Same options as
resume.
report
mentu-recipes report run_20260902181805_3B76D7E7# Mentu Recipes Run
- Run: `run_20260902181805_3B76D7E7`
- Recipe: `hello-justifiable`
- Outcome: `ok`
- Started: 2026-09-02T18:18:05Z
- Ended: 2026-09-02T18:18:06Z
- Cloud: `local-only`
| Step | Backend | Outcome | Complete | Exit | Duration | Attempts | Commit | Quarantine |
| --- | --- | --- | ---: | ---: | ---: | ---: | --- | ---: |
| produce | shell | success | yes | 0 | 0s | 1 | 2b2031805c39 | 0 |
| prove | shell | success | yes | 0 | 0s | 1 | | 0 |Markdown is the default. It adds a ## Warnings section when any step
recorded warnings and a ## Hooks section when any hook ran.
--format csv gives one row per step, which is the form to feed a spreadsheet
or a CI artifact:
run_id,recipe,step,backend,model,outcome,exit_code,complete,duration_seconds,attempts,committed_hash,quarantine_count
run_20260902181805_3B76D7E7,hello-justifiable,produce,shell,,success,0,true,0,1,2b2031805c39a8f96e6905e02e6b512598f7395d,0
run_20260902181805_3B76D7E7,hello-justifiable,prove,shell,,success,0,true,0,1,,0--format json prints the whole run.json record, pretty-printed with sorted
keys. --workspace PATH reads the record from another workspace. A missing
run exits 1 with the file error from the operating system.
The run record
Every run creates .mentu/runs/<run-id>/ in the workspace. The run id is
run_ plus a UTC timestamp plus eight hex characters. For the two-step example
above the directory holds:
.mentu/runs/run_20260902181805_3B76D7E7/
run.json the record report reads: recipe, outcome, cloud_mode, hooks, and one entry per step
events.jsonl one JSON object per line, sequence-numbered: run_started, step_queued,
backend_selected, step_started, hook_finished, verification_started,
verification_finished, workspace_drift, step_finished, step_skipped,
run_resumed, run_finished
baseline.json the paths that were already dirty when the run began
state.json per-step state (pending, running, success, warn_bookkeeping, failed,
skipped, cancelled), attempts, and the saved vars; resume reads this
produce.stdout captured output of the step named produce
produce.stderr
prove.stdout
prove.stderrEach step entry in run.json records the backend, model, attempts, exit code,
duration, completion_method, the verification errors and warnings, the git
result (expected_paths, changed_paths, unexpected_paths,
committed_hash, quarantine_files), and a drift block that separates paths
created by the step from paths that were already dirty. When a step changes
files outside its declared paths, the diff is written under
.mentu/runs/<run-id>/quarantine/.
doctor
mentu-recipes doctor hello-justifiable --strict# Mentu Recipes Doctor
- Recipe: `hello-justifiable`
- Score: 100
No findings.The quality pass. It loads the recipe without running it, scores it out of 100,
and lists findings with a severity, a code, a location, and a recommendation.
A recipe that does not load scores 0 with one invalid_recipe error.
# Mentu Recipes Doctor
- Recipe: `needs-doctor`
- Score: 78
| Severity | Code | Location | Recommendation |
| --- | --- | --- | --- |
| warning | missing_completion_signal | steps[0].draft | Add `completion_keyword` or `verify` so completion is observable. |
| warning | missing_expected_changes | steps[0].draft | Declare expected paths or add `verify.git_clean_outside`. |
| info | default_timeout | steps[0].draft | Set `timeout` for long-running provider steps. |Each error costs 30 points, each warning 10, and each info finding 2. The codes are:
| Code | Severity | Fires when |
|---|---|---|
invalid_recipe |
error | The recipe does not load. |
duplicate_step |
error | Two steps share a label. |
unknown_backend |
error | A step names a backend that is not built in or defined under providers. |
destructive_shell |
error | A shell prompt contains a pattern such as rm -rf /, mkfs, dd if=, or a fork bomb. |
generic_provider_credential |
error | A custom provider reuses OPENAI_API_KEY, DEEPSEEK_API_KEY, openai-api-key, or deepseek-api-key. |
missing_completion_signal |
warning | A non-shell step has neither completion_keyword nor verify. |
missing_expected_changes |
warning | The prompt looks like it writes files but the step declares no expected_changes and no verify.git_clean_outside. |
unsupported_allowed_tools, unsupported_disallowed_tools, unsupported_thinking |
warning | The step sets an option its backend does not support. |
default_timeout |
info | A non-shell step has no timeout. |
An error finding exits 1. --strict extends that to warnings. The exit message
is mentu-recipes: doctor found N finding(s). --format json prints
recipe_name, score, and findings; --format csv prints one finding per
row under recipe,score,severity,code,location,message,recommendation.
--workspace PATH points name resolution at another workspace.
analyze-runs
mentu-recipes analyze-runs# Mentu Recipes Run Analysis
- Workspace: `redacted`
- Runs: 1
- Success rate: 1.00
| Backend | Steps | Success Rate | Median Duration |
| --- | ---: | ---: | ---: |
| shell | 2 | 1.00 | 0s |Reads every run.json under .mentu/runs in the workspace and aggregates
them: run success rate, and per backend the step count, step success rate, and
median duration. It does not open prompts, step output, or events. The
workspace path is always printed as redacted.
A ## Recommendations section appears when the records justify one:
repeated_step_failure when the same step label failed in two or more runs
(the label is reported as a SHA-256 hash, not in clear), add_verification
when provider-completed steps ran with no deterministic checks, and
tighten_expected_changes when any step produced a quarantined patch.
--format json prints workspace, runs, success_rate, backend_summary,
and recommendations. --format csv prints one row per backend under
backend,steps,success_rate,median_duration_seconds. --export-jsonl PATH
writes the JSON summary as a single line to that file, creating parent
directories, and still prints the chosen format to stdout. --workspace PATH
analyzes another workspace.
adapters
mentu-recipes adaptersshell shell plain_text ignored available explicit
openai llm-http openai_sse native unavailable auto
openai-chat llm-http openai_sse native unavailable auto
deepseek llm-http openai_sse native unavailable auto
ollama llm-http openai_sse native available auto
claude agent-cli claude_json native available auto
codex agent-cli codex_json folded_into_prompt available autoOne tab-separated line per built-in backend: name, execution kind, stream
format, how system context is passed, whether the backend is usable on this Mac
right now (a key in the environment or Keychain for HTTP backends, a reachable
server for ollama, the CLI on the PATH for claude and codex), and whether
it can be auto-selected. Custom providers declared in a recipe are not listed.
mentu-recipes adapters --explain shellshell
execution: shell
stream: plain_text
completion: shell_exit_code
local: true
network: false
credential: false
tools: false
structured completion: false--explain NAME prints one backend's completion policy and its local, network,
credential, and tool flags. An unknown name exits 1 with
Backend unavailable: NAME. --json prints the full capability record for
every backend as a JSON array; it includes fields --explain does not show,
such as can_run_offline, reports_token_usage, supports_reasoning,
supports_thinking, supports_tool_allow_list, and
supports_tool_deny_list. See Providers.
vault
printf '%s' "$OPENAI_API_KEY" | mentu-recipes vault set openai-api-key
mentu-recipes vault get openai-api-key
mentu-recipes vault list
mentu-recipes vault delete openai-api-keyReads and writes the macOS Keychain. set reads the secret from stdin, strips
trailing newlines, and prints ✓ stored <key>; an empty stdin exits 1 with
No secret received on stdin. get prints the value. list prints one stored
key name per line. delete prints ✓ deleted <key>. Pipe secrets in so they
stay out of shell history. See Credentials.
scan
mentu-recipes scan .✓ release scan passedRuns the release source scanner over a path (default: the current directory)
and over any number of built artifacts given with --artifact PATH. It reads
every regular file, skipping .build/, .swiftpm/, .git/, .mentu/runs/,
.mentu/cache/, and dist/stage/, and reports secret-shaped tokens (OpenAI,
Slack, and GitHub key prefixes), hardcoded local user paths, a confidentiality
marker, internal project names, and files under protected .mentu paths. Each
hit prints the file and the reason, then the command exits 1:
✗ leak.js: likely secret token
mentu-recipes: release scan failed with 1 finding(s)It is the check the project runs on its own releases before publishing.