pxh
Fast, local-first shell history search and sync for bash and zsh.
Your shell history is one of the most useful things on your machine -- but it's fragile, unsearchable, and stuck on one box. pxh fixes that. It stores every command in a local SQLite database with rich metadata (directory, host, exit code, duration), gives you powerful regex search and an interactive TUI, and syncs across machines over SSH or a shared filesystem.
No cloud accounts. No servers. No networking code. No AI. Just your history, on your machines, searchable in milliseconds.
$ pxh s ffmpeg
2022-06-04 07:54:04 ffmpeg -encoders | grep '^ V'
2022-06-07 23:17:33 ffmpeg -y -r 30 -f concat -safe 0 -i <(sed...) -c:v libx264rgb output.mp4
2022-08-03 10:39:11 ffmpeg -i cropped.mp4 -vf "pad=width=430:..." cropped.gif
Install
Homebrew (macOS and Linux):
brew install chipturner/tap/pxh
Prebuilt binaries (Linux x86_64/ARM64, macOS x86_64/ARM64):
curl -sSfL https://raw.githubusercontent.com/chipturner/pxhist/main/install.sh | shFrom source:
cargo install pxh # requires Rust 1.88+Shell setup
After installing, set up shell integration (including tab completions) and import your existing history:
pxh install bash # or: pxh install zsh # Import your existing history pxh import --shellname bash --histfile ~/.bash_history # or for zsh: pxh import --shellname zsh --histfile ~/.zsh_history # Activate in current session without restarting source <(pxh shell-config bash) # or: source <(pxh shell-config zsh)
From now on, pxh automatically records commands with directory, host, user, exit code, and duration.
Note: By default, trivial commands (
ls,cd,pwd,exit, etc.) are not recorded. See Configuration to change this.
Note: fish shell is not currently supported. PRs welcome!
Usage
Interactive Browser (pxh recall)
Press Ctrl-R in your shell to open the interactive history browser. Type to filter, arrows to navigate, Enter to execute, Tab to edit before executing. Alt-1 through Alt-9 quick-select visible entries.
pxh recall # Open history browser pxh recall --here # Limit to current directory pxh recall -q "git" # Start with a pre-filled query
Supports both emacs (default) and vim keybindings -- set keymap = "vim" in ~/.config/pxh/config.toml.
Keybindings:
Ctrl-Y/Alt-W-- Copy selected command to clipboard (via OSC 52)Ctrl-K-- Delete selected entry from historyCtrl-H-- Toggle host filter (this host only / all synced hosts)Ctrl-G-- Toggle directory/global filterAlt-1throughAlt-9-- Quick-select visible entries
Searching History (pxh show)
The show command (alias: s) is the power-search interface:
pxh show ffmpeg # Find commands containing "ffmpeg" pxh s docker run # Multiple patterns match in order (docker.*\s.*run) pxh s -i CMAKE # Case-insensitive search pxh s --here # Only commands from current directory pxh s --session $PXH_SESSION_ID # Only commands from current shell session pxh s -v cargo build # Verbose: show duration, session, directory pxh s -l 100 git # Show up to 100 results (default: 50, 0 = unlimited) pxh s --loosen foo bar # Match patterns in any order pxh s -F # Show commands that failed (non-zero exit) pxh s -F docker # Failed docker commands pxh s -H # Short for --here (current directory only) pxh s -S current # Short for --session current pxh s --working-directory ~/project # Filter to a specific directory
Failed commands are highlighted in red when the status column is visible (-v or -F).
Verbose output (-v):
$ pxh s -v cargo build
Start Duration Session Context Command
2023-02-06 22:10:20 1s 116ef63fc226 . cargo build --release
2023-02-07 06:32:04 37s ee6e1989f3da . cargo build --release
Synchronizing History (pxh sync)
Sync history across machines via SSH or a shared directory. pxh contains no networking code itself - sync works by invoking SSH or reading/writing files from a shared filesystem.
SSH Synchronization
# Bidirectional sync (default) pxh sync --remote myserver # One-way sync pxh sync --remote myserver --send-only # Push local → remote pxh sync --remote myserver --receive-only # Pull remote → local # Custom SSH options (like rsync's -e flag) pxh sync --remote myserver -e "ssh -p 2222" pxh sync --remote myserver -e "ssh -i ~/.ssh/special_key" # Sync only recent history pxh sync --remote myserver --since 30 # Last 30 days only # Custom remote paths pxh sync --remote myserver --remote-db /custom/path/pxh.db pxh sync --remote myserver --remote-pxh /usr/local/bin/pxh
Note: Remote sync automatically detects whether the remote host uses XDG paths (
~/.local/share/pxh/) or the legacy path (~/.pxh/). Use--remote-dbto override if needed.
Shared Directory Synchronization
Use Dropbox, OneDrive, NFS, or any shared filesystem:
# On each machine, run: pxh sync ~/Dropbox/pxh/ # Export only (don't import from others) pxh sync ~/Dropbox/pxh/ --export-only
Each machine writes its own .db file and reads from all others.
Note: During sync, incoming commands are automatically scanned for secrets (API keys, passwords, etc.) and filtered out. Use
--no-secret-filterto disable this behavior.
Security: Scanning and Scrubbing
Scanning for Secrets
Detect potential secrets (API keys, passwords, tokens) in your history:
pxh scan # Scan with default sensitivity (critical) pxh scan -c high # Include high-confidence matches pxh scan -c all # Show all potential matches pxh scan -v # Verbose: show which pattern matched pxh scan --json # Output as JSON for scripting pxh scan --histfile ~/.bash_history # Scan a histfile directly pxh scan --dir ~/Dropbox/pxh/ # Scan all databases in a sync directory
Confidence levels: critical (default), high, low, all. Each level includes all stricter levels -- high includes critical patterns, low includes both, and all adds patterns without a confidence rating.
Removing Secrets
Remove sensitive commands from your history:
pxh scrub # Interactive: prompts for the secret to remove pxh scrub "my-api-key" # Remove commands containing this string pxh scrub --scan # Remove all secrets found by scan pxh scrub --scan -c high # Remove critical and high-confidence secrets pxh scrub -n # Dry-run: show what would be removed pxh scrub -y # Skip confirmation prompt # Scrub from multiple locations pxh scrub --histfile ~/.bash_history "secret" # Also scrub from histfile pxh scrub --dir ~/Dropbox/pxh/ "secret" # Scrub from sync directory pxh scrub --remote myserver "secret" # Scrub from remote machine
Other Commands
Import
Import history from existing shell history files:
pxh import --shellname zsh # defaults to ~/.zsh_history pxh import --shellname bash # defaults to ~/.bash_history pxh import --shellname zsh -n # dry-run: show count without importing # Import from another machine pxh import --shellname zsh --histfile <(ssh server cat ~/.zsh_history) \ --hostname server --username root # Import from Atuin contrib/atuin-to-pxh | pxh import --shellname json --histfile /dev/stdin
Export
Export your entire history as JSON:
pxh export > history.json pxh export | jq '.[] | select(.exit_status != 0)' # Filter failed commands
Maintenance
Optimize database performance and reclaim space:
pxh maintenance # ANALYZE and VACUUM the database pxh maintenance other.db # Operate on a specific database file
Shell Completions
Tab completions are included automatically by pxh shell-config. To generate them separately:
pxh completions bash # or: zshDiagnostics
Check for common issues or view history statistics:
pxh doctor # Diagnose common issues pxh doctor --fix # Attempt automatic fixes pxh doctor --report # Generate a diagnostic report for bug reports pxh stats # Show history statistics
Configuration
pxh reads configuration from ~/.config/pxh/config.toml. All settings are optional with sensible defaults.
Note: If
~/.pxhexists from a previous installation, pxh uses it as a fallback for both config and data. New installations use XDG paths by default (~/.config/pxh/for config,~/.local/share/pxh/for data). Override withXDG_CONFIG_HOMEandXDG_DATA_HOME.
Example configuration:
[recall] # Keymap mode: "emacs" (default) or "vim" keymap = "emacs" # Show the preview pane with command details show_preview = true # Maximum results to load (default: 5000) result_limit = 5000 [recall.preview] # Which fields to show in the preview pane show_directory = true show_timestamp = true show_exit_status = true show_duration = true show_hostname = false # Useful if syncing across machines [host] # Override the detected hostname hostname = "my-laptop" # Previous hostnames for this machine (history from these is treated as local) aliases = ["old-laptop", "work-mac"] [shell] # Disable Ctrl-R binding (use pxh recall directly instead) disable_ctrl_r = false [history] # Regex patterns for commands to skip recording. # Default patterns ignore trivial commands (ls, cd, pwd, exit, etc.) # Set to [] to disable. ignore_patterns = [ "^ls$", "^cd( .)?$", # matches cd, cd -, cd ~, cd / (single-char args only) "^pwd$", "^exit$", "^clear$", "^fg$", "^bg$", "^jobs$", "^history$", "^true$", "^false$", ]
Tips and Tricks
Quick Search Alias
Create a symlink named pxhs pointing to pxh, and it will automatically run pxh show:
ln -s $(which pxh) ~/.local/bin/pxhs pxhs ffmpeg # Equivalent to: pxh show ffmpeg
Useful Patterns
# Commands that failed pxh s -F # Show commands that exited with non-zero status # What did I do in this project last week? pxh s --here -l 0 # How did I use that obscure tool? pxh s -v ansible-playbook # Commands from a specific session pxh s --session $PXH_SESSION_ID # Current session
Sync Strategies
- Real-time sync: Run
pxh sync --remote serverperiodically via cron - Shared folder: Just run
pxh sync ~/Dropbox/pxh/occasionally on each machine - Backup: The database is a single SQLite file -
cp ~/.local/share/pxh/pxh.db backup/
zsh-autosuggestions
If you use zsh-autosuggestions, pxh automatically registers itself as a suggestion strategy. Suggestions come from your full cross-machine history database, not just the local zsh history file.
Privacy
- Commands starting with a space are ignored (like bash's
HISTCONTROL=ignorespace) - Use
pxh scanregularly to detect accidentally committed secrets - Use
--no-secret-filterwith sync if you want to disable automatic secret filtering during import
Disabling Ctrl-R
If you prefer to keep your shell's default Ctrl-R behavior:
Option 1: CLI flag
# When sourcing manually: source <(pxh shell-config zsh --no-ctrl-r)
Option 2: Config file (~/.config/pxh/config.toml)
[shell] disable_ctrl_r = true
You can still use pxh recall directly or bind it to a different key.
Credits
Inspired by bash-history-sqlite, zsh-histdb, mcfly, and atuin. Embeds bash-preexec and secrets-patterns-db.
How it Works
pxh contains zero networking code. Sync works by invoking your SSH client or reading/writing files on a shared filesystem. No accounts, no cloud services, no ports, no attack surface. Your history stays on machines you control.
All data lives in a local SQLite database (~/.local/share/pxh/pxh.db). There's no central server. The binary is statically linked with no runtime dependencies beyond libc -- consistent behavior across bash and zsh, proper handling of edge cases (quoting, binary data, concurrent access), and fast enough that you never notice it.
pxh hooks into your shell via preexec/precmd functions to capture each command with its start/end time, working directory, exit status, session ID, hostname, and username. For bash, it embeds bash-preexec; for zsh, it uses native hooks.
Commands are stored as BLOBs (to handle non-UTF8 data) in SQLite with WAL mode and a 5-second busy timeout, so multiple shells can record simultaneously. A unique index prevents duplicates. Secret scanning uses patterns from secrets-patterns-db, categorized by confidence level.
Database location: ~/.local/share/pxh/pxh.db (override with --db or PXH_DB_PATH)
sqlite3 ~/.local/share/pxh/pxh.db "SELECT * FROM command_history LIMIT 10"