AI-powered shell auto-completion for Zsh. Suggestions appear as you type, powered by any OpenAI-compatible API.
ashlet runs a lightweight daemon (ashletd) that gathers context from your shell — working directory, command history, git status, project manifests — and sends it to an inference API. Candidates are streamed back and displayed inline below your prompt.
Zsh Shell <--> ashlet.zsh <--> (Unix socket) <--> ashletd <--> API provider
- Quickstart
- Requirements
- Build from Source
- How It Works
- Privacy
- Keybindings
- Troubleshooting
- Configuration
- Architecture
- Development
- Why Name It
ashlet? - License
# 1) Install
brew install paranoid-af/tap/ashlet
# 2) Enable in Zsh
echo 'source $(brew --prefix)/share/ashlet/ashlet.zsh' >> ~/.zshrc
exec zsh
# 3) Start the daemon
brew services start ashlet
# 4) Configure an API key (recommended)
ashlet # creates ~/.config/ashlet/config.json and ~/.config/ashlet/prompt.md
# If you are running ./ashletd manually (not via brew services), you can also:
# export ASHLET_GENERATION_API_KEY="your-openrouter-key"Type a command, wait for a suggestion, then press Tab to accept.
Try it:
git st- Zsh 5.3+
- Go 1.25+ (to build from source)
- socat and jq (runtime)
# Build the daemon
make build
# Start it
./ashletd
# In your .zshrc
source /path/to/ashlet/shell/ashlet.zshSet an API key to enable completions:
export ASHLET_GENERATION_API_KEY="your-openrouter-key"Or run ashlet in your shell to launch the configuration TUI, which creates ~/.config/ashlet/config.json.
You type → Zsh hook fires → JSON request over Unix socket → ashletd gathers context →
API call → candidates parsed → displayed below prompt → Tab to accept
The daemon gathers rich context for each request:
- Shell history — recent commands + semantically relevant ones (via optional embeddings)
- Directory listing — files, detected package manager
- Git info — repo root, staged files, recent commits, manifests (package.json, Makefile, etc.)
- Cursor position — understands partial tokens
ashlet sends context to your configured API provider to generate completions.
- What gets sent:
- The line you are typing (and cursor position)
- Local context like directory info and (optionally) git metadata
- History context:
- With embeddings enabled and
generation.no_raw_history: true(default), ashlet sends only semantically relevant history commands (not a raw recent-history window). - When embeddings are disabled, it may fall back to sending a recent commands window.
- With embeddings enabled and
- History redaction: In shell history only, environment variable references (
$SECRET,${API_KEY}) and assignments (TOKEN=abc) are redacted before being sent. Safe variables like$HOME,$PATH, and$PWDare preserved. - IMPORTANT: Your current input is not redacted. If you are typing sensitive content, press
Escapeto enable PRIVATE MODE until the next prompt (Enter/Ctrl+C). You will see㊙ PRIVATE MODE ACTIVE - no input sent to AIbelow your prompt.
- Local-only IPC: The shell client and daemon communicate over a Unix domain socket. Nothing is sent over the network except API calls to your configured provider.
- Telemetry: When
telemetry.openrouteristrue(default), OpenRouter attribution headers are sent. Set it tofalseto disable.
| Key | Action |
|---|---|
Tab |
Accept the displayed suggestion |
Shift+Tab |
Fall through to default Zsh completion |
Shift+Left / Shift+Right |
Browse between candidates |
Escape |
Enable PRIVATE MODE (stop sending input) until next prompt |
- No suggestions appear
- Ensure the daemon is running:
brew services list(or start it withbrew services start ashlet) - If you built from source, run
./ashletdand watch logs for errors
- Ensure the daemon is running:
Tabdoesn’t accept the suggestion- Make sure
ashlet.zshis sourced in your~/.zshrc, then restart your shell - If
Tabis bound by another plugin, you can still access regular Zsh completion viaShift+Tab
- Make sure
- Missing dependencies (
jq/socat)- Install them:
brew install jq socat
- Install them:
- Socket / connection problems
- If you override the socket path, confirm
ASHLET_SOCKETpoints to the same location for both shell + daemon
- If you override the socket path, confirm
- API auth / request failures
- Set
ASHLET_GENERATION_API_KEY(or runashletto create/edit~/.config/ashlet/config.json)
- Set
- Accidentally sending something sensitive
- Use
Escapeto enable PRIVATE MODE before typing secrets, prventing sending requests (current input is not redacted in requests)
- Use
After enabling it in your shell, you can run ashlet to generate your configuration files.
This includes:
config.json: General configuration, such as API base URL, your API key, and the model name.prompt.md: Your custom prompt (Gotext/template). See the default prompt at: DEFAULT PROMPT.
Config lives at ~/.config/ashlet/config.json:
{
"version": 1,
"generation": {
"base_url": "https://openrouter.ai/api/v1",
"api_key": "",
"api_type": "responses",
"model": "mistralai/codestral-2508",
"max_tokens": 120,
"temperature": 0.3,
"no_raw_history": true
},
"embedding": {
"base_url": "https://openrouter.ai/api/v1",
"api_key": "",
"model": "openai/text-embedding-3-small",
"dimensions": 1536,
"ttl_minutes": 60,
"max_history_commands": 3000
},
"telemetry": {
"openrouter": true
}
}Embeddings are optional. When disabled, ashlet uses recency-only history (no semantic search).
"responses"(default) — OpenAI Responses API (POST /responses). Works with OpenRouter."chat_completions"— Chat Completions format (POST /chat/completions). Use this for Ollama or other local providers.
You can override some config.json values via environment variables.
| Config key | Priority |
|---|---|
generation.base_url |
$ASHLET_GENERATION_API_BASE_URL > generation.base_url in config.json |
generation.api_key |
$ASHLET_GENERATION_API_KEY > generation.api_key in config.json |
generation.model |
$ASHLET_GENERATION_MODEL > generation.model in config.json |
embedding.base_url |
$ASHLET_EMBEDDING_API_BASE_URL > embedding.base_url in config.json |
embedding.api_key |
$ASHLET_EMBEDDING_API_KEY > embedding.api_key in config.json |
embedding.model |
$ASHLET_EMBEDDING_MODEL > embedding.model in config.json |
Prompt lives at ~/.config/ashlet/prompt.md. It uses Go text/template syntax. See the default prompt at: DEFAULT PROMPT for template variables and format.
| Variable | Default | Description |
|---|---|---|
ASHLET_SOCKET |
auto | Override the Unix socket path |
ASHLET_MAX_CANDIDATES |
4 |
Max suggestions per request |
ASHLET_MIN_INPUT |
2 |
Minimum characters before requesting |
ASHLET_DELAY |
0.05 |
Debounce delay (seconds) |
shell/ Zsh client — captures input, communicates over Unix socket, renders suggestions
generate/ Completion engine — context gathering, API inference, candidate parsing
index/ History indexing and embedding via API
serve/ Daemon entry point and Unix socket server (ashletd)
repl/ Interactive test REPL with cursor tracking (dev-only, not distributed)
default/ Embedded default config and prompt template
ashlet.go Shared IPC types (Request, Response, Error)
config.go Configuration types and path resolution
Dependency graph: root (ashlet) <- index <- generate <- serve
make bootstrap # Download Go dependencies
make build # Build ashletd
make test # Go tests + shell tests (bats)
make lint # go vet + staticcheck + shellcheck
make format # gofmt + shfmt
make repl # Build and run the test REPL (dev-only)Open two terminals to test end-to-end:
Terminal 1 — daemon:
./_run.sh # build ashletd and start it
./_run.sh --verbose # same, with request/response loggingTerminal 2 — shell client:
./shell/_run.shThis launches an interactive zsh session with ashlet pre-loaded. Type commands to see completions.
For testing completions without the daemon or shell client:
make repl # interactive, TOML output on screen
make repl > log.toml # save structured output to fileThe REPL calls the completion engine directly with raw terminal cursor tracking. Each submission outputs structured TOML (context gathered, request, response). Use :cwd <path> to change directory, :quit to exit. Embeddings are cached to .cache/ in the project root for fast subsequent runs (REPL-only — the daemon does not use disk cache).
AI/Autocomplete Shell Scriptlet (and a reference to Ashley, to make it a bit more anthropomorphic).
See LICENSE for details.
