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.
Every generated command opens the action menu straight away — there is no hint
line to read and no need to type x first (x only re-opens the menu).

A command that exits non-zero is refined automatically, at most
auto_refine_rounds times per intent (default 3; the demo sandbox sets 2).
The round is shown, and an exhausted budget stops the chain and hands the
prompt back instead of starting another round.

Ctrl+C interrupts the running step and then asks Exit REPL? [y/N]; N
drops the interrupted step and returns to > . The welcome block also shows
the REPL input history, which is ON by default.

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
Execute? [Enter] exec / [e]dit / [r]efine / [c]opy / [Esc] cancel
▸ Running: fd --size +100M
> sort by size descending
fd --size +100M -x ls -lh {} + | sort -k5 -h -r
Execute? [Enter] exec / [e]dit / [r]efine / [c]opy / [Esc] cancelEvery generated command immediately opens the action menu — Execute? [Enter] exec / [e]dit / [r]efine / [c]opy / [Esc] cancel — so a fresh command never leaves you wondering how to run it: press Enter to run it, e to edit it first, r to have the model refine it, c to copy it, or Esc to cancel and go back to the > prompt. There is no separate hint line to read and no need to type x first: x / exec just re-opens the menu for the current command (handy after an Esc). This is interactive-mode only, so one-shot and piped-stdin runs are unchanged.
If the command you executed exits non-zero, , automatically refines it: the failed command, its exit code and a truncated summary of its output go back to the model, and the corrected command is printed with the action menu again (nothing is auto-executed). You can also refine at the main prompt, without executing anything first:
> /refine use ripgrep instead of grep # alias: /rSee Auto-refine after a failure for what is sent and how to turn it off.
Ctrl+C always gets you out, and never leaves the process to the terminal's default handler (which would skip the single exit path and lose the session history):
- At the
>prompt — one press exits, exactly likeq/quit/exit; the REPL history is written on that path too.Ctrl+Dis unchanged and does not exit. - While something is running (a model request, a refine, a command) — the running step is interrupted first, then
Exit REPL? [y/N]asks:yleaves (history saved),N— orEnter, or anotherCtrl+C— goes back to>. The interrupted step's result is dropped, never executed. - In a menu or a sub-prompt —
Ctrl+Cmeans the same asEsc: the action menu, the candidate selector, and theedit>/refine>prompts cancel back to>, so a menu can never trap you; pressCtrl+Conce more at the prompt to leave. - While a command runs —
Ctrl+Cis still forwarded to it: as0x03through the pty for a captured run (so the command is interrupted first, as any terminal would), and asSIGINTfrom the kernel for a run with inherited stdio (auto_refine: false). In both cases,records the exit intent and asks once the command is done.
A model request that is still waiting for a response is aborted immediately (Interrupted system call), so the question comes up right away. A request that has already started receiving its body and then stalls cannot be aborted: the spinner switches to Ctrl+C — will ask to exit when this step finishes and the question appears when the request's own timeout expires (60s per configured model).
Non-TTY runs (one-shot, piped stdin, COMMA_EVAL_FILE eval mode) are unchanged: nothing is installed there, so Ctrl+C keeps the terminal's default behavior. On Windows , installs the console-control-handler equivalent, since the console would otherwise end the process in the middle of a step.
| Key | Action |
|---|---|
Tab |
Autocomplete filename |
↑/↓ |
Select candidate |
Enter |
Confirm / Execute |
Esc |
Cancel |
e |
Edit command |
r |
Refine via LLM |
x |
Re-open the action menu for the current command |
/refine TEXT |
Refine the current command directly (alias /r) |
c |
Copy to clipboard |
q |
Quit (history is saved) |
Ctrl+C |
At the prompt: quit (history is saved). In a menu: cancel, like Esc. While busy: interrupt, then ask whether to 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.
In eval mode the command runs in the wrapper, not in ,, so there is no exit code and no output to inspect: automatic refine never triggers there (and output is not captured either).
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). The REPL history is the exception: it only ever lives in $XDG_STATE_HOME/comma/history (%APPDATA%\comma\history on Windows), with no portable or legacy fallback. It is on by default; setting "history": false stops it from being read or written at all.
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.
By default , remembers what you type at the REPL prompt, so ↑ recalls earlier intents in the next session. Every time you enter the REPL the welcome line says so and tells you how to stop it:
{ "history": false }The history is stored in $XDG_STATE_HOME/comma/history (default ~/.local/state/comma/history; %APPDATA%\comma\history on Windows). It is written once when you leave the REPL with q/quit/exit — or with Ctrl+C (see Leaving the REPL) — is readable by your user only (0600, because it contains your raw intents), keeps the newest 1000 entries, and is never sent to the API. Only what you type at the REPL prompt is saved; text entered for the in-session e (edit) and r (refine) prompts is not persisted.
To turn it off, set "history": false in the config — then nothing is read or written and no history file is created — or use the toggle in , --setup. While the feature is off, the welcome line does not mention it.
In the interactive REPL, a command that exits non-zero (including one killed by a signal) automatically starts a refine turn: the failed command, its exit code and a summary of its output are sent to the model, and the corrected command is printed with the action menu — you still choose to run it. Nothing is executed automatically, and each executed command triggers this at most once. Non-TTY runs (one-shot, piped stdin) and COMMA_EVAL_FILE eval mode never auto-refine.
Disable it in the config:
{
"auto_refine": false
}or toggle it in , --setup. With auto_refine: false the command runs with inherited stdio exactly as before — no pseudo-terminal is allocated and nothing is captured.
The automatic refine is itself capped, so a command that keeps failing cannot chain forever: one user intent may spend at most auto_refine_rounds automatic refine rounds (default 3), and the notice shows where you are (e.g. round 2/3). When the budget is used up and the command still fails, , stops refining, prints an error saying how many rounds were tried and hands control back to you — it never starts round N+1 on its own. Typing a new intent resets the counter; a manual refine (r / /refine TEXT) is your own action and is not limited by it.
{
"auto_refine": true,
"auto_refine_rounds": 3
}auto_refine_roundstakes an integer, is clamped to 1–10, falls back to 3 when the key is absent or unusable, and0turns auto-refine off entirely (equivalent toauto_refine: false— the command then runs with inherited stdio too, so no pty is allocated and nothing is captured).auto_refine: falsealways wins over the round count., --setuptoggles auto-refine on/off but leavesauto_refine_roundsuntouched (edit the key inconfig.jsonto change the number).
While auto-refine is enabled, , needs the command's output for the summary, so the child runs on a pseudo-terminal that , relays live:
- output streams to your terminal as it is produced — nothing waits for the command to exit;
- the child keeps full TTY semantics: colors and progress bars render as usual and full-screen programs (
vim,less, ...) work — the terminal size is forwarded at startup and re-synced on a window resize; - your typing, and
Ctrl-C(which interrupts the command), are forwarded to the command; that sameCtrl-Calso records the exit intent, so,asks whether to leave once the command is done (see Leaving the REPL); - your terminal is put in raw mode for the duration and is always restored afterwards — normal exit,
Ctrl-Cor a fatal signal; - a bounded copy of the output (first + last 32 KB) is kept for the summary, so an endless command (
yes, a chatty daemon) cannot grow it without bound.
Windows has no pseudo-terminal support yet: output still streams live and stdin is inherited, but the child sees pipes rather than a TTY, so colors/progress bars may be missing and full-screen programs (vim, less) do not work there — use auto_refine: false for those. The same piped mode is used on a Unix host where no pty can be allocated, and , says so instead of degrading silently.
What is sent is bounded and sanitized: ANSI escapes and control characters are stripped, the summary is truncated to 2000 characters (head + tail, because errors usually land at the end), and an empty output sends only the command and the exit code. Real $HOME, username and hostname are replaced by {{HOME}}/{{USER}}/{{HOSTNAME}} before anything is sent (see Privacy) — masking happens before truncation, so a half-cut path can never leak.
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)
The same rule covers automatic refine: command output is captured on your machine and masked back to {{HOME}}/{{USER}}/{{HOSTNAME}} (before truncation) before it is sent, so a build log or ls listing full of real paths and hostnames never carries them to the API. The --test self-check asserts that an auto-refine request body contains none of the real values.
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).
The prompt also states the limits of that shell, because commands run in a non-interactive child shell ($SHELL -c) rather than in your interactive session: aliases, functions and unexported variables from ~/.zshrc/~/.bashrc do not apply, and there is no shell history — history, fc -l, !!, !n and the other history builtins/expansions either fail (zsh:fc:N: no such event, exit 1) or return nothing (bash starts with an empty list). A history intent is therefore answered by reading the history file instead:
# intent: "history"
tail -n 20 {{HOME}}/.zsh_history # zsh
tail -n 20 {{HOME}}/.bash_history # bash$HISTFILE and $HISTCMD are normally not exported, so they are empty in that child shell and are never relied on.
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.



