Aster
Aster is a natural, history-first shell completion system. It would rather show nothing than offer a completion it cannot justify.
The project is in early development. The current vertical slice provides a shared local daemon, SQLite-backed command history, Zsh history import, and an inline completion menu, ghost text, command discovery, lazy descriptions, native Zsh candidates, full acceptance, and segment-by-segment Tab completion.
Principles
- Completed command history is the highest-priority source.
- Candidate providers are ordered tiers; lower tiers fill unused menu capacity but never outrank higher-confidence results.
- The menu appears while typing and highlights the highest-ranked candidate.
- Ctrl-Space accepts the entire candidate; Tab accepts one useful segment.
- No per-command shell completion scripts are required.
- Every host has one shared daemon and one history database.
- tmux panes and concurrent SSH shells remain lightweight clients.
- Silence is preferable to an uncertain completion.
Build
Or install the published package while keeping the executable name aster:
Aster currently targets Unix systems because its shared transport is an
owner-private Unix socket. Inline fuzzy mode requires fzf on the host.
Zsh Setup
Add one integration line to .zshrc:
This is Aster's only shell integration. You do not need to source completion scripts for every installed program.
The integration:
- Imports the existing Zsh history at shell startup when the file changed since the previous import.
- Records submitted foreground commands and the shell status reported afterward.
- Starts the per-host daemon automatically.
- Uses Tab to accept the shortest next word or path segment from an open Aster suggestion; each accepted segment resets the refreshed menu to row 1. With no open suggestion, Tab delegates to the previous Zsh widget.
- Uses Shift-Tab to move upward through suggestions without entering a modal editing state; letters and Backspace continue editing normally.
- Consumes a second consecutive Space to enter inline fuzzy mode over shared history and installed commands. Escape restores only the current command's base buffer; Ctrl-C and each new prompt discard all fuzzy state.
- Captures append-safe candidates from the configured Zsh completion system in a forked completion context after about 30-60 ms idle and blends them into Aster's menu without blocking input.
- Shows ranked candidates automatically as the command buffer changes.
- Renders a bordered, color-highlighted menu and selected-candidate ghost text
as part of ZLE's multiline display. Long rows preserve the completion suffix,
rendering as
… suggestionwhen the typed command prefix would hide it. - Adds a lazy preview box at 100 columns or wider only when useful content is available. History-only and generic rows stay compact; command details reuse the existing asynchronous description workers, and selected native text files are read through a bounded, sanitized background helper. Changing rows erases the previous preview immediately and stale async results are target-checked.
- Previews simple
ls, GNUgls, andezasuggestions asynchronously, includinglsaliases backed byeza. Aster passes a validated argv directly without a shell, translatesezacolors into ZLE-safe highlight spans, disables icons and hyperlinks, caps output, and kills previews that exceed the short deadline. - Uses Ctrl-Space to accept the entire highlighted candidate and preserves its previous binding as the fallback when Aster has no candidate.
- Uses Ctrl-N and Ctrl-K to move through an open menu and updates inline ghost text to preview the selected row; outside the menu their prior widgets remain active.
- Leaves Up and Down dedicated to their existing Zsh history widgets, cancelling stale completion work before history changes the command buffer.
- Accepts the full suggestion with Ctrl-Space, or the shortest next segment with Tab; Enter remains the shell's untouched command-submission binding.
- Leaves tmux pane titles and automatic window naming entirely under tmux and the foreground application's control.
Aster owns ZLE's suggestion display. Do not load a second autosuggestion plugin
alongside it; competing POSTDISPLAY highlights can recolor or stale the menu.
Native Zsh capture is asynchronous and best-effort. History remains first at
every position; explicit filesystem matches follow it and remain stable while
native results arrive. Duplicate displays are removed.
At every argument position Aster offers bounded
local filesystem matches, so path completion does not depend on a
command-specific completion function. Root-command flags parsed from man pages
or sandboxed --help output appear lazily with their descriptions ahead of
duplicate native matches. Quoted
replacements, mid-word edits, and other non-append-safe completion behavior
remain under ordinary Zsh Tab. For ambiguous filesystem matches, Tab accepts
only their shared prefix and never selects the first entry arbitrarily. An exact
file remains selectable with a following Tab, which appends a space.
The same setup works inside tmux and on SSH hosts. Each remote host runs its own
daemon and keeps its own local history; tmux panes on that host share it.
Aster uses its Unicode UI when locale charmap reports UTF-8 and automatically
falls back to ASCII borders and markers otherwise.
Image and PDF candidates currently show bounded metadata rather than graphics.
Kitty image transmission needs terminal-owned image IDs and cleanup that ZLE's
POSTDISPLAY model cannot provide safely; it is tracked for the future PTY UI.
Conservative Acceptance
Given a history entry:
cd ~/dev/gitrepos/aster
and the current buffer:
cd ~/d
pressing Tab on the automatically highlighted candidate inserts only:
ev/
Aster then queries again from the new buffer. Pressing Ctrl-Space instead inserts
the complete ev/gitrepos/aster remainder. Tab also recognizes structured shell
values. For example, ssh ali completes to ssh alice@ before accepting the
host, while scp zzu can advance through zzuser@, example.com:/, and each
remote path component separately. Assignments, comma-separated values, URLs,
rsync's host::module syntax, quoting, escaping, and bracketed IPv6 hosts use the
same conservative boundary scanner.
Commands
aster daemon
aster stop
aster doctor
aster init zsh
aster import-history --file ~/.zsh_history
aster record --command "git status" --cwd "$PWD" --exit-code 0
aster complete --buffer "git st" --cursor 6 --cwd "$PWD"
Client commands automatically start the daemon if it is unavailable.
Configuration
The default configuration is written by aster init zsh:
[]
= 8
= "ctrl-space"
= "full"
[]
= true
= true
[]
= 64
= 6
= 2
= "4"
= "10"
= "7"
= "8"
= "8"
= "8"
= "15"
= "0"
completion.key also accepts Ctrl-letter names such as ctrl-x. Ctrl-I (Tab),
Ctrl-J, Ctrl-K, Ctrl-M, and Ctrl-N are reserved by the menu integration. The
generated binding is refreshed when a new shell starts.
UI colors accept ANSI palette indexes from 0 through 255 or exact #RRGGBB
values. ANSI indexes follow the active terminal theme. prompt_offset is the
visual width of the final prompt line before editable text. The menu follows the
live letter cursor and clamps before the terminal's final column.
Paths can be overridden for testing or custom deployments:
| Variable | Purpose |
|---|---|
ASTER_CONFIG |
Configuration file |
ASTER_STATE_DIR |
SQLite database and daemon log directory |
ASTER_SOCKET |
Unix socket path |
XDG_CONFIG_HOME |
Default config root |
XDG_STATE_HOME |
Default state root |
Run aster doctor to print the resolved paths and verify daemon connectivity.
Custom state and socket paths must live beneath directories owned by the current
user and inaccessible to group and other users.
Status
Implemented:
- Shared auto-started daemon.
- Versioned JSON protocol over an owner-private Unix socket.
- SQLite WAL history shared by all shells on a host.
- Change-detected Zsh history import.
- Current-directory, success, and recency-aware history ranking.
- Cached
PATHcommand inventory, shell builtins, semantic command icons, and authored descriptions for common tools. - Lazy command-description enrichment from exact man pages and sandboxed
--helpoutput, with executable-fingerprinted persistent caching. - Asynchronous native Zsh candidate capture for append-safe options, subcommands, paths, aliases, and other configured completion sources.
- Prefix-only completion with conservative partial acceptance.
- Cursor-anchored, scrolling ZLE menu with ghost text, descriptions, semantic kinds, highlighted matches, position counter, and configurable key hints.
- Debounced asynchronous completion requests that never block character input.
- Zsh native-completion fallback.
Description discovery never runs on the completion request path. An unresolved
visible command keeps its origin fallback, is queued on a bounded worker pool,
and is refreshed in the menu when metadata becomes ready. Aster checks the exact
man page first. On macOS, it may then run --help through sandbox-exec with
network and writes denied, an empty environment, bounded output, and a hard
timeout. Direct executable probing is disabled on platforms without that
sandbox. Cached misses expire after one day; descriptions expire after 30 days
or immediately when the executable path, identity, size, mode, or timestamps
change.
Planned:
- Structured option, subcommand, and enum candidates.
- Filesystem segments.
- Optional project-aware providers.
See docs/architecture.md and
docs/roadmap.md.