Plain mode and scripting
Besides the full-screen terminal UI, malac has a line-based interface that streams plain text. It is used whenever the full-screen UI cannot or should not run: with --plain, with ui.tui = false, when stdin or stdout is not a terminal, with -p or --json, and when TERM is dumb or unset.
Where it runs depends on how you use it:
| Use | Runs in |
|---|---|
malac --plain (interactive) |
The daemon, like the full-screen UI: the session lives in a daemon tab, keeps running when the terminal closes, and other clients see it. If the daemon cannot be reached, it runs in this process instead. |
malac -p, malac --json, piped input, no terminal |
This process. It never starts the daemon, and the run ends with the command, which is what scripts and CI want. |
Reading the output
Section titled “Reading the output”$ malac -y calc.py has a bug in add, fix it and verify with a python one-liner● read calc.py ⎿ 1 def add(a, b): (+1 lines)● edit calc.py ⎿ Edited calc.py: (+2 lines)● bash python3 -c "import calc; assert calc.add(2,3)==5; assert calc.add(-1,1)==0; print('ok')" ⎿ okFixed `calc.py:2`: `add` returned `a - b`, now returns `a + b`. Verified with `python3 -c "..."` → `ok`.─ lora/default · 3.3k context · 366 out · $0.0024 · 2.2s● tool summarystarts a tool call.⎿shows the first line of its result; errors are shown in red.- Subagents get one indented line per step,
↳ label · tool summary, and a closing✓ label finished(or○ cancelled,✗ failed). - The last line summarizes the turn: model, context size, output tokens, cost and duration, plus
cancelledorfailedwhen the turn did not complete. -v(orui.show_thinking = true) also streams the model’s reasoning.
The line REPL
Section titled “The line REPL”malac --plain opens an interactive session with a › prompt, in your last workspace: the same workspace options apply (-n, -w, --name, -c, -r). History is kept in ~/.malac/history.txt, shared with the full-screen UI. Slash commands work as in the full-screen UI. Ctrl+C interrupts the current turn and every subagent it started; Ctrl+D exits, and malac reminds you that the session keeps running in the daemon and that malac --plain returns to it.
When it connects, the banner shows the workspace and tab, and a notice when the daemon runs another malac version: the daemon runs malac 0.1.2; malac daemon restart switches it to 1.0.0.
When a tool call needs approval, malac asks inline:
? bash cargo test --workspace [y] allow [a] always [n] denyy or Enter allows the call, a allows it for the rest of the session, and n, Esc or Ctrl+C denies it. Requests from subagents start with the subagent’s label in brackets.
One-shot prompts
Section titled “One-shot prompts”-p (--print) answers the prompt and exits instead of staying interactive:
malac -p list the public functions in src/lib.rs that have no doc commentmalac exits with status 0 when the turn completes and 1 when it fails, so you can use it in scripts and CI.
Piping stdin
Section titled “Piping stdin”Piped input becomes part of the prompt, wrapped in a <stdin> block after the text you typed:
git diff | malac -p review this changecat error.log | malac -p what is the root causemalac -p < task.mdIf you give a prompt on the command line and stdin stays open without sending anything (as under some IDEs and CI runners), malac stops waiting for stdin after three seconds and runs with the command-line prompt alone. Without any prompt at all, a non-interactive run fails with no prompt given.
Approvals without a terminal
Section titled “Approvals without a terminal”When stdin is not a terminal, nobody can answer a permission prompt. malac warns:
! no terminal to ask for permission: calls that need approval will be denied (use -y)Calls that would ask are denied, and the agent is told so. For unattended runs, use autopilot together with deny rules, or pre-approve exactly what the job needs with allow rules. See Permissions and autopilot and the tutorial Running malac unattended.
JSON output
Section titled “JSON output”--json replaces the rendered output with one JSON object per line, one per event, for the main agent and every subagent:
malac --json -y run the tests and fix what fails > events.jsonl{"agent":"019a1f3c-…","seq":0,"at":"2026-10-25T12:00:00.412Z","type":"agent_started","label":"main","parent":null,"model":"lora/default","cwd":"/repo"}{"agent":"019a1f3c-…","seq":5,"at":"2026-10-25T12:00:01.937Z","type":"tool_started","call_id":"…","tool":"bash","summary":"cargo test","input":{"command":"cargo test"}}{"agent":"019a1f3c-…","seq":9,"at":"2026-10-25T12:00:07.205Z","type":"turn_finished","status":"completed","text":"All tests pass."}--json implies a non-interactive run: malac exits after the main agent’s turn. The event types and their fields are listed in JSON events.
To pull out just the final answer, take the last turn_finished event (subagents finish before the main agent):
malac --json -y summarize this repo \ | jq -rs 'map(select(.type == "turn_finished")) | last | .text'Stderr
Section titled “Stderr”Warnings go to stderr: configuration warnings, MCP servers that could not be reached (for example ! MCP server `github` unavailable: …), and logs. MALAC_LOG controls log verbosity, for example MALAC_LOG=debug. See Environment variables.
malac is created by lobirus and released under the MIT license. About malac