Stop googling shell commands. Type what you want, get the command, run it.
# Linux / macOS — install in 10 seconds
curl -sSL https://github.com/miuzel/comma-cli/releases/latest/download/install.sh | bash
# or via Homebrew (macOS / Linux)
brew install miuzel/tap/comma-cli# Windows (PowerShell) — install to D:\tools\bin
$dir = "D:\tools\bin"; Invoke-WebRequest -Uri "https://github.com/miuzel/comma-cli/releases/latest/download/comma-windows-x86_64.zip" -OutFile "$dir\comma.zip"; Expand-Archive -Path "$dir\comma.zip" -DestinationPath $dir -Force; Rename-Item "$dir\comma.exe" "c.exe"; Remove-Item "$dir\comma.zip"# Use it
, find all TODO comments in python files
# → rg -n TODO --type py # Find TODO comments in Python files
# → [Enter] to executeThat's it. No sessions, no runtime, no dependencies. Just a 2.5MB binary that turns intent into shell commands.
You're in the terminal. You want to:
- Compress a video for Slack
- Find files modified today larger than 100MB
- Check which ports are in use
- Extract audio from a video file
You know what you want, but can't remember the exact flags. So you:
- Open a browser
- Search "ffmpeg compress video"
- Read 3 Stack Overflow answers
- Copy-paste something that might work
- Debug it for 5 minutes
Or you could just type:
, compress video to 10mb
# → ffmpeg -i input.mp4 -b:v 8M -b:a 128k output.mp4The key difference: , is a command generator, not an agent.
, |
ChatGPT / Codex / Claude Code | |
|---|---|---|
| What it does | Generates ONE shell command | Has conversations, writes code, executes tasks |
| State | Stateless — no memory between calls | Maintains conversation history |
| Scope | Single command | Multi-file editing, refactoring, debugging |
| Size | 2.5MB binary | 100MB+ runtime (Node.js, Python) |
| Startup | Instant | 2-5s cold start |
| Dependencies | None | Node.js, Python, npm, etc. |
| Privacy | Placeholders (no personal data sent) | Full context sent |
| Use case | "I need a command" | "I need to build a feature" |
# You know what you want, just need the command
, find all TODO comments in python files
, compress video to 10mb
, check which ports are in use# You need a conversation, not just a command
"Help me refactor this function to be more efficient"
"Debug why this test is failing"
"Write a Python script that processes CSV files"
Think of it this way:
- ChatGPT is a conversation partner — you talk back and forth
,is a command translator — you say what you want, get the command, done
The , philosophy: The terminal is for doing, not talking. One intent → one command → execute → done.
Configure multiple providers with automatic fallback:
{
"providers": {
"cerebras": {
"base_url": "https://api.cerebras.ai/v1",
"auth_token": "csk-xxx",
"api_style": "openai"
},
"anthropic": {
"base_url": "https://api.anthropic.com",
"auth_token": "sk-ant-xxx"
}
},
"models": [
{"provider": "cerebras", "model": "llama-3.3-70b", "retries": 2},
{"provider": "anthropic", "model": "claude-sonnet-4-20250514", "retries": 1}
]
}api_style can be "openai" (chat completions, the default), "responses" (OpenAI Responses API, /v1/responses) or "anthropic". It's auto-detected from the base URL (anthropic or responses in the URL) when omitted.
After getting a command, you can:
- Enter — Execute as-is
- e — Edit inline (pre-filled, use arrow keys)
- r — Refine via LLM ("add --dry-run")
- Esc — Cancel
For scripts and agents, add ! to skip all confirmations:
, find large files ! # auto-execute
, compress video to 10mb ! # auto-explore + auto-executeThe model checks what's installed before suggesting commands:
$ , compress this image
▸ Checking: convert magick ffmpeg
Available: ffmpeg
Not found: convert, magick
ffmpeg -i input.png -quality 85 output.jpg
Check for updates and update the binary from GitHub releases:
, --update
# ▸ Checking for updates (current: 0.22.3)...
# Update available: 0.22.3 → 0.23.0
# Release notes (0.23.0):
# ...changelog...
# Upgrade now? [Enter/y/N]
# ▸ Updated to 0.23.0The release changelog is shown before you confirm — the binary is never replaced unasked. The downloaded archive is verified against the release's sha256sums.txt before the binary is replaced.
The weekly auto-update check (auto_update in the config) works the same way: when a new version is found, the changelog is shown and you choose whether to upgrade. Declining offers to disable future checks — answering y writes auto_update: false to your config for you.
When unsure about a tool, the model runs help first:
$ , compress video using ffmpeg
▸ Exploring: ffmpeg -h
▸ Learning from output...
ffmpeg -i input.mp4 -b:v 8M output.mp4
Probe commands always ask for confirmation before running (a single probe too), unless you pass !.
When the answer depends on current information (latest versions, recent changes, download URLs), the model can search the web first via #SEARCH::
$ , upgrade rust to the latest stable release
▸ Searching the web: latest stable rust version
rustup update stable # Update Rust to the latest stable toolchain
Search results (titles, URLs, snippets) are fed back to the model, which then generates the final command. Backends that can return richer per-result content do so natively — no page fetching on our side: Tavily includes the cleaned page text (search_depth: "advanced" + include_raw_content — note the advanced depth costs 2 credits per search), Brave uses the LLM Context endpoint (/res/v1/llm/context), which returns pre-extracted page content made for LLM grounding — included in every Search plan; DuckDuckGo, Mojeek and SearXNG only have snippets. Search is off by default — pick a backend in the search config to enable it (see below). Brave and Tavily need an API key; DuckDuckGo and Mojeek are keyless but scrape result pages, which can trigger anti-bot measures on some networks; a self-hosted SearXNG instance works great too. Search queries are model-generated and never contain your username, hostname, or paths.
The interface speaks 9 languages — English, 中文, 日本語, 한국어, Français, Deutsch, Español, Português, Русский:
COMMA_LANG=fr , --help # or set "lang": "fr" in config.jsonLanguage is auto-detected from your system locale (LANG/LC_ALL); COMMA_LANG overrides it, and lang in the config takes top priority.
, works with any OpenAI or Anthropic compatible API. Here are some great options:
| Provider | Model | Speed | Cost | Best for |
|---|---|---|---|---|
| Cerebras | gemma-4-31b |
⚡ Ultra-fast | Free tier | Quick commands, high throughput |
| Groq | llama-3.1-8b-instant |
⚡ Ultra-fast | Free tier | Low latency, real-time use |
| Provider | Model | Best for |
|---|---|---|
| Moonshot | kimi-k2.7-coding |
Shell commands, code generation |
| DeepSeek | deepseek-v4-flash |
Fast inference, coding tasks |
| Tool | Model | Best for |
|---|---|---|
| Ollama | qwen3.6-35b-a3b |
Privacy, offline use |
| vLLM | Any model | Self-hosted, high throughput |
Cerebras (fast, free):
{
"base_url": "https://api.cerebras.ai/v1",
"auth_token": "your-api-key",
"model": "gemma-4-31b"
}Ollama (local):
{
"base_url": "http://localhost:11434/v1",
"auth_token": "ollama",
"model": "qwen3.6-35b-a3b"
}DeepSeek:
{
"base_url": "https://api.deepseek.com/v1",
"auth_token": "your-api-key",
"model": "deepseek-v4-flash"
}Multi-provider fallback:
{
"providers": {
"cerebras": {
"base_url": "https://api.cerebras.ai/v1",
"auth_token": "csk-xxx"
},
"deepseek": {
"base_url": "https://api.deepseek.com/v1",
"auth_token": "sk-xxx"
},
"ollama": {
"base_url": "http://localhost:11434/v1",
"auth_token": "ollama"
}
},
"models": [
{"provider": "cerebras", "model": "gemma-4-31b", "retries": 2},
{"provider": "deepseek", "model": "deepseek-v4-flash", "retries": 1},
{"provider": "ollama", "model": "qwen3.6-35b-a3b", "retries": 1}
]
}, find all TODO comments in python files
# → rg -n TODO --type py # Find TODO comments in Python files
, list files larger than 1G
# → fd --size +1G # Find files larger than 1GB
, what is my ip
# → curl -s ifconfig.me # Get public IP addressOnly leading arguments are parsed as flags — everything after the first intent word is intent text, verbatim, and -- ends flag parsing explicitly. So intents containing - words just work:
, grep -v pattern # intent: "grep -v pattern"
, use curl -V # intent: "use curl -V"echo "find large files" | , # generates, then asks for a "y" line on stdin
echo "find large files" | , ! # skips confirmation (scripting / agents)With piped stdin, , never auto-executes: it reads one line from stdin and runs the command only if that line is y.
,
> find large files
fd --size +100M # Find files larger than 100MB
> sort by size descending
fd --size +100M -x ls -lh {} + | sort -k5 -h -r
> x # execute| Key | Action |
|---|---|
Tab |
Autocomplete filename |
↑/↓ |
Select candidate |
Enter |
Confirm / Execute |
Esc |
Cancel |
e |
Edit command |
r |
Refine via LLM |
x |
Execute (interactive mode) |
c |
Copy to clipboard |
q |
Quit |
By default , runs the confirmed command in a child process, so a cd or export is lost when it exits. With a small wrapper function, commands run in your current shell instead (navi/fzf-style): the binary appends each confirmed command to the file named by COMMA_EVAL_FILE (one per line), and the wrapper evaluates that file in the current shell after , exits.
Add to ~/.bashrc or ~/.zshrc:
,() {
local f; f=$(mktemp)
COMMA_EVAL_FILE="$f" command , "$@"
local ec=$?
[ -s "$f" ] && eval "$(cat "$f")"
rm -f "$f"
return $ec
}Add to your $PROFILE (the function is named comma because , is reserved in PowerShell):
function comma {
$f = New-TemporaryFile
try {
$env:COMMA_EVAL_FILE = $f.FullName
$env:COMMA_EVAL_SHELL = 'powershell'
comma.exe @args
}
finally {
Remove-Item Env:COMMA_EVAL_FILE -ErrorAction SilentlyContinue
Remove-Item Env:COMMA_EVAL_SHELL -ErrorAction SilentlyContinue
}
if (Test-Path $f) {
$c = Get-Content $f -Raw; Remove-Item $f
if ($c) { Invoke-Expression $c.Trim() }
}
}COMMA_EVAL_SHELL tells the model which shell dialect to generate for: since the wrapper evals in PowerShell, this makes it emit PowerShell syntax ($env:USERPROFILE, ~) instead of cmd syntax (%USERPROFILE%). (bash/zsh need no such hint — $SHELL already drives the dialect.)
Save as comma.cmd somewhere in PATH (before or alongside comma.exe):
@echo off
set "F=%TEMP%\comma-eval-%RANDOM%-%RANDOM%.cmd"
set "COMMA_EVAL_FILE=%F%"
comma.exe %*
set "COMMA_EVAL_FILE="
if exist "%F%" ( call "%F%" & del "%F%" )%* passes the arguments through; call runs the eval file in the current cmd session, so a cd actually changes your directory. No COMMA_EVAL_SHELL is needed here — cmd.exe is already the reported dialect on Windows when SHELL is unset.
Notes:
, go to the temp directory→cd /tmpnow actually changes your directory.- In interactive mode each executed command is appended to the file; the wrapper evals them in order when the session exits.
- Without the wrapper nothing changes — commands run in a child process as before (a bare
cdthere prints a note pointing here).
COMMA_* environment variables
↓
~/.config/comma/config.json (XDG; $XDG_CONFIG_HOME honored)
↓
,.config.json next to the binary (portable installs)
↓
~/.local/bin/,.config.json (legacy, still read if present)
↓
~/.claude/settings.json
↓
Built-in defaults
The same chain applies to the cache (~/.cache/comma/cache.json, $XDG_CACHE_HOME honored) and additional_prompt.md — so a directory containing the binary plus ,.config.json, ,.additional_prompt.md, and ,.cache.json is fully portable. On Windows they default to %APPDATA%\comma\ (binary-adjacent and legacy files are still read).
export COMMA_BASE_URL="https://api.cerebras.ai/v1"
export COMMA_API_KEY="csk-xxx"
export COMMA_MODEL="llama-3.3-70b"
export COMMA_API_STYLE="openai"{
"base_url": "https://api.cerebras.ai/v1",
"auth_token": "csk-xxx",
"model": "llama-3.3-70b"
}{
"prefer": {
"editor": ["nvim", "vim"],
"list": ["eza", "ls"],
"grep": ["rg", "grep"],
"find": ["fd", "find"]
}
}Repeated intents are answered from ~/.cache/comma/cache.json (default cap: 1000 entries). The cache is checked for all configured models in fallback order before any network request, so a cached fallback answer avoids a slow or unreachable primary call. Set "cache_size": 0 in the config to disable the cache entirely.
For Anthropic models, "reasoning": <tokens> enables extended thinking with that budget. max_tokens is raised automatically, so budgets ≥ 1024 work.
Backend for the #SEARCH: protocol (see the Web search feature above):
{
"search": {
"provider": "off",
"api_key": "",
"base_url": "",
"max_results": 5
}
}Providers: off (default — the model is not told it can search), brave (requires api_key), tavily (requires api_key), searxng (requires base_url of your instance), duckduckgo / mojeek (keyless page scraping — convenient, but bot detection may rate-limit your IP; duckduckgo automatically falls back to Mojeek). max_results defaults to 5 (max 10).
The default system prompt is compiled into the binary, so upgrades always bring the latest version. View it with:
, --default-prompt
To customize:
additional_prompt.md(resolved like the config:~/.config/comma/, next to the binary as,.additional_prompt.md, or legacy~/.local/bin/) — appended to the default prompt. This is the recommended way to add your own rules (placeholders{{SYSTEM_CONTEXT}}/{{PREFERENCES}}work there too)."full_prompt"in config.json — a total override for experts. The value is either a path to a prompt file (~/and paths relative to the config dir work) or the inline template itself. When set, neither the default noradditional_prompt.mdis used.
A legacy ~/.config/comma/prompt.md whose content differs from the built-in default is still honored as a full override; a copy identical to the default is ignored (it was only ever the installed template).
No personal data is sent to the API. The model uses placeholders:
User: "list my home directory"
↓
LLM sees: "User: {{USER}}, Home: {{HOME}}" (no real values)
LLM outputs: "ls -la {{HOME}}"
↓
Local replace: "ls -la /home/miuzel" (local only)
On each call, comma-cli injects:
- Distro, kernel, architecture
- Shell, current directory
- User-installed packages
This ensures correct commands for your platform (apt vs pacman, brew vs port).
curl -sSL https://github.com/miuzel/comma-cli/releases/latest/download/install.sh | bashThe installer verifies the archive's SHA-256 checksum against the release's sha256sums.txt when available.
brew install miuzel/tap/comma-cliThe binary is installed as , (with comma as an alias), plus a comma-setup helper: run it once to create ~/.config/comma/config.json interactively (base URL, API key, model name — skipping the prompts still leaves a default config you can edit). Update with brew upgrade comma-cli.
# Install to D:\tools\bin (change path as needed)
$dir = "$env:USERPROFILE\.local\bin"; New-Item -ItemType Directory -Force -Path $dir | Out-Null; Invoke-WebRequest -Uri "https://github.com/miuzel/comma-cli/releases/latest/download/comma-windows-x86_64.zip" -OutFile "$dir\comma.zip"; Expand-Archive -Path "$dir\comma.zip" -DestinationPath $dir -Force; Remove-Item "$dir\comma.zip"; Write-Host "Installed to $dir\comma.exe — add $dir to PATH, then use: comma <intent>"Note: PowerShell reserves
,as a keyword. Rename the exe if you want a shorter name (e.g.,c.exe).Note: On Unix, commands are generated for and executed by
$SHELL -c(falling back to/bin/shwhen$SHELLis unset). On Windows, commands are generated for and executed bycmd /CwhenSHELLis unset; ifSHELLis set (e.g., you use Git Bash/MSYS), generation and execution both use that POSIX shell instead.
Grab the archive for your platform from releases:
| Platform | Archive |
|---|---|
| Linux x86_64 | comma-linux-x86_64.tar.gz |
| Linux aarch64 | comma-linux-aarch64.tar.gz |
| macOS x86_64 | comma-macos-x86_64.tar.gz |
| macOS aarch64 (Apple Silicon) | comma-macos-aarch64.tar.gz |
| Windows x86_64 | comma-windows-x86_64.zip |
# Example: Linux x86_64
tar xzf comma-linux-x86_64.tar.gz
mv comma ~/.local/bin/,, --updategit clone https://github.com/miuzel/comma-cli.git
cd comma-cli
./build.shRun the interactive wizard (it also starts automatically on the first run when no provider is configured):
, --setupIt manages LLM providers (add/edit/delete/reorder — the order is the fallback order) and the web-search backend, then writes ~/.config/comma/config.json (the previous file is backed up with a timestamp). Prefer editing by hand? A minimal config:
{
"base_url": "https://api.cerebras.ai/v1",
"auth_token": "your-api-key-here",
"model": "gemma-4-31b"
}Or use environment variables:
export COMMA_BASE_URL="https://api.cerebras.ai/v1"
export COMMA_API_KEY="your-api-key-here"
export COMMA_MODEL="gemma-4-31b"Free options to get started:
- Cerebras — Free tier, ultra-fast, no credit card needed
- Groq — Free tier, low latency
- Ollama — Local, no API key, requires 8GB+ RAM
./uninstall.sh- Sysadmins: Quick one-liners without man page archaeology
- Developers: Convert intent to
ffmpeg,find,tarcommands - DevOps: Check ports, processes, disk usage
- Anyone who uses the terminal and hates memorizing flags
Small is big. A comma is the smallest punctuation — yet it changes everything.



