Skip to main content
Slash commands turn markdown files into reusable, parameterized prompt templates: drop review-pr.md into .chatcli/commands/ and /review-pr 1326 security becomes a full, expanded prompt — in the REPL, inside a running /coder session, in one-shot -p scripts, through the messaging gateway, and over ACP and MCP. Expansion happens before the request is built, so commands work identically with every provider ChatCLI supports. And if your team already keeps commands for Claude Code, Devin, Windsurf, Cursor, opencode, Codex, Gemini CLI, Qwen Code or GitHub Copilot, those files work here unchanged — zero migration.

Commands vs. skills

Both appear in the completer and in the /menu palette.

Where commands live

Scanned in precedence order — first hit for a name wins. Native ChatCLI dirs come first, then the interop matrix: if your team already uses any of these agent CLIs, their command files work in ChatCLI unchanged. Subdirectories become namespaces: frontend/deploy.md/frontend:deploy (Gemini’s git/commit.toml/git:commit). Foreign frontmatter keys (agent, subtask, auto_execute_steps) are tolerated; opencode’s model maps to ChatCLI’s model hint, and mode is a first-class ChatCLI key (see the execution-mode section below). Frontmatter that isn’t even valid YAML — like Codex’s own documented argument-hint: [FILES=<paths>] shape — falls back to a line-wise read instead of dropping the file. A command can never shadow a built-in command — files named session.md, config.md etc. are refused and reported in /config commands.

Anatomy of a command

Placeholders (every dialect’s syntax works in every file): Unknown $WORDS pass through untouched.

Pre-execution lines (!)

A line starting with ! runs a shell command and embeds its output into the expanded prompt. The inline dialects work too — Gemini’s !{cmd} and opencode’s !`cmd` substitute in place mid-sentence. Every occurrence, whole-line or inline, goes through the same security gate as coder tools:
  1. Safety-immune commands (rm -rf, sudo, …) always require interactive approval — never auto-approved, not even by automode.
  2. Your /policy rules apply (allow / ask / deny), and approval decisions can persist new rules (allow-always / deny-forever).
  3. On unattended surfaces (gateway, MCP, ACP, scheduler) an “ask” resolves through /policy automode — or fails safe to deny. A denied line is replaced by an explicit marker so the model knows the output is missing, never silently empty.
Lines inside fenced code blocks are never executed — documentation stays documentation.

allowed-tools

When set, the run initiated by the command gets an ephemeral overlay on the security gate: a tool outside the list escalates from allow to ask — the human (or policy automode) arbitrates the exception. It never silently widens permissions and never silently denies. Matching is case-insensitive and the leading @ is optional (coder@coder). Names are matched exactly against the tool names below — a name outside the catalog never matches, which means every tool call in that run escalates to ask. In particular, Claude Code’s tool names (Read, Write, Edit, Bash) do not exist in ChatCLI — don’t copy them into a command’s frontmatter.

Available tool names

The coder engine is a single tool, @coderread, write, patch, multipatch, search, tree, exec, git-status, git-diff, git-log, test, rollback and friends are its subcommands, not standalone tools. Allowing @coder allows all of them (each execution still goes through /policy and the interactive gate). External plugins from ~/.chatcli/plugins participate under their own names (e.g. @docker-ps). @model is only registered while the model-routing tool is enabled (CHATCLI_AGENT_MODEL_TOOL).

MCP tools

Tools from connected MCP servers appear as mcp_ + the tool’s own name exactly as the server declares it — no server prefix. A server named github declaring create_issue becomes mcp_create_issue. Example:
Any other MCP tool the model reaches for during that run escalates to an interactive ask instead of running silently.

Execution mode & the chat auto-route

Chat mode is tool-less by design — a command whose body drives tools would just be refused there. The mode: key declares where the command is meant to run, and the chat dispatcher honors it:
  • mode: coder (or inferred from a non-empty allowed-tools): invoked in REPL chat, the command prints a notice (⚡ /deploy runs with tools…) and auto-routes through a coder one-shot run — same engine, security gate and allowed-tools overlay as a manual /coder, returning to the chat prompt when the loop reaches its final answer. chatcli -p "/deploy prod" routes the same way (notice on stderr).
  • mode: chat explicitly vetoes the inference — the command stays a plain conversational turn even if it declares allowed-tools.
  • Absent or unrecognized values never invalidate the file: resolution falls back to the inference, then to chat.
Interop files from Claude Code and friends that carry allowed-tools therefore run correctly with zero edits. Routed commands are marked [coder] in the completer, /menu and /config commands. Headless surfaces (gateway, ACP, MCP, scheduler) keep their existing behavior — the auto-route only fires on the interactive REPL and the -p one-shot. Opt out globally with CHATCLI_COMMANDS_AUTOROUTE=off.

Surfaces


Managing

The panel groups the catalog by source with counts and argument hints, then the failure ledgers — commands refused for shadowing a built-in and files skipped with the parse reason — and every scanned directory with a / existence marker:
The catalog is served from a stat-fingerprint cache: lookups are free until a file actually changes.

Example: team standup

Commit it — everyone on the team (and their agents, via @commands) now has /standup 2.