hey, man, how do I find which process is listening on port 8080?
heyman turns a plain-English request into a shell command. Before answering, the model reads the man pages installed on your machine, so the flags it uses are the ones your system actually has.
Project page and latest eval results: https://alecf.github.io/heyman/
$ heyman lsof which process is listening on port 8080
lsof -i :8080
$ heyman stat print the size in bytes and modification time of notes.txt
stat -f "%z %Sm" notes.txtThat second answer is the BSD stat that ships with macOS. On Linux the same request gets GNU syntax (stat -c ...), because the model read a different man page.
If you don't know which program to use, put -- before the request and let heyman pick. -v shows the man pages and tool calls it used (on stderr):
$ heyman -v -- compress the logs directory into a tarball, excluding .tmp files
Model: anthropic/claude-haiku-4-5 (default)
Request: compress the logs directory into a tarball, excluding .tmp files
→ man tar search="exclude"
→ answer {"command":"tar --exclude='*.tmp' -czf logs.tar.gz logs", ...}
Man pages consulted: tar
Steps: 2, tokens in/out: 3051/297
tar --exclude='*.tmp' -czf logs.tar.gz logs--explain adds an explanation:
$ heyman --explain find files modified in the last 2 days larger than 10MB
find . -mtime -2 -size +10M
This command finds files in the current directory and subdirectories that:
- `-mtime -2`: were modified within the last 2 days (the minus sign means "less than")
- `-size +10M`: are larger than 10 megabytes (the plus sign means "more than")
The results will be printed to stdout by default.Only the command goes to stdout, so $(heyman ...) and pipes get just the command. Progress, verbose output and token counts go to stderr.
When you name a program (heyman tar ...), its man page from this machine goes into the prompt. The model can also call three tools while it works out the answer:
man: read any man page on this machine, in chunks or filtered by a search patternman_search: search man page names and descriptions, likeman -kwhich: check whether a program is installed
It finishes by calling answer with the command. With --, nothing is preloaded and the model uses man_search and man to find the right program.
The model reads man pages through these tools instead of relying on what it remembers, so the flags match your OS: BSD sed, date, find, stat and xargs on macOS, GNU versions on Linux. The prompt also tells the model which OS and shell you're using.
See docs/architecture.md for details.
With Go 1.27 or later:
go install github.com/alecf/heyman/cmd/heyman@latestFrom source:
git clone https://github.com/alecf/heyman
cd heyman
go build -o bin/heyman ./cmd/heymanRelease builds are set up with GoReleaser to publish binaries on GitHub Releases and a Homebrew cask in alecf/homebrew-tap. No release has been published yet. After the first one, you'll be able to install with:
brew install --cask alecf/tap/heymanheyman needs man on your PATH. It works on macOS and Linux.
heyman [flags] <command> <request>
heyman [flags] <section> <command> <request>
heyman [flags] -- <request>
heyman [flags] "<whole request in one quoted argument>"
heyman tar extract foo.tgz into /tmp: names the program, and its man page is preloaded.heyman 5 crontab run a job every weekday at 9am: reads the man page from a specific section.heyman -- find duplicate files: no program named, so the model chooses. A single quoted argument containing spaces does the same thing:heyman "find duplicate files".
Flags must come before the command name. Anything after the command name belongs to the request, so heyman ls -la what does this do asks about -la and doesn't pass it as a flag.
| Flag | Description |
|---|---|
-m, --model provider/model |
Model to use (see Models) |
-p, --profile name |
Use a named profile from the config file |
-s, --section n |
Man page section for the named command (heyman -s 3 printf …) |
--reasoning-effort level |
Thinking level for ollama/openai-compat models: none (fastest), low, medium, high |
-e, --explain |
Include an explanation |
-j, --json |
JSON output: command, explanation, and metadata (model, man pages, steps, tokens, cached, cost) |
-c, --copy |
Copy the command to the clipboard (pbcopy, wl-copy or xclip) |
-t, --tokens |
Show token usage and cost on stderr |
-v, --verbose |
Show the model, man pages consulted and tool calls on stderr |
-d, --debug |
Like -v, plus the full tool trace |
-q, --quiet |
No spinner |
--no-cache |
Skip the cache for this query |
--dry-run |
Print the prompt without calling a model |
--version |
Print the version |
Pick a model with --model provider/model. heyman checks these in order and uses the first one that's set:
--model--profile- the
HEYMAN_MODELenvironment variable - the
HEYMAN_PROFILEenvironment variable default_profilein the config file (set it withheyman profile set-default <name>)- the built-in default,
anthropic/claude-haiku-4-5
| Provider | Credentials | Example |
|---|---|---|
anthropic |
ANTHROPIC_API_KEY or CLAUDE_API_KEY |
anthropic/claude-haiku-4-5, anthropic/claude-sonnet-5-5 |
openai |
OPENAI_API_KEY |
openai/gpt-... |
openrouter |
OPENROUTER_API_KEY |
openrouter/<org>/<model> |
google |
GEMINI_API_KEY or GOOGLE_API_KEY |
google/gemini-... |
ollama |
none; OLLAMA_HOST is optional (default localhost:11434) |
ollama/qwen3:4b |
openai-compat |
HEYMAN_OPENAI_COMPAT_BASE_URL (or base_url in a profile), plus HEYMAN_OPENAI_COMPAT_API_KEY if the server needs a key |
openai-compat/<model> |
claude-code |
the claude CLI, installed and logged in |
claude-code/haiku |
You can leave out the provider when the model name makes it obvious. Names starting with claude- go to Anthropic, gpt-, chatgpt- and o1-style names go to OpenAI, and gemini- goes to Google. A name:tag name like qwen3:4b goes to Ollama. Anything else needs the provider/ prefix.
For open-weight models, you can run them locally with Ollama, use a hosted one through OpenRouter, or point openai-compat at any OpenAI-compatible server (llama.cpp, vLLM, LM Studio, ...).
Short version: on a 32 GB M2 Max, no local model we tested is both accurate and fast enough for interactive use. For that, use an API model (anthropic/claude-haiku-4-5: 94%, ~3 seconds). Local models work, with a real trade-off:
| model | pass (50 cases) | judge agrees | median | p95 | memory |
|---|---|---|---|---|---|
ollama/gemma4:12b |
82% | 84% | 41s | 119s | 10.2 GB |
ollama/qwen3.5:9b |
76% | 64% | 23s | 87s | 7.9 GB |
ollama/ministral-3:8b |
60% | 52% | 15s | 87s | 10.7 GB |
ollama/qwen3.5:4b |
50% | 46% | 10s | 55s | 5.9 GB |
ollama/gemma4:e4b |
46% | 39% | 14s | 41s | 7.3 GB |
Measured October 2026 with Ollama 0.33.1, thinking off, a 32K context window, models warmed up and the machine kept awake; memory is the runner's peak resident size. Per-case answers are on the project page. Also tried and dropped after a 16-case screen: granite4.1:8b, granite4.2:8b (too slow), granite4:7b-a1b-h, qwen3:4b-instruct, lfm2.5:8b, olmo-3:7b-instruct, phi4-mini, ministral-3:3b. Kimi has no local version.
If you want local anyway, qwen3.5:4b is the fastest; gemma4:12b is the most accurate if you can wait.
ollama pull qwen3.5:4b
# Cap the context window. Ollama may otherwise pick a large one (64K on a
# 32 GB Mac), which more than doubles memory use. 32K is plenty for heyman.
printf 'FROM qwen3.5:4b\nPARAMETER num_ctx 32768\n' > Modelfile
ollama create qwen3.5-4b-32k -f Modelfile
heyman -m ollama/qwen3.5-4b-32k --reasoning-effort none lsof which process is listening on port 8080Or save it as a profile (heyman profile setup does this for you, and sets reasoning_effort = "none" on Ollama profiles):
[profiles.local]
provider = "ollama"
model = "qwen3.5-4b-32k"
reasoning_effort = "none"Notes:
--reasoning-effort(orHEYMAN_REASONING_EFFORT, orreasoning_effortin a profile) is sent toollamaandopenai-compatmodels.noneturns thinking off; on Qwen 3.5 that halved latency and made the 4B model more accurate and more consistent. Models without thinking ignore it, and if a model returns an empty reply with thinking off (gemma4:e4b does), heyman retries once with the model's default.- Latency on local models is mostly reading the prompt and writing the answer. Shrinking the preloaded man page cost accuracy for little speed, so it's left at the default; the answer tool only asks for an explanation with
--explain, which roughly halved what local models generate. - The model needs tool-calling support to read man pages. If the server rejects tool definitions, heyman retries once without tools, with just the named command's man page in the prompt. With
--there's no page to include, so the model answers from memory. - Small models sometimes write their answer tool call as text instead of making the call. heyman recognizes the common forms and uses them. If it still can't find a command,
--debugshows the model's final reply. - heyman preloads up to about 48,000 characters (~12K tokens) of the named man page, and the
mantool returns at most 24,000 characters per call, sending longer pages in chunks. If the context window is smaller than that, Ollama silently drops the start of the prompt, so keepnum_ctxat 16K or more. - Small models make more mistakes, and sometimes confident, destructive ones. heyman only prints commands; read them before running them.
claude-code runs claude -p and uses your Claude Code login, so you don't need an API key. Claude Code can run only read-only commands (man, apropos, whatis, which, grep, head, col), and it runs in an empty temporary directory. Expect it to take 10 seconds or more per request.
The config file lives at:
- macOS:
~/Library/Application Support/heyman/config.toml - Linux:
~/.config/heyman/config.toml(or$XDG_CONFIG_HOME/heyman/config.toml)
default_profile = "haiku"
cache_days = 30
[profiles.haiku]
provider = "anthropic"
model = "claude-haiku-4-5"
[profiles.sonnet]
provider = "anthropic"
model = "claude-sonnet-5-5"
[profiles.local]
provider = "ollama"
model = "qwen3:4b"
[profiles.llamacpp]
provider = "openai-compat"
model = "my-model"
base_url = "http://localhost:8080/v1"base_url is the OpenAI-compatible endpoint, including the /v1 path. It's required for openai-compat. For ollama, it's optional and replaces the URL heyman builds from OLLAMA_HOST. base_url only applies when the model comes from a profile, not from --model or HEYMAN_MODEL. Put API keys in environment variables, not in the config file.
heyman profile setup # interactive wizard; writes a profile to the config file
heyman profile list # list profiles; * marks the default
heyman profile show [name] # show a profile (default profile if no name)
heyman profile set-default <name>
heyman profile delete <name> # -f skips the confirmation
heyman test-config # check each profile can be constructed (keys, base URLs)test-config doesn't call the model. It only checks that the provider can be set up with the credentials and URLs it has.
Answers are cached on disk for cache_days (default 30). The cache key covers the model, the request, the named command and section, whether --explain was used, and the OS. Changing any of these sends a new request.
heyman cache-stats # entries, size, hits, location
heyman clear-cache
heyman --no-cache ... # bypass for one queryThe default location is ~/Library/Caches/heyman on macOS and ~/.cache/heyman on Linux. Set HEYMAN_CACHE_DIR to use a different directory.
| Variable | Purpose |
|---|---|
HEYMAN_MODEL |
Model spec. Overrides HEYMAN_PROFILE and default_profile, but not --profile. |
HEYMAN_PROFILE |
Profile name. Overrides default_profile. |
HEYMAN_REASONING_EFFORT |
Default for --reasoning-effort |
HEYMAN_CONFIG |
Config file path |
HEYMAN_CACHE_DIR |
Cache directory |
HEYMAN_OPENAI_COMPAT_BASE_URL, HEYMAN_OPENAI_COMPAT_API_KEY |
Endpoint and key for openai-compat |
OLLAMA_HOST |
Ollama server address |
| Provider keys | See the table in Models |
--tokens prints token usage, totaled over every step of the tool loop, to stderr:
$ heyman -t -- compress the logs directory into a tarball, excluding .tmp files
tar --exclude='*.tmp' -czf logs.tar.gz logs
Token usage:
Input: 3,051 tokens
Output: 297 tokens
Total: 3,348 tokens
Cost: $0.0045 (estimated from 2026-09-25 list prices; check https://www.anthropic.com/pricing#api)heyman only has prices for Anthropic models. Their cost is estimated from list prices built into the binary, and it doesn't account for prompt-caching discounts. claude-code reports its own cost. Ollama shows as free. For every other provider, the cost shows as unknown. A typical request on the default model costs about half a cent.
The evals/ directory has an eval suite that runs a set of requests against one or more models and checks the commands they return. Run it with go run ./cmd/heyman-eval --models ... or make eval. See evals/README.md for the case format and options.
Latest results (macOS, 50 cases): Claude Sonnet 5.5 98%, Claude Haiku 4.5 94%; best local gemma4:12b 82% but ~40s per answer (see Local models). Per-case answers are on the project page; regenerate its data with make site-data RESULTS="<results dirs>".
go build -o bin/heyman ./cmd/heyman # or: make build
go test -race ./...
go vet ./...Layout:
| Path | Contents |
|---|---|
cmd/heyman |
main; version info is injected with ldflags |
internal/cli |
Cobra commands, argument parsing, model resolution, output |
internal/assist |
Prompts, the tool loop (man, man_search, which, answer) and the claude-code backend |
internal/llm |
provider/model parsing and provider construction on fantasy |
internal/manpage |
Runs man / man -k, validates names, strips formatting |
internal/cache |
On-disk answer cache |
internal/config |
TOML config and profiles |
internal/pricing |
Built-in Anthropic prices |
internal/output |
JSON output and clipboard |
docs/architecture.md covers the request flow, the tools and how to add a provider.
MIT