AI Commander for WezTerm
May 9, 2026 · View on GitHub
A WezTerm plugin that generates terminal commands and answers questions using AI providers (Anthropic Claude, OpenAI GPT, or custom).


Why
When you need a command you don't remember or hit an error you need to troubleshoot, the typical flow is: open ChatGPT/Claude in a browser, paste the context, wait for the answer, copy the command back. Or launch a CLI agent like Claude Code, which takes over your terminal session -- overkill when you just need a quick one-liner or want to ask "what does this error mean?"
AI Commander stays out of your way. Press a hotkey, type your question, get the answer -- all without leaving the terminal or interrupting your workflow:
- Quick command generation — describe what you need, pick from ready-to-run variants, it's pasted into your shell.
- Context-aware troubleshooting — select an error message or log snippet in the terminal, press the hotkey, and get a fix specific to what just happened.
- Streaming ask — ask any question and get a formatted, streamed response in a split pane.
Features
- Command Generation — describe what you want in plain English, get ready-to-run commands
- Streaming Ask — ask AI questions and get streamed, markdown-rendered responses in a split pane
- Multiple Options — generates several command variants to choose from (default: 5)
- Context-Aware — select text in the terminal (error messages, file paths, logs) and use it as context
- Results Recall — browse and reuse previously generated commands without re-querying the API
- Prompt History — access and re-run previous prompts from a persistent history file
- Syntax Highlighting — commands are highlighted via
bat(if installed) in the selection menu - Pluggable Providers — Anthropic and OpenAI built-in; add new providers by dropping a single file
Prerequisites
- WezTerm — recent version with Lua plugin support
- API Key from Anthropic or OpenAI
- bat (optional) — for syntax highlighting in the command selector
- streamdown (optional) — for streaming markdown rendering:
uv tool install streamdown - jq — required for streaming ask mode
Installation
Add to your .wezterm.lua:
local wezterm = require 'wezterm'
local config = wezterm.config_builder()
-- Load AI Commander plugin
local ai = wezterm.plugin.require("https://github.com/dimao/ai-commander.wezterm")
-- Apply configuration
ai.apply_to_config(config, {
provider = "anthropic", -- or "openai"
api_key = {
anthropic = "your-anthropic-api-key",
openai = "your-openai-api-key",
},
})
-- Keybindings
config.keys = {
{ key = 'X', mods = 'ALT|SHIFT', action = wezterm.action_callback(function(w, p) ai.show_prompt(w, p) end) },
{ key = 'X', mods = 'CTRL|SHIFT', action = wezterm.action_callback(function(w, p) ai.show_last_results(w, p) end) },
{ key = 'H', mods = 'ALT|SHIFT', action = wezterm.action_callback(function(w, p) ai.show_history(w, p) end) },
{ key = 'A', mods = 'CTRL|SHIFT', action = wezterm.action_callback(function(w, p) ai.show_ask_inline(w, p) end) },
}
return config
Usage
| Function | Suggested Keybinding | Description |
|---|---|---|
show_prompt(w, p) | Alt+Shift+X | Open prompt input, generate commands from text |
show_last_results(w, p) | Ctrl+Shift+X | Recall previously generated commands |
show_history(w, p) | Alt+Shift+H | Browse and re-run previous prompts |
show_ask_inline(w, p) | Ctrl+Shift+A | Ask AI a question, stream response in split pane |
Command Generation
- Press
Alt+Shift+X→ type what you need (e.g. "find all PDF files modified today") - AI generates several command options
- Pick one from the interactive selector (press
/to filter) - The command is inserted into your terminal
Streaming Ask
- Press
Ctrl+Shift+A→ type a question - AI response streams in real-time in a split pane with markdown rendering via
sd(streamdown) - After streaming finishes, press any key to open the response in
lessfor search, orqto close
Context-Aware Generation
- Select text in the terminal (error message, file listing, log snippet, etc.)
- Press
Alt+Shift+X(orCtrl+Shift+A) → the selected text is sent as context alongside your prompt - AI generates commands/answers that take the context into account
Recalling Results
Press Ctrl+Shift+X to browse commands from previous prompts grouped by query. Useful when you generated several options and want to try another one without re-querying.
Prompt History
Press Alt+Shift+H to pick a previous prompt and re-run it. History is persisted to a file across WezTerm restarts.
Configuration
All options with their defaults:
ai.apply_to_config(config, {
-- Provider: "anthropic" or "openai"
provider = "anthropic",
-- API keys (only the active provider's key is required)
api_key = {
anthropic = nil,
openai = nil,
},
-- API endpoints (override for proxies or compatible APIs)
api_url = {
anthropic = "https://api.anthropic.com/v1/messages",
openai = "https://api.openai.com/v1/chat/completions",
},
-- Models
model = {
anthropic = "claude-haiku-4-5",
openai = "gpt-4o",
},
-- Generation parameters
max_tokens = 4000, -- max tokens for command generation
chat_max_tokens = 16000, -- max tokens for streaming ask
temperature = 0.1,
command_count = 5, -- number of command variants to generate
-- Streaming ask renderer ('sd' for streamdown, 'cat' for plain text)
renderer = "sd",
chat_pane_size = 0.8, -- split pane size (0.0-1.0)
-- System prompts (see plugin/config.lua for full defaults)
system_prompt = "...", -- persona for command generation
chat_system_prompt = "...", -- persona for streaming ask
-- Results recall
max_results_history = 5, -- how many result sets to keep in memory
-- Prompt history (persisted to disk)
history_file = wezterm.home_dir .. "/.wezterm_ai_prompt_history.txt",
max_history = 100,
})
Adding a New Provider
Providers live in plugin/providers/ as individual Lua files. To add a new provider:
- Create
plugin/providers/<name>.luathat returns a factory function:
return function(api_key, model, max_tokens, temperature)
return {
-- HTTP headers for API requests
headers = {
{ "Content-Type", "application/json" },
{ "Authorization", "Bearer " .. api_key },
},
-- Build the JSON request body for blocking (non-streaming) calls
build_body = function(system_message, messages)
return {
model = model,
max_tokens = max_tokens,
temperature = temperature,
messages = messages,
}
end,
-- Extract the text content from a parsed JSON response
extract_response = function(response)
if response.choices and response.choices[1] then
return response.choices[1].message.content
end
return nil, "Error: No content in response"
end,
-- Bash pipeline to parse SSE stream into raw text (used for streaming ask)
stream_filter = table.concat({
"grep --line-buffered '^data: '",
"sed -u 's/^data: //'",
"jq --unbuffered -j '.choices[0].delta.content // empty'",
}, ' | '),
-- jq template for building the streaming request body
-- Available variables: $model, $max_tokens, $sys (system prompt), $msg (user message)
body_template = '\'{ model: $model, max_tokens: $max_tokens, stream: true,'
.. ' messages: [{ role: "system", content: $sys }, { role: "user", content: $msg }] }\'',
}
end
- Register it in
plugin/provider.lua:
local providers = {
anthropic = require('providers.anthropic'),
openai = require('providers.openai'),
myprovider = require('providers.myprovider'),
}
- Add default
api_urlandmodelentries inplugin/config.lua:
api_url = {
...
myprovider = 'https://api.example.com/v1/chat/completions',
},
model = {
...
myprovider = 'my-model-name',
},
The provider is then available via provider = "myprovider" in user config.
Updating the Plugin
WezTerm caches plugins locally. After the plugin is updated upstream, you need to pull the new version:
Option 1 — from the WezTerm Debug Overlay (Ctrl+Shift+L):
wezterm.plugin.update_all()
wezterm.reload_configuration()
Option 2 — delete the cached copy and restart WezTerm:
# macOS
rm -rf ~/Library/Application\ Support/wezterm/plugins/*ai-commander*
# Linux
rm -rf ~/.local/share/wezterm/plugins/*ai-commander*
WezTerm will re-fetch the plugin from the repository on next start.
Troubleshooting
| Problem | Solution |
|---|---|
| "API key not configured" | Set api_key.anthropic or api_key.openai matching your provider |
| "Unsupported provider" | Check that a matching file exists in plugin/providers/ |
| "Failed to connect to API" | Check internet, API key validity, firewall rules |
| Keybindings not working | Ensure config.keys entries are present; check for conflicts |
| Keybindings not working on macOS | Plain CTRL+<key> can conflict with terminal control characters or IME; use CTRL+SHIFT combos instead |
| Plugin not updating | Clear the plugin cache (see Updating the Plugin) |
| No commands generated | Rephrase the prompt to be more specific |
| No syntax highlighting | Install bat |
| Streaming ask shows raw text | Install streamdown: uv tool install streamdown |
sd command not found | Ensure ~/.local/bin is in your PATH, or set renderer to the full path |
Security
- API keys are stored in your WezTerm config and sent to the provider's API
- Prompts and selected context are sent to the AI provider for processing
- Generated commands are never executed automatically — you always choose
- Do not commit API keys to version control