- Go 98.8%
- Just 1.2%
| .just | ||
| .woodpecker | ||
| internal | ||
| .gitignore | ||
| CHANGELOG.md | ||
| go.mod | ||
| go.sum | ||
| justfile | ||
| main.go | ||
| README.md | ||
verbatim
Shell history stored in SQLite, synced via git.
Install
cd $HOME/dev/vlv
git clone git.meatbag.se:varl/verbatim.git verbatim
mkdir histdb
cd verbatim
just install # builds and copies verbatim to ~/.local/bin ($XDG_BIN_HOME)
Or build manually:
go build -o verbatim .
install -d ~/.local/bin
install verbatim ~/.local/bin/
Migrate from log files
verbatim import ~/dev/vlv/histdb/
zsh
~/.zshrc:
eval "$(verbatim hook zsh)"
That is the whole integration. It defines a precmd that records each
command as it is run, and a verbatim-search widget bound to Ctrl-R that
picks one back out and puts it on the line.
Nothing is installed beside the binary and nothing is sourced out of the
clone, so the glue can't be older than the verbatim it calls — which is
what used to go wrong across machines.
Ctrl-R is where the picker belongs — it is the same question zsh's own
search asks, answered across every host instead of this shell. --bind
is there for when a shell wants it elsewhere, or not at all:
eval "$(verbatim hook --bind '^X^R' zsh)" # somewhere else
eval "$(verbatim hook --bind '' zsh)" # nowhere; stock Ctrl-R stays
The widget is defined either way, so it stays one bindkey away — and
verbatim pick runs it from the command line regardless.
The picker wants fzf; without it the hook records but binds nothing.
Not everything is recorded. Commands run as root are skipped, and so are the
ones you keep out of zsh's own history with a leading space, where
histignorespace is set — that one matters more here than it does locally,
since this database is synced across hosts and a secret committed to it is
harder to take back than a line in a file.
$VERBATIM_FZF_OPTS is how the picker looks — an array, read at each
invocation, so it can be reassigned freely:
VERBATIM_FZF_OPTS=(--height=100% --layout=reverse) # full screen, prompt on top
VERBATIM_FZF_OPTS=(--height=12) # a smaller window
It defaults to --height=40% --layout=default --no-sort, which is the same
share of the screen moor's ^G takes. What it cannot change is how a row is
built and read back — --delimiter, --with-nth, --nth, --tabstop,
--expect, --prompt and the binds are the picker's, and are applied last.
Matching is over the command alone: the dimmed context is there to be read, so
a run count of ×25 does not answer to 25.
Usage
verbatim [--db PATH] add [--timestamp T] [--hostname H] [--dir D] -- COMMAND...
verbatim [--db PATH] search [--host H] [--dir D] [--after T] [--before T] [--limit N] [--group] [QUERY...]
verbatim [--db PATH] pick [QUERY...]
verbatim [--db PATH] sync [DIR]
verbatim [--db PATH] import DIR
verbatim [--db PATH] stats
verbatim hook [--bind KEY] zsh
add fills in the timestamp, hostname and working directory itself; the
flags are for importers speaking for entries they did not witness.
In the picker: enter puts the command on the line (or prints it),
ctrl-y copies it, ctrl-r switches what is being searched — this
directory, or everything on every host.
The picker lists each distinct command once — the most recent time it was
run, where it was run, and ×N for how often. A menu, not a log: 74k rows
of history are 19k commands, and a third of the rows are ls, git status
and cd ... search --group is the same view from the command line;
search alone stays a log, in order, every occurrence.
Default --db: ~/.local/share/verbatim/history.db
Alternatives
Of course, there are many other shell history tools that are more mature, have more features, solve the same problem in a different way, etc:
- https://github.com/alessandrocarminati/hc
- https://github.com/ddworken/hishtory
- https://github.com/atuinsh/atuin
Those are my favourites of the ones I considered before settling on the two legacy shell scripts (later ported to Go -- and expanded on) was the fact that I want to rely on git for syncing, and I don't want to rely on a single server.
And it's hard to beat the <1ms speed of a kernel level >> for
appending a command to a file. I've accepted the trade-off of verbatim
taking ~2ms to add a record to SQLite.
With Git I can choose to pull from a specific machine, or a centralised forge depending on what I feel like, so I use that for sync.