Agent CLI
mur agent cli opens an interactive, streaming terminal chat with a running MUR agent β tokens stream live, tool calls show as step cards, and risky actions pause for your approval.
mur agent cli coach # chat with the "coach" agent
murmur coach # quick form β the murmur symlink is identical
mur agent cli coach --resume # continue the last conversation
mur agent cli dev qa ops # three agents, one tiled pane each
murmur # bare murmur opens the concierge
The agent must be running (mur agent status <name>; start one with mur agent start <name>). Multiple names open one pane per agent β tmux is preferred, with zellij / WezTerm / kitty auto-detected. See Agent Lifecycle for start/stop/remove and what changes once it runs as a service.
Keys
| Key | Action |
|---|---|
Enter | Send the message |
Alt+Enter | Insert a newline |
β / β | Move through the completion menu (when open) |
Tab / Enter | Accept the highlighted completion or suggestion |
Esc | Dismiss the menu Β· clear the input Β· cancel a streaming turn |
! (line start) | Run the rest of the line as a shell command on your machine |
Ctrl+V | Paste a screenshot from the clipboard |
Ctrl+O | Open the full transcript in a pager |
Ctrl+D | Quit |
Completion menu
Type / to open a filtered menu of everything you can invoke β built-in slash commands and the agent's own skills. Keep typing to filter, ββ to move, Tab or Enter to accept, Esc to dismiss. Commands expand to a second layer (for example /mcp β list Β· add Β· remove Β· β¦), and the agent's skills appear by name so you can drop one straight into your message.
The second layer is not always a fixed list. Four commands complete from the agent itself, so what you see is what this agent can actually do right now:
| Command | Second layer comes from |
|---|---|
/effort | The reasoning levels this agent's model accepts β read from the model registry, not a fixed scale |
/model | Your registry aliases, each labelled with the model id behind it |
/secret | The credential keys this agent already holds, plus --delete |
/forget | This agent's own notes, last first |
/effort is the one where the difference bites. Effort levels are an arbitrary subset per model, never a prefix of one scale: Claude Opus 4.6 has max but no xhigh, DeepSeek V4 publishes low/high/max with no middle step, Qwen and GLM are a two-position switch, and Magistral rejects the parameter outright. The menu asks the same table the command does, so a model with no reasoning control opens no menu at all rather than offering a level it would have to silently drop. Switch models with /model and the levels on offer change with it.
The menu marks where you are
Five commands are settings rather than actions, and their menu shows the value in force β a green β on the row you are already on: /model, /effort, /skin, /auto, /verbose. Effort is marked from the session first and the profile second, so a /effort high you set for this conversation shows as the current level even though the profile still says something else. A level nobody has set marks nothing, because the API default belongs to the model and a β on a guess would be a lie. Action menus β /mcp, /skill, /login, /secret, /forget β mark nothing; there is no value in force to mark.
Completing a shell line
A line that starts with ! completes too, from your machine rather than the agent. The first word offers executables on your $PATH, scanned once when you first ask. Every word after that offers paths under the chat's working directory: directories come first with a trailing /, and accepting one keeps the menu open on its contents so you can walk down a tree with Tab. ~ expands to your home. Hidden entries stay hidden until you type the leading dot.
Slash commands
| Command | Does |
|---|---|
/help | Show the in-chat cheatsheet |
/clear | Start a fresh conversation |
/card | Show the agent's A2A card |
/sessions | List past conversations |
/channels [N] [--follow] | List channels, switch to channel N, or live-tail channel N while you keep chatting. Each channel has a stable number assigned at creation and never reused, so deleting one never renumbers the rest; /channels 01a0d420 or #01a0d420 switches by short id. The status line reads β΅ 2 Β· 01a0d420 |
/review [--main <agent> --reviewer <agent> <task> | resume <session>] | Run a Fleet Review loop inside the chat; bare /review lists sessions you can resume |
/search <query> [--all] [--limit N] [--lines N] [--send] | Search the project index from the chat β one header plus a three-line preview per hit, with β¦ +N lines where a chunk was cut. --lines N sets the preview depth, --all widens past the current project, --send hands the results to the agent as your message |
/search --expand <id>[,<id>β¦] | Print the full chunk for a hit from the last result set, by the id shown beside it. Cannot be combined with the query flags |
/model [N|name] | List the registry's models (current one marked, with prices) or switch to another β the next message uses it, no restart. An agent with a fallback chain switches its primary and keeps the chain; the pick is saved to the profile either way |
/auto [on|off] | Toggle session-wide auto-approval of tool calls β on by default; --ask starts a session ask-first |
/mcp β¦ | Manage the agent's MCP servers (list Β· add Β· remove Β· β¦) |
/skill β¦ | Manage the agent's skills (list Β· add Β· remove) |
/login [anthropic|chatgpt] | OAuth health for every provider, or repair one β re-read the credential, ask the owner CLI to refresh, and only then a browser login |
/secret <KEY> [--delete] | Hand the agent a credential through a hidden prompt β the value never enters the chat, and the model only ever sees $KEY |
/effort [level] [--save] | Show the reasoning levels this agent's model accepts and set one for the conversation; --save writes it to the profile |
/verbose [on|off] | Expand tool cards to their full arguments and output |
/remember [--kind rule|fact] <text> | Save an agent-local memory note |
/instruct [--kind rule|fact] <text> | Save a permanent instruction β added to the context every turn, never ranked or truncated |
/instruct-edit <name> <text> | Rewrite a permanent instruction |
/pin <name> | Make an existing memory a permanent instruction; refused if it would not fit the budget |
/unpin <name> | Turn a permanent instruction back into a remembered-when-relevant memory (always confirmed) |
/memories | List every memory this agent can see, labelled by scope |
/forget <name|last> | Drop an agent-local memory so it stops being injected |
/open | What is still outstanding, observed and reported kept apart |
/panel [tab] | Open or drive the MUR Hub companion window |
/skin [ansi|light|mur|clay] | Switch the colour theme and remember it. ansi (the default) follows your terminal's own colours; dark still works as an alias of it; clay is warm terracotta on dark |
/quit | Exit (/exit also works) |
Running a command yourself
Start a line with ! and the rest runs on your machine, in your shell, in the chat's working directory:
!cargo test --lib
The command and its output appear as their own card, and the same block goes to the agent as your message the moment the command finishes β so the next thing you type can be "why did that fail?" with nothing pasted. If the agent is mid-reply when the command lands, the output steers the turn already running instead of starting a second one.
Output is captured whole (stdout and stderr, with the exit code when it is non-zero) up to a byte cap, and a command that runs too long is cut with a note rather than hanging the chat. Nothing is run without you typing it: ! is a line prefix, not something the agent can invoke.
Handing over a credential
Sooner or later an agent needs a token β a Gitea deploy key, an API key for a service it has to call. Pasting it into the chat puts the plaintext in three places at once: the model's context, the agent's signed append-only channel on disk, and your model provider. Pattern-based redaction does not save you; it recognises sk-, ghp_, AKIA and a handful of other shapes, and a bare hex token matches none of them.
/secret is the way in:
/secret GITEA_TOKEN
Enter value for GITEA_TOKEN (input hidden, Enter alone cancels): ****
β GITEA_TOKEN available as $GITEA_TOKEN
The value is read with echo off, outside the chat box β it never reaches your input history, the transcript, or the channel. Only the KEY goes on the command line; a value typed there is ignored rather than accepted.
What the agent gets is an environment variable. Its shell commands can use $GITEA_TOKEN, but the model is only ever told the name, and every tool result is scrubbed of the value on the way back:
> print the length of $GITEA_TOKEN and then its value
LEN=40 VAL=[SECRET:GITEA_TOKEN]
Tell the agent to use it by name β curl -H "Authorization: token $GITEA_TOKEN" β and keep it out of URLs and git remotes, where it would land in .git/config.
Secrets are per-agent and survive restarts: the value goes to your OS keychain (the same slot mur agent secret set uses) and the name to the agent's profile, while the running agent is told immediately so nothing needs restarting. /secret with no key lists the names you have set; /secret <KEY> --delete revokes one.
If the agent is not running, the command says so in as many words β saved to keychain β Β· running agent: not reached β rather than showing a tick that would have you believe the agent already has it.
What this does and does not protect. The guarantee is that the plaintext never enters the model's context. Masking tool output on top of that is a second line of defence, not the first: it is a literal string match, so a value the agent re-encodes (base64, xxd) before printing is not caught. Anything you hand an agent, it can use β treat the token's scope, not the masking, as the real boundary.
Suggested replies
When the agent wants you to pick from a short set of options, it offers them as Tab-to-fill suggestions right in your input box β no copy-paste. A single suggestion shows as greyed ghost text you accept with Tab; several show as a small picker you move through with ββ and accept with Tab/Enter. Start typing to ignore them.
Proposal chips
When the agent wants you to run something rather than pick a reply, it offers one command as a chip above the input instead of telling you to retype it.
| Kind | Key | What happens |
|---|---|---|
| shell | Tab on an empty input | !command is placed in the input for you to review and send |
| slash | Tab on an empty input | /command is placed in the input for you to review and send |
| restart | Enter on an empty input, agent idle | restarts the agent that proposed it β typically after /mcp or /skill changes |
Nothing runs on its own: Enter on a shell or slash chip does nothing, and a restart chip can only restart the agent that offered it. A chip disappears 120 seconds after the turn finishes; Ctrl+K on an empty input pins it, and pressing it again unpins it. A new chip replaces the old one. Proposals are vetted before they reach you β placeholders such as <name>, control characters and secret-shaped strings are rejected back to the agent.
Permanent instructions
Ordinary memories compete for a share of the prompt and may lose to a better match. A permanent instruction does not compete: it is injected every turn, before ranking, and is never truncated. That is a guarantee that the text is in front of the model, not that the model obeys it.
Only you can create one β with /instruct, or by promoting a memory with /pin. The agent's own remember tool always saves ordinary memories. Permanent instructions share a fixed budget of 3,500 tokens. A write that would overflow it is refused with the size it needs and the commands that free room (/memories shows each one's size). The same check runs before every turn, from the chat, --plain, and mur agent send alike.
Live fleet progress
When the agent you are chatting with kicks off a fleet (the concierge's fleet_run tool β which returns a run_id at once and lets the agent carry on), the TUI arms two things automatically β no flag, no command:
- a member rail at the bottom: one row per fleet member with its live state (working / blocked on approval / done / failed), the sub-goal it was handed, and elapsed time;
- a milestone feed in the transcript: delegations with their sub-goal, each member's own completion summary with how long the turn took, run outcomes, loop-guard notes, and approval gates (with the exact
mur channel approve β¦command inline).
Milestones come from the fleet's signed channel, so what you see is what was recorded β not a narration. The raw event stream is still there when you want it: /channels N --follow live-tails any channel line by line, and mur agent cli <name> --fleet <fleet> arms the rail at launch for a fleet you are about to run by hand.
The rail's collapsed job line (job N/M Β· K β failed) counts failures from the moment it armed, not the whole queue's history β a fleet dispatched a second time no longer inherits a stale failure count from an earlier run. When there is an older, unrelated failure still sitting in the queue, the line splits it out explicitly: K β failed (this run) vs. K β failed (queue, before this run). mur fleet jobs <name> --since <run-id> gives you the same split from the CLI, scoped to one dispatch's run_id (see Fleet Loops).
Tool approvals
A session starts with auto-approve on: read and write tool calls run without asking, and the AUTO badge in the status bar says so the whole time. Auto-approve has a ceiling it cannot lift β spend, destructive, privileged and network-egress calls (rm -rf, curl, sudo, git push --force) still stop and ask, even in the default session.
Browser MCP tools are tiered by what they do to your logged-in web session, whatever name you gave the MCP server:
| Browser tool | Tier | Under AUTO |
|---|---|---|
Observers β browser_snapshot, browser_take_screenshot, browser_console_messages, browser_network_requests, browser_wait_for, browser_verify_*, β¦ | read | runs |
browser_storage_state, browser_set_storage_state (cookies and tokens) | privileged | asks |
Everything else β browser_navigate, browser_click, browser_fill_form, browser_evaluate, β¦ and any browser_* tool added later | network-egress | asks |
The read list is an allow-list on purpose: a new upstream browser tool asks until it is known to be harmless.
To have the agent pause before any write or otherwise risky tool call and show you exactly what it's about to run β approve or deny in place β start the session ask-first:
mur agent cli coach --ask # ask before every risky tool call this session
mur agent cli coach --ask --no-auto-reads # β¦and ask for reads too β the strictest setting
mur agent cli coach --auto # the default, spelled out (kept for older scripts)
You can also toggle full auto-approval mid-chat with /auto on / /auto off. Nothing is persisted β a fresh session starts from the default again, not from where the last one left off.
read_file and shell commands a conservative classifier can prove read-only (cat, ls, grep, git status, β¦) are auto-approved by default, even under --ask β anything it cannot prove still asks. --no-auto-reads puts the prompt back on reads; the older --auto-reads is still accepted and does nothing. It behaves the same in --plain mode, so a piped or headless run doesn't stop on every read.
/auto off also revokes the per-tool grants made with [a], and the AUTO: badge in the status bar names the tools currently muted rather than only counting them β so you can see what you granted, and take it back.
Answering a gate
An open gate always renders somewhere: inline on the step card that triggered it, or as a centred modal when that card has scrolled out of the live view. The status line names the tool and counts down to the auto-deny.
| Key | Action |
|---|---|
y | Approve this call |
a a | Always allow this tool for the session β two presses |
A A | Always allow every tool for the session β two presses |
n / Esc | Deny |
Decision keys only count while the composer is empty. Once you have started typing, y / a / n are ordinary characters β a message that happens to contain one cannot decide the gate behind your back. The session-wide grants take two presses for the same reason: a single keystroke should never hand out blanket approval.
If nobody answers, the gate expires, the call is denied, and the status line stops asking.