ยท15 min read

Custom Commands and Slash Commands: Building Your Own Claude Code CLI

claude-codetutorialtools

Every developer has workflows they repeat. Commit with a specific message format. Run a standard set of checks before pushing. Generate a boilerplate file with the right structure. Audit code against a design system. Extract lessons from a debugging session.

In most tools, you'd write a shell script, a Makefile target, or a bash alias. In Claude Code, you write a markdown file and invoke it with a slash command.

That's the entire idea. A markdown file becomes a reusable workflow. You type /command-name, Claude reads the file, and follows the instructions. No build step. No registration. No compilation. No plugin system. Just a file in a directory.

How a slash command becomes an executable workflow: the filesystem is the registry, the markdown file is the implementation.

I have been building custom commands for months now. Some are simple -- five lines that run a git command and summarize the output. Some are elaborate -- multi-step workflows with branching logic, tool usage instructions, and output formatting rules. They all share the same foundation: a markdown file that tells Claude what to do. You can see the full collection of commands I use daily in my exact setup walkthrough.

What Slash Commands Are

A slash command is a markdown file that lives in one of two places:

~/.claude/commands/ for user-global commands. These are available in every project, every session. Personal workflows that follow you everywhere.

.claude/commands/ inside a project directory for project-specific commands. These only appear when you're working in that project. Team members who clone the repo get them automatically.

When you type /command-name in a Claude Code session, Claude finds the matching markdown file, reads it, and follows the instructions inside. The filename minus the .md extension is the command name. A file called reflect.md becomes /reflect. A file in a subdirectory like gsd/new-project.md becomes /gsd:new-project.

That's it. There is no command registry. There is no configuration file that maps names to handlers. There is no import or require statement. The filesystem is the registry. The filename is the identifier. The file contents are the implementation.

Anatomy of a Command

A command file is markdown with instructions for Claude. Nothing more. Here's a real example -- a command that reviews recent work and extracts lessons:

# Reflect on Recent Work

Review the recent work in this session and extract actionable lessons.

## Steps
1. Run `git log -5 --oneline --stat` to see recent commits
2. Run `git diff HEAD~1` if relevant
3. Identify: wrong assumptions, overengineering, missed edge cases,
   failed approaches, useful patterns
4. Save each finding with `save_observation` using a "LESSON:" prefix
   in the title
5. Summarize what was captured in 2-3 sentences

When you type /reflect, Claude reads this file and follows it step by step. It runs the git commands. It analyzes the output. It identifies patterns. It saves observations. It summarizes.

The command file is simultaneously the documentation and the implementation. There is no separation between "what the command does" and "how the command works." They are the same document. You read the file, you understand the command. You edit the file, you change the command. The next invocation picks up the changes immediately.

My Command Library

I have built a collection of commands that I use regularly. Each one started as a workflow I kept doing manually -- typing the same sequence of prompts, giving the same instructions, correcting the same mistakes. At some point, the repetition became annoying enough that I wrote it down as a command file.

/reflect

This is the command I run most often. After a coding session where something went sideways -- a wrong assumption, a wasted approach, multiple failed attempts -- I type /reflect and Claude reviews what happened.

It looks at the recent git history, analyzes the diffs, and identifies what went wrong and what went right. Then it saves each lesson to my persistent memory system with a LESSON: prefix so I can search for it later. Over time, this has built a searchable database of things I've learned the hard way. When I start working in a domain I haven't touched in a while, I search for relevant lessons first. The five minutes of reflection after a messy session saves hours of repeating the same mistakes.

/audit

This command checks existing code against my design system. It looks for hardcoded spacing values, inconsistent depth treatment, color values that don't come from design tokens, and pattern violations. Instead of manually scanning files for px values or hex codes that should be CSS variables, I type /audit and get a structured report.

The output is a list of violations grouped by severity. Critical violations break the design system. Warnings indicate drift that should be cleaned up. The format is consistent every time because the command file specifies exactly how to structure the output.

/critique

Different from /audit. Where audit checks against mechanical rules, critique evaluates craft. It looks at a build and asks: does this look like something a thoughtful designer made, or does it look like generic AI output? Are the typography choices intentional? Is the spacing rhythmic? Does the color palette have personality?

The command explicitly instructs Claude to identify anything that "defaulted to generic AI aesthetics" and then rebuild those pieces with more intention. It's the command I run when something technically works but feels flat.

/init

Bootstraps a new UI component with craft and consistency baked in from the start. Instead of creating a bare component file and then iterating on the design, /init front-loads the design thinking. It checks the existing design system, understands the visual language of the project, and generates a component that fits.

/status

Shows the current state of the design system. What tokens are defined. What patterns are in use. What the color palette looks like. It's a quick snapshot that I check before building something new to make sure I'm working with the system, not against it.

/extract

The reverse of /init. Instead of applying the design system to a new component, /extract reads existing code and pulls out the implicit design patterns. It generates a system.md file that documents the typography scale, spacing values, color usage, and component patterns it found. Useful when you inherit a codebase that has a design system embedded in the code but not documented anywhere.

The GSD Command Suite

GSD -- Get Stuff Done -- is a set of 27 commands organized under a gsd/ subdirectory. They cover the full project lifecycle from initial idea to completed, verified work. I wrote a deep dive on Superpowers and GSD that explains the methodology behind these commands. Each one is a markdown file in ~/.claude/commands/gsd/.

The naming convention creates a natural CLI feel. Every GSD command starts with /gsd: followed by the action. The colon is the separator between the directory name and the command name.

/gsd:new-project

Initializes a project with deep context gathering. It doesn't just create a directory structure. It researches the existing codebase, maps dependencies, identifies integration points, and creates a PROJECT.md that serves as the source of truth. The command file instructs Claude to ask clarifying questions before making assumptions -- the kind of upfront work that prevents building the wrong thing.

/gsd:plan-phase

Takes a project phase and creates a detailed execution plan. Tasks with dependencies, risk assessment, estimated effort. The output is structured enough to be machine-readable but clear enough for a human to review and approve before execution begins.

/gsd:execute-phase

Where things get built. This command implements wave-based parallelized execution -- tasks that don't depend on each other run in parallel, and each completed wave gates the next. Every meaningful chunk of work gets an atomic commit. The command file specifies the commit discipline, the wave structure, and the checkpointing behavior.

/gsd:verify-work

Goal-backward verification. Starts from the success criteria defined in the project plan and works backward to verify that the implementation actually achieves them. Catches the failure mode where code works and tests pass but the feature doesn't solve the actual problem.

/gsd:debug

Systematic debugging with persistent state. Instead of shotgun debugging -- try this, try that, hope something works -- this command applies the scientific method. Observe. Hypothesize. Test. Conclude. The command file instructs Claude to form hypotheses before making changes and to track which hypotheses were tested and their outcomes.

/gsd:progress

Checks project progress and routes to the next action. If the current phase is complete, it suggests moving to verification. If there are blocked tasks, it surfaces them. It's the command you type when you sit down and think "where was I?"

/gsd:pause-work and /gsd:resume-work

Context handoff between sessions. /gsd:pause-work captures the current state -- what's done, what's in progress, what's blocked, what the next steps are -- and saves it. /gsd:resume-work loads that state and picks up where you left off. No more spending the first ten minutes of a session figuring out what you were doing yesterday.

And the Rest

The remaining commands cover milestones, todo management, codebase mapping, assumption tracking, phase insertion and removal, research workflows, and settings management. Twenty-seven commands in total. Each one is a markdown file under 100 lines. The entire suite is a folder of text files.

Project-Specific Commands

Commands in a project's .claude/commands/ directory only appear when you're working in that project. This is useful for workflows that are specific to a codebase -- deploy commands, migration scripts, environment setup, test suites with project-specific flags.

The real value is team-level. When you commit the .claude/commands/ directory to your repo, every developer who clones the project gets those commands automatically. No setup. No installation. They type /deploy and the team's deployment workflow runs, written in plain language in a markdown file they can read and understand.

This is how institutional knowledge gets encoded. Instead of a wiki page that says "to deploy, first run the migration, then build the assets, then push to staging, then run the smoke tests," you have a /deploy command that does all of those steps. The wiki page and the implementation are the same document.

Project-specific commands also prevent the "works on my machine" problem with custom workflows. If the deploy command is in the repo, it's version controlled. When the deploy process changes, you update the command file. The next person who runs /deploy gets the updated process. No slack message saying "hey, the deploy steps changed, check the wiki."

Writing a Good Command

I've written enough commands to have opinions about what makes a good one. Here are the principles I've landed on.

Be specific about steps. "Check the code" is vague. "Run eslint ., then check for unused imports with grep -r 'import.*from' src/ | grep -v 'used', then verify types with npx tsc --noEmit" is actionable. Claude follows specific instructions reliably. Vague instructions produce inconsistent results.

Include the tools Claude should use. "Look at recent commits" could mean anything. "Run git log -5 --oneline --stat" is unambiguous. When your command file names the exact tool or command to run, there's no room for misinterpretation. Claude runs what you told it to run.

Specify the output format. If you want a structured report, show the structure. If you want bullet points, say bullet points. If you want a summary in three sentences, say three sentences. Without format instructions, Claude will produce a different format every time you run the command. With them, you get consistent output that you can skim and compare across invocations.

Keep it focused. One command, one workflow. A command that audits code, generates a report, fixes the violations, updates the design system, and commits the changes is doing five things. Make five commands. Each one is easier to write, easier to debug, and easier to compose with others.

Use numbered steps. Claude follows numbered steps more reliably than paragraph-form instructions. Each step should be one action. If you catch yourself writing "and then also" in a step, break it into two steps.

Add negative instructions. "Do NOT create new files." "Do NOT modify code outside the specified directory." "Do NOT ask clarifying questions when the input is unambiguous." These constraints prevent the most common failure modes. Without them, Claude will occasionally do helpful things you didn't ask for. Helpfulness without constraint is a liability.

Commands vs. Skills

Claude Code has two extension mechanisms: commands and skills. They solve different problems and the distinction matters.

Commands are simple, linear workflows. A markdown file with steps 1 through 5. They live in .claude/commands/ directories. They're invoked with a slash. They're good for repetitive sequences of actions that you'd otherwise type out manually every time.

Skills are more complex. They live in ~/.claude/skills/ as directories with a SKILL.md file. They have frontmatter with metadata -- name, description, argument hints, allowed tools. They support argument routing, where a single skill handles multiple subcommands based on the input. They can have security boundaries via the allowed-tools field that restricts which Claude Code tools the skill can access. I go deep on how skills work in the post on why skills are just markdown files.

The heuristic I use: if it fits in one markdown file with under 50 lines of instructions and doesn't need argument routing, it's a command. If it needs to handle multiple subcommands, requires a security boundary on tool access, or involves complex workflows with branching logic, it's a skill.

Commands are quick to write. A few minutes and you have a working workflow. Skills take more thought -- the argument routing table, the tool permissions, the workflow definitions for each subcommand. But skills are more powerful for anything that needs to behave like a mini-application with multiple modes of operation.

In practice, I start with a command. If I find myself wanting to add subcommands or needing to restrict tool access, I promote it to a skill. Most of my workflows stay as commands. The simplicity is the point.

A Command in Five Minutes

Here's how fast you can go from "I keep doing this manually" to "I have a command for that."

Say you review pull requests regularly and always check the same things: type safety, unused imports, test coverage, and adherence to the project's naming conventions. Every review, you type the same prompts. Every review, you forget one of the checks and catch it later.

Create the file:

~/.claude/commands/review-pr.md

Write the instructions:

# Review Pull Request

Perform a structured code review of the current changes.

## Steps

1. Run `git diff main...HEAD --stat` to see changed files
2. For each changed file, check:
   - Type safety: no `any` types, no type assertions without
     justification
   - Unused imports: flag any import not referenced in the file
   - Naming: functions use camelCase, components use PascalCase,
     constants use UPPER_SNAKE_CASE
   - Error handling: async functions have try/catch or .catch,
     no swallowed errors
3. Run `npx tsc --noEmit` to verify type checking passes
4. Run the test suite with `npm test -- --coverage` and flag
   any changed file with less than 80% coverage
5. Summarize findings as a bullet list grouped by file, with
   severity (critical / warning / nitpick) for each item

That took about three minutes to write. Now every PR review starts with /review-pr and you get a consistent, thorough check every time. No forgotten steps. No inconsistent format. And when you decide to add a new check -- say, accessibility attributes on interactive elements -- you add one line to the file.

The iteration cycle is instant. Edit the file, invoke the command, see the result. No rebuild. No restart. The next invocation reads the updated file.

Closing

Custom commands are the fastest way to make Claude Code feel like your own tool. The barrier to entry is knowing how to write clear instructions in a markdown file. The payoff is eliminating every repetitive prompting sequence you currently type by hand.

Start with the workflow you repeat most often. The thing you do every day that takes the same five prompts in the same order. Write those prompts as numbered steps in a markdown file. Save it in ~/.claude/commands/. Invoke it with a slash.

Five minutes of writing saves hours of repetitive prompting. And unlike a shell script, your commands get better over time because Claude interprets the instructions with full context awareness. It knows your codebase. It knows your conventions. It reads your CLAUDE.md. The command file provides the workflow. Claude provides the intelligence.

That combination -- a structured workflow executed by a capable agent -- is more powerful than either piece alone. A good command turns Claude Code from a general-purpose assistant into a specialized tool that works the way you work. And all it took was a markdown file.