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).

Example Usage

Ask Mode Example

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

FunctionSuggested KeybindingDescription
show_prompt(w, p)Alt+Shift+XOpen prompt input, generate commands from text
show_last_results(w, p)Ctrl+Shift+XRecall previously generated commands
show_history(w, p)Alt+Shift+HBrowse and re-run previous prompts
show_ask_inline(w, p)Ctrl+Shift+AAsk AI a question, stream response in split pane

Command Generation

  1. Press Alt+Shift+X → type what you need (e.g. "find all PDF files modified today")
  2. AI generates several command options
  3. Pick one from the interactive selector (press / to filter)
  4. The command is inserted into your terminal

Streaming Ask

  1. Press Ctrl+Shift+A → type a question
  2. AI response streams in real-time in a split pane with markdown rendering via sd (streamdown)
  3. After streaming finishes, press any key to open the response in less for search, or q to close

Context-Aware Generation

  1. Select text in the terminal (error message, file listing, log snippet, etc.)
  2. Press Alt+Shift+X (or Ctrl+Shift+A) → the selected text is sent as context alongside your prompt
  3. 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:

  1. Create plugin/providers/<name>.lua that 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
  1. Register it in plugin/provider.lua:
local providers = {
    anthropic = require('providers.anthropic'),
    openai = require('providers.openai'),
    myprovider = require('providers.myprovider'),
}
  1. Add default api_url and model entries in plugin/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

ProblemSolution
"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 workingEnsure config.keys entries are present; check for conflicts
Keybindings not working on macOSPlain CTRL+<key> can conflict with terminal control characters or IME; use CTRL+SHIFT combos instead
Plugin not updatingClear the plugin cache (see Updating the Plugin)
No commands generatedRephrase the prompt to be more specific
No syntax highlightingInstall bat
Streaming ask shows raw textInstall streamdown: uv tool install streamdown
sd command not foundEnsure ~/.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

License

MIT