Building Slash Commands for Your CLI Agent
A slash command is a deterministic escape hatch from the model — instant, free, predictable. This guide builds cli agent slash commands, including user-defined ones from a config directory.
def handle_input(text, state):
if text.startswith("/"):
parts = text[1:].split(maxsplit=1)
cmd = parts[0]
arg = parts[1] if len(parts) > 1 else ""
handler = COMMANDS.get(cmd)
if handler:
return handler(arg, state) # runs instantly, no model call
return f"Unknown command: /{cmd}. Type /help."
return run_agent(text, state) # normal path -> the model
COMMANDS = {
"help": lambda a, s: "\n".join(f"/{k}" for k in COMMANDS),
"clear": lambda a, s: (s["messages"].clear(), "Context cleared.")[1],
"model": lambda a, s: s.update(model=a) or f"Model set to {a}",
}Picture this: you're a developer three hours into a debugging session with your CLI agent, the context is a mess, you want to switch to a cheaper model for a quick question, and your only option is to quit and restart — losing everything. That friction is exactly what cli agent slash commands solve. A slash command is a deterministic, instant escape hatch from the model: type /clear, /model haiku, or /save, and your code handles it directly, no API call, no waiting, no ambiguity. This guide gives you a working slash-command system you can drop in today.
By the end you'll have /help, /clear, /model, and /save working, plus a pattern for users to define their own — all bypassing the model entirely for speed and predictability.
Quick-Start (Copy This Right Now)
Here's the whole mechanism: intercept input that starts with a slash before it ever reaches the model, and dispatch to a local handler.
[object Object], ,[object Object],(,[object Object],):
,[object Object], text.startswith(,[object Object],):
parts = text[,[object Object],:].split(maxsplit=,[object Object],)
cmd = parts[,[object Object],]
arg = parts[,[object Object],] ,[object Object], ,[object Object],(parts) > ,[object Object], ,[object Object], ,[object Object],
handler = COMMANDS.get(cmd)
,[object Object], handler:
,[object Object], handler(arg, state) ,[object Object],
,[object Object], ,[object Object],
,[object Object], run_agent(text, state) ,[object Object],
COMMANDS = {
,[object Object],: ,[object Object], a, s: ,[object Object],.join(,[object Object], ,[object Object], k ,[object Object], COMMANDS),
,[object Object],: ,[object Object], a, s: (s[,[object Object],].clear(), ,[object Object],)[,[object Object],],
,[object Object],: ,[object Object], a, s: s.update(model=a) ,[object Object], ,[object Object],,
}What this does: Checks whether input begins with a slash and, if so, routes it to a local handler that runs immediately instead of calling the model. Everything else falls through to the normal agent path. The slash is the signal for "this is a command, not a question."
Wire this in front of your loop and you've added instant, free, predictable controls to an otherwise model-driven tool.
Understanding the Variables
The COMMANDS map is your command registry — command name to handler function. Each handler receives the argument string and the session state, and returns text to display. Keeping handlers in one map makes /help trivial (just list the keys) and adding a command a one-line change.
The state object is the shared context slash commands operate on: the message history, the current model, config flags, whatever your session tracks. Commands mutate state directly — /clear empties the messages, /model swaps the active model — which is exactly why they're powerful. They reach into the machinery the model can't touch.
The leading-slash check is the whole routing decision. Anything starting with / is a command; everything else is a prompt. That one-character convention gives users a clear, memorable boundary between talking to the agent and issuing orders about it.
⚡ Pro tip: Make /help list commands with one-line descriptions, not just names. Store each command as a {handler, help} pair so /help renders " /model <name> — switch the active model". Discoverability is the entire reason slash commands beat memorized flags; a bare list of names throws that away.
Why Do cli agent slash commands Bypass the Model?
Because some actions shouldn't be a language problem. Asking the model to "clear the context" is slow, costs tokens, and might not do exactly what you mean. A slash command is deterministic: /clear clears, every time, in a millisecond, for free. That reliability is the point.
There's a deeper reason too. The model can't reliably perform meta-operations on its own session — it can't truly wipe its own history mid-conversation or switch the model it's running on, because those are properties of the harness around it, not things it controls. Slash commands give the user direct access to that harness. They're the control panel; the model is the engine.
[object Object], ,[object Object],(,[object Object],):
name = arg ,[object Object], ,[object Object],
path = pathlib.Path.home() / ,[object Object], / ,[object Object],
path.write_text(json.dumps(state[,[object Object],], indent=,[object Object],))
,[object Object], ,[object Object],
COMMANDS[,[object Object],] = cmd_saveWhat this does: Adds a /save <name> command that writes the current conversation to a named file — a deterministic operation you'd never want to route through the model. The user gets exact, repeatable behavior from a two-word command.
⚠️ Common mistake: Routing meta-commands through the model instead of intercepting them. If a user types "clear the context" and you send it to the model, you get an unreliable, expensive approximation of an action that should be instant and exact. Reserve a deterministic command layer for anything that manipulates the session itself.
Step-by-Step: Adding User-Defined Commands
The best slash-command systems let users define their own — a /deploy that expands to a favorite prompt, a /review that runs a standard code-review request. Load these from a config directory so users extend the agent without touching your code.
[object Object], pathlib, json
,[object Object], ,[object Object],():
d = pathlib.Path.home() / ,[object Object], / ,[object Object],
d.mkdir(parents=,[object Object],, exist_ok=,[object Object],)
cmds = {}
,[object Object], f ,[object Object], d.glob(,[object Object],):
template = f.read_text()
,[object Object],
cmds[f.stem] = ,[object Object], arg, s, t=template: run_agent(
t.replace(,[object Object],, arg), s)
,[object Object], cmdsWhat this does: Scans a ~/.agent/commands/ directory for markdown files and turns each into a slash command whose body is a prompt template. A file review.md containing "Review this code for bugs: {{input}}" becomes /review, expanding the template and running it through the agent.
The step-by-step: create the commands directory, drop in a markdown file per command with {{input}} placeholders, load them at startup, and merge with your built-ins. Users now build their own vocabulary of shortcuts, and they carry those files between machines.
⚡ Pro tip: Let built-in commands win name collisions, but warn the user. If someone defines /clear.md, they probably didn't mean to shadow the built-in — print "note: /clear is built-in, your custom one is ignored" so the behavior isn't silently confusing.
How Do You Parse Slash Command Arguments and Flags?
Real commands need more than a single argument string. /model haiku --temporary, /save my-session --overwrite — once commands take flags, naive splitting breaks down and you need a small, consistent parser so every command handles arguments the same way.
[object Object], ,[object Object],(,[object Object],):
tokens = raw.split()
positional, flags = [], {}
,[object Object], t ,[object Object], tokens:
,[object Object], t.startswith(,[object Object],):
key, _, val = t[,[object Object],:].partition(,[object Object],)
flags[key] = val ,[object Object], ,[object Object], ,[object Object],
,[object Object],:
positional.append(t)
,[object Object], positional, flagsWhat this does: Splits a command's argument string into positional values and named flags, treating --flag as a boolean and --key=value as a pair. Every handler receives the same clean structure, so flag handling is consistent instead of reinvented per command.
The consistency is the point. When /save and /export both parse arguments the same way, users learn one convention and it works everywhere. Ad-hoc parsing in each handler produces a tool where --force works on one command and -f on another and neither on a third, which is exactly the kind of papercut that makes a CLI feel amateurish.
⚡ Pro tip: Validate flags against a per-command allowlist and reject unknown ones with a suggestion. A user who types /save --overwite (misspelled) should get "unknown flag --overwite, did you mean --overwrite?" rather than silent no-op behavior. Catching typos on flags is a small thing that makes the difference between a tool that feels solid and one that feels flaky.
Pro-Level Variations
Teams shape slash commands to their workflow in ways worth stealing.
A DevOps engineer adds /env staging and /env prod that switch which cluster the agent's tools target, making environment a deliberate slash command rather than something buried in each request.
A technical writer builds a library of /tone, /summarize, and /expand commands as prompt templates, turning repetitive editing instructions into two-keystroke shortcuts she reuses across every document.
A data analyst wires /sql to a command that constrains the agent to read-only query mode for the rest of the session, using state to enforce a safety boundary through a single command.
Each extends the same registry-and-state pattern. The mechanism is fixed; the vocabulary is yours.
⚡ Pro tip: Add tab-completion for slash commands. The moment a user types /, offer the command list — it turns your control layer from something users must memorize into something they discover by pressing a key. Completion and slash commands together are what make a terminal agent feel like a real tool.
Troubleshooting Common Issues
If a slash command seems to do nothing, check that you're intercepting before the model call, not after — the whole point is that these never reach the API. If arguments come through mangled, remember split(maxsplit=1) keeps everything after the first space as one argument, which is usually what you want for prompt templates.
If users can't find commands, your /help is failing them — add descriptions and tab-completion. And if a custom command collides with a built-in, decide your precedence rule and surface it, rather than letting one silently win.
One more thing users expect from a real command surface: history. If your agent runs an interactive session, wire the up-arrow to recall previous inputs — slash commands included — using your prompt library's history support. A control layer people can't scroll back through feels half-finished, because retyping /model claude-sonnet-5 for the tenth time is exactly the friction slash commands were supposed to remove. Persist that history to a file between sessions and the recall survives restarts, which turns your command layer into something with real muscle memory behind it.
Finally, keep the built-in set small. It's tempting to add a slash command for everything, but a control layer with forty built-ins is as hard to remember as the flags you replaced. Ship the handful everyone needs — help, clear, model, save — and let users grow their own vocabulary through custom commands rather than bloating your defaults.
Your Turn
Add a slash layer to your agent: intercept leading slashes, build a command registry with /help, /clear, and /model, then load user-defined commands from a config directory. You'll have turned a chat loop into a controllable tool with a real command surface.
The user-defined commands are prompt templates in disguise — the /review, /summarize, and /deploy bodies are exactly the reusable prompts worth curating. Storing them in a library like PromptABCD means your best cli agent slash commands travel with you across projects and machines, so the shortcuts you tune once become a permanent part of how you work in the terminal.
Continue Reading
Save the prompts from this post
PromptABCD is a free prompt manager. Paste, organize, and reuse your best AI prompts — no more hunting through chat history.
