shell-hooks
| name | last commit | updated |
|---|---|---|
| README.md | Strip references to the uncommitted spec and fix doc drift | 14 days ago |
| signald-hooks.zsh | Supervise the collectors and bound the ways they hang | 14 days ago |
shell-hooks
Shell side of the terminal collector. Live as of v0.2. These
snippets belong in the dotfiles repo and are sourced by the user's .zshrc;
they are kept here so the privacy contract lives next to the code it constrains
(and so the differential secret-typing test can drive the real file).
The one rule
Aggregate-only. The hooks emit a keystroke count and a session duration. They never read, store, or transmit the content of a command or a keystroke.
- No input tap, no
CGEventTap, no PTY sniffing. - Never reference the
zleline buffer or capture argv. - What leaves the shell is a number, flushed on
precmd.
v0.2 ships the zle keypress counter: a widget wraps self-insert, does
(( _SIGNALD_KEYS++ )), then calls the built-in insert. It receives the key in
the editor and discards it — the character is never assigned to a variable that
outlives the widget and never leaves the shell. On each precmd the hook
appends one count record — <epoch_ms> <keys> <session_seconds> <session_id>,
numbers only — to $SIGNALD_SPOOL. The session id is the shell's pid, so
several shells can share one spool and signald still derives each shell's
rate separately. signald consumes the spool on every tick (renames it aside,
reads it, deletes it), so it never grows.
This contract is enforced by the forbidden-symbol scan and the differential
secret-typing test in crates/signal-schema/tests/privacy_invariant.rs (plus
the full-pipeline gate in crates/signald/tests/), which drive this file with
a planted secret and fail the build if it — in any encoding — reaches the spool,
the wire, or SQLite.
Files
signald-hooks.zsh— thezlekeystroke counter +precmdcount flush (aggregate-only).