Zage is a small Rust CLI that predicts what you’re about to type next in your shell. It learns from your history and context (repo, folder, exit status, recent commands) using a lightweight online embedding model stored in your local database.
- ✅ Works offline and stays local by default (no cloud required)
- ⚡ Learns your workflows per-repo ("in this project I usually run X after Y")
- 🧠 Uses AI to generalize beyond exact string matches (flags reorder, similar commands, similar args)
- 🔮 Fewer keystrokes: suggestions match what you actually do in this directory/project.
- 🧩 Subtle improvements: not just “history search” — it learns patterns and generalizes.
- 🏠 Privacy-friendly: everything can be local (or you can opt into remote sync + encryption).
# 1) Import history and train the model (pick your shell)
zage import --shell zsh
# 2) Ask for suggestions
zage suggest --count 5
# 3) Ask for token completions for the *current word*
zage suggest --current-line "git " --count 5- Figure out your shell:
echo "$SHELL"- Edit the matching file:
- bash →
~/.bashrc - zsh →
~/.zshrc
Then follow the Bash or Zsh setup section below and restart your shell (or open a new terminal tab).
Build from source:
git clone https://github.com/casualjim/zage.git
cd zage
cargo build --release
# optional: install `zage` into ~/.cargo/bin
cargo install --path .Make sure ~/.cargo/bin is on your PATH (restart your terminal after installing).
Dev setup:
mise install
mise build:debugZage can run in two modes:
- Embedded (simplest): no daemon; commands talk directly to the DB.
- Server/service (best experience): a background daemon handles requests and updates the online model continuously.
Create a config file:
mkdir -p ~/.config/zage
cat > ~/.config/zage/config.toml <<'TOML'
backend = "embedded"
TOMLNow zage suggest and zage import work without installing a service.
Note: in embedded mode, the online model is trained during
zage import(and other explicit training steps), not continuously after every command.
Install the user service (systemd on Linux, launchd on macOS):
zage service installIf you prefer foreground (for debugging):
zage serverIn server/service mode, Zage can update the online model continuously as your shell records commands.
Once Zage is installed, add a small snippet to your shell config so Zage can:
- record commands as you run them (this is how it learns)
- show suggestions (zsh only, via zsh-autosuggestions)
Zsh is where Zage shines the most, because it can plug into zsh-autosuggestions.
zsh-autosuggestions by itself mostly suggests from:
- your history (often global)
- simple matching
Zage adds a smarter backend that uses AI-style embeddings + context, so suggestions are more likely to match what you do in this repo/dir, and it can generalize a bit (flags reordered, similar commands).
- Ensure you have zsh-autosuggestions installed (pick any method you like):
- In
~/.zshrc, load zsh-autosuggestions first, then load Zage:
# 1) zsh-autosuggestions
# Install it first: https://github.com/zsh-users/zsh-autosuggestions
# Then source it here (example path; yours may differ):
# source /path/to/zsh-autosuggestions/zsh-autosuggestions.zsh
# 2) Zage (adjust to where you cloned it)
ZAGE_DIR="$HOME/src/zage"
source "$ZAGE_DIR/src/shell_integration/zsh.zsh"- Restart your terminal (or
exec zsh).
Zsh options:
ZAGE_AUTOSUGGEST_DISABLE=1disables zage autosuggestionsZAGE_AUTOSUGGEST_ONLY=1makes zage the only autosuggest strategyZAGE_ZSH_DEBUG=/path/to/logwrites debug logs
Antidote users:
# Add Zage to your bundle list; Antidote will source zage.plugin.zsh
casualjim/zageMany Linux distros default to bash, so here’s the shortest reliable setup.
Zage’s bash integration relies on bash-preexec to get preexec/precmd hooks (bash doesn’t provide those by default).
After installing it, you should have a bash-preexec.sh you can source.
Example (git clone):
mkdir -p ~/.config
git clone https://github.com/rcaloras/bash-preexec ~/.config/bash-preexec
# then in ~/.bashrc:
# source "$HOME/.config/bash-preexec/bash-preexec.sh"# 1) bash-preexec (adjust path)
source /path/to/bash-preexec.sh
# 2) Zage (adjust to where you cloned it)
ZAGE_DIR="$HOME/src/zage"
source "$ZAGE_DIR/src/shell_integration/bash.sh"Bash options:
ZAGE_BASH_DEBUG=/path/to/logwrites debug logs
# Import your history (first-time setup / re-train)
zage import --shell zsh
# Ask for suggestions
zage suggest --count 5
# Inspect training progress
zage model status
# Run the always-on daemon (optional)
zage service install # or: zage serverZage isn’t calling a hosted LLM. Instead it maintains a small online embedding model (think: recommender system) that learns from:
- your recent commands (short context window)
- your current repo/folder
- exit status (failed commands change what you do next)
- and tokenized structure (command head, flags, normalized args)
This is why suggestions can improve in subtle ways over time: it’s learning patterns, not just replaying strings.
Zage can read a config file to set the default backend and database connection.
Load order (highest priority first):
- CLI flags
- Environment variables
--config-file /path/to/zage.tomlZAGE_CONFIG=/path/to/zage.toml~/.config/zage/config.toml
Example:
backend = "embedded" # or "server"
[db]
kind = "local" # or "remote" or "remote_replica"
path = "/path/to/zage.db" # local/replica only
# Remote libsql/sqld connection (optional for local)
# url = "libsql://your-host"
# auth_token = "your-token" # or set ZAGE_DB_AUTH_TOKEN
# Local at-rest encryption (requires libsql encryption feature)
# encryption_key = "super-secret"
# encryption_cipher = "aes256cbc"
# Remote encryption context (base64 key sent to server)
# remote_encryption_key = "base64-encoded-key" # or set ZAGE_DB_REMOTE_ENCRYPTION_KEY
# Remote replica sync tuning (remote_replica only)
# sync_interval_ms = 1000Environment overrides:
ZAGE_DB_PATH(local DB path)ZAGE_DB_AUTH_TOKENZAGE_DB_ENCRYPTION_KEYZAGE_DB_REMOTE_ENCRYPTION_KEYZAGE_DB_SYNC_INTERVAL_MSZAGE_SESSION_IDZAGE_COMPLETION_FORMAT=plain|zshZAGE_ALIASESorZAGE_ALIAS_FILEZAGE_HOSTNAMEZAGE_SUGGEST_TIMEOUT(human duration, server mode only)
Default local path (Linux): ~/.local/share/zage/zage.db
- Local at-rest encryption: set
encryption_key(and optionallyencryption_cipher). - Remote encryption: set
remote_encryption_key(base64 key).
If you use multiple machines, point Zage at a Turso (libsql) database. Your shell history is private by nature, so enable encryption whenever you use a remote database.
Pick a mode:
remote: connect to Turso directly (no local replica).remote_replica: keep a local replica and sync changes to the remote. This keeps reads fast and still shares history across machines.
Example: remote (no local replica)
backend = "embedded"
[db]
type = "remote"
url = "libsql://your-host"
auth_token = "your-token"
# Required for remote encryption
remote_encryption_key = "base64-encoded-key"Example: remote replica (recommended for laptops/desktops)
backend = "embedded"
[db]
type = "remote_replica"
path = "/path/to/zage.db"
url = "libsql://your-host"
auth_token = "your-token"
sync_interval_ms = 1000
# Encrypt the local replica at rest
encryption_key = "local-secret"
encryption_cipher = "aes256cbc"
# Encrypt remote traffic and server-side storage
remote_encryption_key = "base64-encoded-key"Key generation tip (base64, 32 bytes):
openssl rand -base64 32Zage uses an online two-tower embedding model that learns continuously from your shell history:
-
Candidate generation
- Parses commands with tree‑sitter (bash/zsh) into structured tokens.
- Generates candidates from transitions, context stats, and sequence patterns.
- Applies hard constraints (prefix matching, syntax validity, deduplication).
-
Online model ranking
- Learns a context embedding from workspace, directory, exit status, and recent commands.
- Learns a command embedding from normalized command structure (head, flags, args).
- Scores candidates by dot product plus calibrated priors (frecency, sequences).
- Updates embeddings online in server/service mode (and during
zage import) using negative sampling. - Uses replay buffers and confidence gates to prevent catastrophic forgetting.
The model trains incrementally as you work, adapting to your workflows without offline batch training. See docs/online_next_command_prediction.md for the full design.
Zage can learn from aliases and their expansions:
ZAGE_ALIASES="gst=git status;gco=git checkout"ZAGE_ALIAS_FILE="$HOME/.config/zage/aliases"
Shell hooks already call this, but you can record a single invocation manually:
zage record \
--shell "zsh" \
--command "ls -la" \
--working-directory "$PWD" \
--exit-status 0 \
--start-timestamp 1710000000 \
--end-timestamp 1710000001 \
--session-id 12345Run:
echo "$SHELL"Then follow the matching section above (Zsh or Bash).
- Import history first:
zage import --shell zsh
# or
zage import --shell bash- Check model status:
zage model statusThat means you’re in server mode but the daemon isn’t running. Pick one:
- Install the service:
zage service install - Run in foreground:
zage server - Or switch to embedded mode by setting
backend = "embedded"in~/.config/zage/config.toml
- Ensure zsh-autosuggestions is loaded before
zsh.zsh. - Ensure
ZAGE_AUTOSUGGEST_DISABLEis not set to1. - For debugging: set
ZAGE_ZSH_DEBUG=/tmp/zage-zsh.logand restart your shell.
MIT
- Inspired by projects like:
- Built with Rust 🦀