srgrn/hy

hy

Per-prompt shell history logger plus a hy <pattern> search shortcut. Sourceable files — one per shell — that share a common on-disk format.

What this does

Every command you run gets appended to a daily log file under ~/.logs/:

~/.logs/history-2026-04-25.log
~/.logs/history-2026-04-26.log
…

Each entry is exactly one line, tab-delimited:

TIMESTAMP  ⇥  PWD  ⇥  COMMAND

Two design choices fix the failure modes of the naive single-line history loggers people typically write:

You then search across all daily logs with hy (oldest matches first) or hyr (newest first):

hy "rsync"            # all commands containing "rsync"
hy -i FOR             # case-insensitive
hy "dir with spaces"  # works because tabs separate fields
hyr git               # most recent git invocations first

The files

hy.zsh

Hooks precmd. Uses zsh/datetime + strftime to avoid forking date.

⬇ Download · view

hy.bash

Hooks PROMPT_COMMAND idempotently. Uses printf '%(…)T' on bash 4.2+, falls back to date on stock /bin/bash 3.2.

⬇ Download · view

hy.fish

Hooks fish_postexec — receives the command directly as $argv[1], so multi-line capture is automatic.

⬇ Download · view

hy.ps1

Wraps PowerShell's prompt function idempotently. Uses Get-History for logging and Select-String for search.

⬇ Download · view

Tip: file:// in some browsers ignores the download attribute and opens the file inline instead. If that happens, right-click the link and choose Save Link As…, or serve this folder over HTTP — e.g. python3 -m http.server from the hy/ directory, then open localhost:8000.

All implementations write the same on-disk format, so you can switch shells and hy still searches across history written by any of them.

Installation

Each one-liner downloads the right file to ~/.config/hy/ (or the conventional fish location) and wires it up. Use the Copy button, paste into your shell, hit return.

zsh

Downloads to ~/.config/hy/hy.zsh and adds a guarded source line to ~/.zshrc:

mkdir -p ~/.config/hy && curl -fsSL "__BASE__/hy.zsh?v=$(date +%s)-$$" -o ~/.config/hy/hy.zsh && { grep -qxF 'source ~/.config/hy/hy.zsh' ~/.zshrc 2>/dev/null || echo 'source ~/.config/hy/hy.zsh' >> ~/.zshrc; } && source ~/.config/hy/hy.zsh

Registered via add-zsh-hook precmd — coexists with oh-my-zsh, starship, prezto, or your own existing precmd. Re-sourcing the file won't duplicate the hook.

bash

Downloads to ~/.config/hy/hy.bash and adds a guarded source line to ~/.bashrc:

mkdir -p ~/.config/hy && curl -fsSL "__BASE__/hy.bash?v=$(date +%s)-$$" -o ~/.config/hy/hy.bash && { grep -qxF 'source ~/.config/hy/hy.bash' ~/.bashrc 2>/dev/null || echo 'source ~/.config/hy/hy.bash' >> ~/.bashrc; } && source ~/.config/hy/hy.bash

fish

Drops the file straight into ~/.config/fish/conf.d/ — fish auto-loads anything in there, no extra source line needed:

mkdir -p ~/.config/fish/conf.d && curl -fsSL "__BASE__/hy.fish?v="(date +%s)"-$fish_pid" -o ~/.config/fish/conf.d/hy.fish && exec fish

PowerShell

Downloads to $HOME/.config/hy/hy.ps1 and adds a guarded dot-source line to $PROFILE.CurrentUserAllHosts:

$HyDir = Join-Path $HOME ".config/hy"; New-Item -ItemType Directory -Force $HyDir | Out-Null; $HyFile = Join-Path $HyDir "hy.ps1"; Invoke-WebRequest -UseBasicParsing "__BASE__/hy.ps1?v=$([DateTime]::UtcNow.Ticks)-$PID" -OutFile $HyFile; if (Get-Command Unblock-File -ErrorAction SilentlyContinue) { Unblock-File -Path $HyFile }; $HyProfile = $PROFILE.CurrentUserAllHosts; New-Item -ItemType Directory -Force (Split-Path -Parent $HyProfile) | Out-Null; $HySource = '. "$HOME/.config/hy/hy.ps1"'; if (-not (Test-Path $HyProfile) -or -not (Select-String -Path $HyProfile -SimpleMatch $HySource -Quiet)) { Add-Content -Path $HyProfile -Value $HySource }; . $HyFile

What gets defined

NameKindPurpose
precmd (zsh) / __hy_log (bash, fish, PowerShell)hookRuns before/after each prompt; appends one log entry.
hy <pattern> [options…]functionSearches across ~/.logs/history-*.log. Chronological order.
hyr <pattern> [options…]functionSame as hy, newest match first.

Verification

After sourcing, try each failure mode and confirm hy finds it:

mkdir -p "/tmp/dir with spaces" && cd "/tmp/dir with spaces" && ls
# zsh/bash multi-line:
for i in 1 2 3; do
  echo $i
done
# plain:
git status

tail ~/.logs/history-$(date +%Y-%m-%d).log
# Every line should have exactly two tab characters.
# The for-loop appears as ONE line containing literal `\n` between statements.
# The space-containing path appears intact in field 2.

hy ls
hyr ls
hy -i FOR
hy "dir with spaces"

Notes & trade-offs