GitHub - Niche-Knack-Apps/stenographer: Shell history search: a daemon records every command you run across all terminals; steno searches and replays them instantly. By Niche-Knack — https://nk.tools

GitHub

5 min read Original article ↗

Stenographer

ci release license

Shell history search. A background daemon records every command you run across all terminals; a CLI lets you search and replay them instantly.

Made by Niche-Knack — Smart Tools. Sharp Solutions.

Install

Linux, amd64 or arm64. Each release has two static binaries — steno (the CLI) and stenographerd (the daemon) — that only need to be put on your PATH. Then steno install sets up the systemd user service and the shell hooks and prints the one source command to run:

arch=amd64   # or arm64
base=https://github.com/Niche-Knack-Apps/stenographer/releases/latest/download
curl -LO $base/steno_linux_$arch -LO $base/stenographerd_linux_$arch -LO $base/SHA256SUMS
sha256sum --ignore-missing -c SHA256SUMS
install -m 0755 steno_linux_$arch ~/.local/bin/steno
install -m 0755 stenographerd_linux_$arch ~/.local/bin/stenographerd
steno install

From source:

make install        # build release + install + print source command
source ~/.zshrc     # activate in current shell (or restart terminal)

make install compiles both binaries, copies them to ~/.local/bin/, installs the systemd user service, and adds shell integration to your RC files. It prints the exact source command to run at the end. Make runs in a subprocess so it cannot source your shell itself — you need to run that one command.

To upgrade: make install && source ~/.zshrc — the hook files are regenerated on every install.

Usage

Open history search from anywhere:

steno              # search all recorded commands (default)
steno history      # same as above
steno here         # search commands run in the current directory

Or press Ctrl-R from any prompt.

Select a command and press:

  • Enter — place it at your prompt, ready to edit or run
  • Alt-Enter — execute it immediately in your current shell

Picker keys

Key Action
Type Filter by substring (all tokens must match)
↑ / ↓ Navigate
Enter Place selected command at prompt
Alt-Enter Execute selected command immediately
Esc Cancel

The right pane shows the full command, working directory, exit code, duration, git branch, and surrounding context.

How many commands the picker loads

By default the picker loads the 500 most recent commands. Raise it for a single search with --limit, or change the default permanently in config:

steno --limit 5000     # load up to 5000 this time
steno --limit 0        # load everything (0 or -1 = unlimited)
steno here --limit -1  # same, scoped to the current directory

To make it permanent, set history_limit in ~/.config/stenographer/config.toml (0 or -1 = unlimited):

The pool is still bounded by retention_days — unlimited means "everything still retained," not "forever."

Commands

Command Description
steno / steno history Search all recorded commands
steno here Search commands run in the current directory
steno prime Import existing shell history into the database
steno install First-time setup: binaries, service, shell hooks
steno hook install Re-install / refresh shell hooks
steno hook uninstall Remove shell hooks
steno hook status Show hook installation status
steno daemon start|stop|restart|logs Control the background daemon
steno version / steno -v Print version
steno completions <shell> Print shell completions

Priming from existing history

Import your existing shell history on first setup:

steno prime                   # auto-detect shell (zsh/bash/fish)
steno prime --shell zsh       # specify shell explicitly
steno prime --limit 50000     # raise the import limit (default: 10 000)
steno prime --dry-run         # preview without importing

Noise commands (ls, clear, cd .., etc.) are filtered out. Duplicates are deduplicated before import. After priming, steno shows results immediately without waiting for new history to accumulate.

Shell completions

# zsh
steno completions zsh > ~/.zfunc/_steno
# add to ~/.zshrc: fpath=(~/.zfunc $fpath) && autoload -U compinit && compinit

# bash
steno completions bash >> ~/.bash_completion

# fish
steno completions fish > ~/.config/fish/completions/steno.fish

Architecture

Two binaries: stenographerd (background daemon, SQLite, Unix socket) and steno (CLI client). Shell hooks write pre/post-exec events to a spool file; the daemon picks them up via inotify and stores them in SQLite. The CLI queries the daemon over a Unix socket.

Shell integration uses a temp-file pattern (same as atuin): the binary writes the selected command to STENO_RESULT_FILE; the shell wrapper reads it and either calls print -z (place at prompt) or eval (execute immediately). This means cd, export, and other builtins work correctly.

Absolute paths. When the daemon records a command it rewrites relative path arguments to absolute paths rooted at the command's working directory (e.g. cat zarf/x/setup.sh → cat /home/me/proj/zarf/x/setup.sh), so replaying from a different directory still works. A one-time backfill rewrites existing history on the next upgrade. This is a whitespace-token heuristic gated on the file actually existing — not a full shell parser: quoted paths with spaces, punctuation-adjacent paths, globs, and command substitution are left untouched, and bare words without a slash or extension (cargo test, make build) are never rewritten. The backfill resolves against the current filesystem, so history whose files have since moved stays as-is.

If the daemon isn't running:

✗ Daemon is not running.
  First-time setup:  steno install
  Start the daemon:  steno daemon start
  Check the logs:    steno daemon logs

Without systemd

Add to ~/.profile:

if ! pgrep -u "$USER" stenographerd >/dev/null 2>&1; then
    nohup stenographerd >> ~/.local/share/com.niche-knack.stenographer/logs/daemon.log 2>&1 &
fi

Build

make dev        # debug build + start daemon
make build      # release build → dist/
make install    # build + install + print source command
make verify     # clippy + tests + fmt check

Releases

Every merge to main runs formatting, clippy and the tests. When the merge carries a new version in VERSION (kept equal to Cargo.toml), the release workflow builds static amd64 and arm64 Linux binaries of steno and stenographerd, tags vX.Y.Z, and publishes them with SHA256SUMS as a GitHub release. A pull request that does not bump VERSION fails its check, so every merge ships.

Support

Stenographer is free and open. Niche-Knack also makes small, sharp, offline-first apps — see them at nk.tools.

Really believe in this project's potential and want to contribute to its development? Buy Me a Coffee.

License

Apache License 2.0. See NOTICE.