Mentu

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 setup

The 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-start

Which 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 init
Initialized .mentu/recipes and .mentu/prompts

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

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

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

plan

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 · ok

Reads .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 · ok

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

Each 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 adapters
shell	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	auto

One 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 shell
shell
  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-key

Reads 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 passed

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

© 2026 Mentu.