A context-aware command launcher for the terminal. commando unifies four
sources of commands behind a single fuzzy TUI, then places the assembled command
on your shell prompt for review before you run it.
- Cheat sheets — parameterized, reusable command templates.
- Project-detected commands —
maketargets,npm/pnpm/yarnscripts,cargo, Gradle tasks,justrecipes, docker-compose services, and git actions, discovered from whatever folder you're in. - Bookmarks — commands you explicitly saved, with searchable tags and notes.
- Shell history — read live, searchable, and offered back to you.
On top of that it adds the things existing tools don't combine:
- Argument memory with frecency ranking — it remembers what you last entered per variable and floats hot values to the top.
- First-class optional and multi-select parameters — no dangling flags, no awkward escaping.
- A live command preview that assembles as you fill in variables.
See PROPOSAL.md for the full design rationale.
With Homebrew on macOS:
brew install --cask Gokuldroid/tap/commandoOr use the release installer on macOS, Linux, or Windows from Git Bash:
curl -fsSL https://raw.githubusercontent.com/Gokuldroid/commando/main/install.sh | shThe installer verifies the release checksum and writes to ~/.local/bin by
default. To build from source instead, install Go 1.25+ and run:
make installThen add the integration for your shell:
eval "$(commando init zsh)" # ~/.zshrc
eval "$(commando init bash)" # ~/.bashrc
commando init fish | source # ~/.config/fish/config.fishReload your shell. On first run, commando seeds a set of sample cheat sheets
into ~/.config/commando/cheats/.
Press Ctrl-G (the default binding) at any prompt to summon the launcher, or
run commando directly.
- Search — type to fuzzy-filter across cheats, project commands, bookmarks,
and history. Each whitespace-separated term is matched independently against
an entry's title, description, command, and tags, so
deploy clusterfinds a command taggeddeploywhose description mentionscluster. Narrow to one source with a:prefix (:ccheats,:pproject commands,:hhistory,:bbookmarks) or pressTabto cycle scopes. Title and prefix matches rank first, with frecency (how often/recently you run a command) breaking close matches. - Fill in — for a parameterized command, a form walks each variable:
- type a value, or pick from candidates with the arrow keys
- optional fields can be skipped (
Ctrl-S) — the whole[...]fragment disappears from the command - multi-select fields let you choose several values (
Spacetoggles) - a command preview updates as the values are filled
- Run — the finished command is placed on your prompt. Review, edit if you like, and press Enter.
commando Open the interactive search TUI
commando search --print Open the TUI; write the chosen command to stdout
commando search --exec Open the TUI; run the chosen command directly
commando init <shell> Print integration for zsh, bash, or fish
commando add Interactively author a new cheat entry
commando promote [flags] <template> Promote a command into a managed cheat
commando history [query] Search your shell history (read live from the file)
commando doctor Diagnose setup and provider issues
commando state clear memory|usage|all Reset remembered state
commando bookmark add <cmd> Save a bookmark (--tags "...", --note "...")
commando bookmark list List bookmarks
commando bookmark remove <cmd> Remove a bookmark
commando version Print the version
In the TUI, press Ctrl-B on any entry to bookmark it. Literal commands are saved immediately; parameterized cheats open their form and save the fully assembled command. Highlight a bookmark and press Ctrl-D to delete it.
A bookmark is a saved command with optional tags and a note — all fuzzy-searchable, so you can find a hard-to-remember command by describing it:
commando bookmark add --tags "k8s deploy" --note "prod rollout" \
"kubectl rollout restart deployment/web -n prod"Bookmarks are stored in ~/.config/commando/bookmarks.toml (plain, editable)
and sort to the top of the list.
Cheat files are TOML, stored in ~/.config/commando/cheats/*.toml. Example:
[[cmd]]
title = "Search git log"
desc = "Search git log by author for a file."
tmpl = "git log [--author=<author>] [-n <count>] -- <file>"
tags = ["git", "log"]
[[cmd.var]]
name = "author"
src = "git log --all --format='%an' | sort -u" # candidate values
[[cmd.var]]
name = "count"
default = "20"
[[cmd.var]]
name = "file"
multi = true # allow selecting several
each = '"{}"' # wrap each value: "a.go" "b.go"
remember = "project" # scope memory per git project (default is global)| Token | Meaning |
|---|---|
<var> |
A required placeholder, prompted in the form. |
[ ... <var> ... ] |
An optional segment — if you skip the variable, the entire bracketed fragment is removed. |
\< \> \[ \] \\ |
Escaped literals. |
| Field | Meaning |
|---|---|
src |
Shell command whose stdout lines become candidate values. |
values |
Inline candidate list (e.g. ["dev","staging","prod"]) — no shell needed. Offered before src values; extraction fields apply to these too. |
default |
Pre-filled initial value. |
multi |
Allow selecting multiple values. |
sep |
Separator joining multiple values (default: space). |
each |
Per-item format, {} = the value (e.g. '"{}"'). |
remember |
Memory scope: unset (default) = global; or project, dir, or none to opt out. See Remembering values. |
header_lines |
Drop this many leading lines from src output (e.g. a header row). Not applied to values. |
delimiter |
Regex used to split each line into columns for columns (default: whitespace runs). |
columns |
Names for the columns of each line after splitting on delimiter, so label/value can reference them as <name>. Mutually exclusive with match. |
match |
Regex applied to each line; its capture groups (named (?P<x>…) or positional) feed label/value. A line that doesn't match is dropped. Mutually exclusive with columns. |
label |
Template for the displayed & searched text (e.g. "<comm> (<pid>)"). Defaults to the raw line. See Display vs. inserted value. |
value |
Template for the inserted & remembered value (e.g. "<pid>"). Defaults to the raw line. |
strict |
Restrict the field to its candidate values; reject free-typed values. |
pattern |
Regex the resolved value must match in full (e.g. '^[0-9]+$'); a non-matching value is rejected. Validates typed or selected values. |
preview |
Command run for the highlighted candidate ({} = the candidate's value); output shown in a preview pane. |
cache |
Memoize a slow src command's output for this Go duration (e.g. "5m") instead of re-running it on every re-fetch. |
Inside label, value, match, and columns, a placeholder is a column
reference, not another variable: <0> is the whole line/match, <1>, <2>, …
are columns/groups by position, and a name from columns or a (?P<name>…)
group in match is addressable as <name>. (This is distinct from a <var> in
a src command, which references another variable — see Shared variable
groups.)
A candidate has two projections: what you see and fuzzy-search (its label)
and what actually gets inserted into the command (its value). By default
they're identical — the whole src line. label and value let them differ,
which solves the classic "the list is readable but I only want one field out of
it" problem.
The motivating example — kill a process you find by name, but insert only its PID:
[[cmd]]
title = "Kill process"
desc = "Pick a process by name and send SIGKILL to its PID."
tmpl = "kill -9 <pid>"
[[cmd.var]]
name = "pid"
src = "ps -eo pid,comm"
header_lines = 1 # drop the "PID COMMAND" header
match = '^\s*(?P<pid>\d+)\s+(?P<comm>.+)$'
label = "<comm> (<pid>)" # shown & searched: "chrome (1234)"
value = "<pid>" # inserted: "1234"
pattern = '^[0-9]+$' # value must be all digits
remember = "none" # PIDs are ephemeralYou fuzzy-search the list by process name, and commando places kill -9 1234 on
your prompt. The same pattern with columns (for cleanly-aligned output) instead
of match:
[[cmd.var]]
name = "pid"
src = "ps -eo pid,comm"
header_lines = 1
columns = ["pid", "comm"]
label = "<comm> (<pid>)"
value = "<pid>"A note on memory: what's remembered is the value (e.g. 1234), not the
label — a recalled value with no matching line in the current list shows as its
bare value. For ephemeral data (PIDs, container ids) set remember = "none"; for
stable data (branches, accounts) the value is already readable, so recall reads
naturally.
commando remembers the values you enter for a variable and offers them the next time as ranked candidates (most-recently-used first) above the input. Memory is on by default — you don't need to configure anything.
The remember field controls the scope of that memory:
remember |
Behavior |
|---|---|
| (unset) | Default. Remember globally — the value is offered everywhere. |
global |
Same as unset: remember across every directory. |
project |
Remember per git project (keyed by the repository root). |
dir |
Remember per working directory. |
none |
Opt out — never store values for this variable (use for secrets). |
How remembered values behave in the form:
- They appear as candidates in the list above the input, ordered most-recent first — never typed into the input automatically.
- For a list field (one with a
srccommand) pressing Enter on an empty input picks the top candidate. For a plain field (nosrc) Enter never auto-fills a remembered value; select one deliberately with ↑ then Enter. - An author-defined
defaultstill pre-fills the input regardless of memory.
Values are stored in ~/.local/share/commando/memory.toml (plain, editable).
Set remember = "none" on any variable holding sensitive input so its values
are never written there.
Define a variable group once and reuse it across commands with use, instead of
repeating specs. A command-local [[cmd.var]] overrides a shared var of the
same name:
[[vars.aws]]
name = "account"
src = "list-accounts"
[[vars.aws]]
name = "region"
default = "us-east-1"
[[cmd]]
title = "SSH to host"
desc = "SSH to a host with shared account and region variables."
tmpl = "ssh --account <account> --region <region>"
use = ["aws"] # pulls in account + regionA variable's src may reference other variables with <name>. commando
resolves referenced variables first (adding them as fields if they don't appear
in the template) and substitutes their chosen values before running the
command, so one field's candidates can depend on another:
[[cmd.var]]
name = "update_id"
src = "remo s3s_updates <stream> -r 5 | jq -r '.[].id'" # depends on <stream>
[[cmd.var]]
name = "stream"
src = 'cat "$COMMANDO_DATA_DIR/streams.txt"'Here <stream> is resolved first (even though it's only in update_id's src,
not the command template), and its value is substituted before the update_id
candidate list is computed. Changing stream refreshes update_id's
candidates.
Add entries interactively with commando add, which discovers the variables in
your template and prompts for their options.
A cheat variable's src command runs with COMMANDO_DATA_DIR set to
~/.local/share/commando/data/. Drop candidate lists there and reference them,
e.g.:
[[cmd.var]]
name = "service"
src = 'cat "$COMMANDO_DATA_DIR/service-names.txt"'The examples/ directory in this repo holds sample cheat files demonstrating
the format, including optional [ ] segments, multi-select, shared variable
groups, and cross-variable src lookups.
Optional ~/.config/commando/config.toml — see
config.example.toml. You can change the key binding,
tune the history redaction denylist, and define your own project providers.
providers (bookmarks · cheats · project · history)
└─ merge + dedup + frecency rank
└─ fuzzy TUI ◄──► plain-file state (memory + usage)
└─ fill-in form (optional/multi/memory + command preview)
└─ assembled command → shell prompt
Everything is a plain, human-readable file — there is no database. You can
cat, grep, and hand-edit every piece of commando's data:
| File | Contents |
|---|---|
~/.config/commando/config.toml |
key binding, redaction denylist, providers |
~/.config/commando/cheats/*.toml |
cheat sheets |
~/.config/commando/bookmarks.toml |
saved commands + tags/notes |
~/.local/share/commando/memory.toml |
remembered variable values (frecency) |
~/.local/share/commando/usage.toml |
per-entry launch counts (ranking) |
your $HISTFILE (e.g. ~/.zsh_history) |
shell history — read live, never copied |
- History is read live from your shell's history file (
$HISTFILE, then the active shell's zsh, Bash, or Fish history path) each time commando runs. No capture hook is installed; the shell already maintains that file. Secret-looking commands are hidden via the configurable denylist. - Argument memory is on by default: values are stored per
(command, variable, scope)and offered as most-recently-used-first candidates. Setremember = "none"on a variable to opt out. See Remembering values. - The binary is pure Go with no CGO, so it builds to a single static binary.
make test # run the full test suite (incl. binary integration test)
make test-short # skip the slow binary build test
make test-race # run tests with the race detector
make test-install # test the release installer against local fixtures
make vet
make fmt # gofmt -w .
make lint # golangci-lint (see .golangci.yml)
make check # full local gate: fmt-check + vet + lint + test-raceLinting uses golangci-lint with the config in
.golangci.yml. Install it with:
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latestZsh, Bash, and Fish summon widgets are supported. Community cheat repositories remain on the roadmap (see PROPOSAL.md §14).
TBD.
