feat(c5): multiplexer session logging (tmux + zellij) #53
@@ -8,6 +8,7 @@ abbreviation system for keyboard-driven workflows.
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Session Logging](#session-logging)
|
||||
- [Documentation](#documentation)
|
||||
- [Installation](#installation)
|
||||
- [Personalization](#personalization)
|
||||
@@ -27,10 +28,71 @@ This config layers on top of the CachyOS base Fish configuration and adds:
|
||||
- **Smart CLI wrappers** that prefer modern tools (`eza`, `bat`, `btop`, `dust`, `prettyping`) with graceful fallbacks
|
||||
- **Auto Python venv** activation on directory change (direnv-aware)
|
||||
- **Kitty terminal** deep integration for splits, tabs, and SSH
|
||||
- **Automatic session logging** — terminal scrollback, multiplexer panes (tmux/zellij), and AUR-helper output are captured to `~/.terminal_history` (see the caution below and [Session Logging](#session-logging))
|
||||
- **AI workflow** helpers for Claude and Antigravity session management
|
||||
- **WakaTime** shell activity tracking
|
||||
- **Opt-out toggles** for every opinionated component — see [Minimal Mode](#minimal-mode)
|
||||
|
||||
> [!CAUTION]
|
||||
> **This configuration logs your terminal sessions to disk by default.**
|
||||
> Out of the box it silently captures terminal output to `~/.terminal_history`:
|
||||
> Kitty scrollback when a window closes, live tmux pane streams, zellij pane
|
||||
> snapshots on exit, and full `paru`/`yay` output. These logs can contain
|
||||
> command output, file contents, and anything else printed to your terminal.
|
||||
> Nothing is sent off your machine, but the files persist locally until pruned.
|
||||
>
|
||||
> To turn all logging off, set the C5 category variable:
|
||||
>
|
||||
> ```fish
|
||||
> set -U __fish_config_op_logging off
|
||||
> ```
|
||||
>
|
||||
> Or run **`config-toggle`** for an interactive menu to flip logging (and any
|
||||
> other opinionated category) on or off — no variable names to remember.
|
||||
>
|
||||
> This takes effect immediately in every open shell. See [Session Logging](#session-logging)
|
||||
> for exactly what is captured and where, and [Minimal Mode](#minimal-mode) for the
|
||||
> full set of opt-out toggles.
|
||||
|
||||
---
|
||||
|
||||
## Session Logging
|
||||
|
||||
This config captures terminal output to `~/.terminal_history` (override with
|
||||
`$SCROLLBACK_HISTORY_DIR`) so you can search back through past sessions. It is
|
||||
**on by default**. Five sources feed it:
|
||||
|
||||
| Source | When it captures | Log file |
|
||||
|---|---|---|
|
||||
| Kitty scrollback | When a Kitty window/tab closes | `scrollback_<timestamp>.log` |
|
||||
| tmux pane | Continuously while the pane is open (`pipe-pane`) | `tmux_<session>-w<win>-p<pane>_<timestamp>.log` |
|
||||
| zellij pane | Snapshot taken on shell exit (`dump-screen`) | `zellij_<session>-p<pane>_<timestamp>.log` |
|
||||
| `paru` wrapper | Every `paru` invocation | `paru_<timestamp>.log` |
|
||||
| `yay` wrapper | Every `yay` invocation | `yay_<timestamp>.log` |
|
||||
|
||||
Old logs are pruned automatically to stay within `$SCROLLBACK_HISTORY_MAX_FILES`
|
||||
(default 100) per source, and empty/trivial captures are discarded.
|
||||
|
||||
**These logs can contain secrets** — anything printed to your terminal (command
|
||||
output, file dumps, tokens echoed to stdout) ends up in them. They never leave
|
||||
your machine, but treat `~/.terminal_history` as sensitive.
|
||||
|
||||
Disable all of it with a single universal variable:
|
||||
|
||||
```fish
|
||||
set -U __fish_config_op_logging off # disable; takes effect in every open shell
|
||||
set -Ue __fish_config_op_logging # re-enable (erase the override)
|
||||
```
|
||||
|
||||
Prefer an interactive interface? Run **`config-toggle`** for a full-screen
|
||||
picker that flips logging — and every other opinionated category — on or off
|
||||
per session or universally, without memorizing variable names.
|
||||
|
||||
Disabling also removes the generated `paru`/`yay` log wrappers and tells the
|
||||
Kitty watcher to skip capture via a sentinel file — no shell or terminal
|
||||
restart required. Logging is category **C5** in [Minimal Mode](#minimal-mode);
|
||||
`set -U __fish_config_opinionated 0` turns it off along with everything else.
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
@@ -178,7 +240,7 @@ If you'd rather set them by hand, each category is controlled by a universal var
|
||||
| `__fish_config_op_autoexec` | Startup side-effects: Fisher bootstrap, theme apply, `paru`/`yay` wrapper generation, auto venv activation, WakaTime hook |
|
||||
| `__fish_config_op_overrides` | Vi mode, `exit`→`smart_exit`, `$PAGER`/`$MANPAGER`/`$CDPATH`, bang-bang history expansion, autopair, puffer, Starship prompt, theme colors |
|
||||
| `__fish_config_op_integrations` | Kitty/WezTerm window abbreviations, `done` notifications, `spwin`/`tab`/`split`, `hist`, `logs`, `upgrade`, WakaTime |
|
||||
| `__fish_config_op_logging` | Scrollback capture on exit, `paru`/`yay` AUR log wrappers, Kitty watcher capture (sentinel-file coordinated) |
|
||||
| `__fish_config_op_logging` | Scrollback capture on exit, tmux `pipe-pane` pane logging, zellij `dump-screen` capture on exit, `paru`/`yay` AUR log wrappers, Kitty watcher capture (sentinel-file coordinated) |
|
||||
| `__fish_config_op_greeting` | Per-session `fish_greeting` (suppresses distro greetings such as CachyOS fastfetch by overriding with an empty function); first-run welcome banner |
|
||||
| `__fish_config_opinionated` | Master switch — all six categories at once |
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# C5 — Logging & Capture: starts a pipe-pane log for the current tmux pane
|
||||
# when fish launches inside a tmux session. Each fish shell gets its own
|
||||
# timestamped log file in SCROLLBACK_HISTORY_DIR (default: ~/.terminal_history).
|
||||
# Naming: tmux_<session>-w<window>-p<pane>_YYYY-MM-DD_HH-MM-SS.log
|
||||
|
||||
__fish_config_op_enabled __fish_config_op_logging; or exit
|
||||
status is-interactive; or exit
|
||||
type -q tmux; or exit
|
||||
set -q TMUX; or exit
|
||||
|
||||
_tmux_pipe_log
|
||||
@@ -0,0 +1,24 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# C5 — Logging & Capture: registers a fish_exit handler that dumps the current
|
||||
# Zellij pane's scrollback to a log file when the shell exits. Unlike tmux
|
||||
# (streamed live via pipe-pane), Zellij has no continuous-capture facility, so
|
||||
# we snapshot once on exit via `zellij action dump-screen --full`.
|
||||
#
|
||||
# The handler is registered whenever fish runs inside Zellij; the C5 logging
|
||||
# guard is evaluated inside _zellij_dump_log at exit time, so toggling
|
||||
# __fish_config_op_logging takes effect on the next exit without a restart
|
||||
# (no sync_logging coordination needed, since there is no live stream to stop).
|
||||
#
|
||||
# Event-handler functions must be defined at startup (conf.d) so the --on-event
|
||||
# binding is registered; autoloaded functions in functions/ never register.
|
||||
|
||||
status is-interactive; or exit
|
||||
type -q zellij; or exit
|
||||
set -q ZELLIJ; or exit
|
||||
|
||||
function __zellij_dump_on_exit --on-event fish_exit \
|
||||
--description 'C5 event handler: dump Zellij pane scrollback on shell exit'
|
||||
_zellij_dump_log
|
||||
end
|
||||
+46
-2
@@ -32,12 +32,27 @@ A production-grade Fish shell configuration targeting Fish 4.x. It provides:
|
||||
- Drop-in replacements for common Unix tools (ls, cat, rm, du, ping, less)
|
||||
- Deep Kitty and WezTerm terminal integration: tab/window/pane management from
|
||||
the command line
|
||||
- Scrollback history snapshots saved to ~/.terminal_history on session exit
|
||||
- Automatic session logging: terminal scrollback, tmux/zellij panes, and
|
||||
paru/yay output captured to ~/.terminal_history (on by default; see below)
|
||||
- Automatic Python virtualenv activation on directory change
|
||||
- Cross-platform package management via pkg and fish-deps
|
||||
- AI session helpers for Claude Code and Antigravity
|
||||
- Catppuccin Mocha color theme throughout
|
||||
|
||||
┌───────────────────────────────────────────────────────────────────────────┐
|
||||
│ CAUTION - SESSION LOGGING IS ON BY DEFAULT │
|
||||
│ │
|
||||
│ This configuration silently records terminal output to │
|
||||
│ ~/.terminal_history: Kitty scrollback on window close, live tmux pane │
|
||||
│ streams, zellij pane snapshots on exit, and full paru/yay output. These │
|
||||
│ logs can contain command output, file contents, and secrets printed to │
|
||||
│ the terminal. Nothing leaves your machine, but the files persist locally. │
|
||||
│ │
|
||||
│ Disable all logging with: set -U __fish_config_op_logging off │
|
||||
│ Prefer a menu? Run the interactive picker: config-toggle │
|
||||
│ See Section 7 (C5 - Logging and Capture) for the full breakdown. │
|
||||
└───────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
The configuration is split across:
|
||||
|
||||
config.fish Main entry point; sets env vars and PATH
|
||||
@@ -51,6 +66,8 @@ The configuration is split across:
|
||||
logging-events.fish C5 --on-variable event handlers; syncs logging state at startup
|
||||
paru-wrapper.fish Auto-generates ~/.local/bin/paru logging wrapper
|
||||
puffer.fish !! / !$ / ./ expansion (bundled from nickeb96/puffer-fish)
|
||||
tmux-logging.fish C5 starts tmux pipe-pane capture when fish runs inside tmux
|
||||
zellij-logging.fish C5 fish_exit handler dumping zellij pane scrollback on exit
|
||||
sponge_privacy.fish Sponge privacy patterns; filters credentials from history
|
||||
starship.fish fish_prompt with OSC 133 shell-integration markers
|
||||
tailscale.fish Tailscale CLI tab completions
|
||||
@@ -1552,6 +1569,10 @@ Examples:
|
||||
set -U __fish_config_op_greeting 1
|
||||
# (erase both to go back to full-flavor defaults)
|
||||
|
||||
For an interactive alternative to setting these variables by hand, run
|
||||
config-toggle — a full-screen TUI that flips any category (including C5
|
||||
logging) on or off, per session or universally. See its entry in Section 5.
|
||||
|
||||
Notes:
|
||||
|
||||
- Command shadows (rm, cat, ls, ...) react immediately; conf.d-level
|
||||
@@ -1678,19 +1699,40 @@ silently failing.
|
||||
|
||||
#### C5 — Logging and Capture
|
||||
|
||||
Three components capture shell output to disk. Disabling
|
||||
Five components capture shell output to disk. Disabling
|
||||
__fish_config_op_logging skips all capture and removes the logging wrappers.
|
||||
|
||||
Component What it captures
|
||||
───────────────────────────────────────────────────────────────────────────
|
||||
Scrollback capture Terminal session output saved to:
|
||||
~/.terminal_history/scrollback_YYYY-MM-DD_HH-MM-SS.log
|
||||
tmux pane capture Continuous pane stream via pipe-pane, saved to:
|
||||
~/.terminal_history/tmux_<session>-w<win>-p<pane>_YYYY-MM-DD_HH-MM-SS.log
|
||||
zellij pane capture Pane scrollback snapshot on shell exit, saved to:
|
||||
~/.terminal_history/zellij_<session>-p<pane>_YYYY-MM-DD_HH-MM-SS.log
|
||||
paru wrapper All paru/AUR output captured to:
|
||||
~/.terminal_history/paru_YYYY-MM-DD_HH-MM-SS.log
|
||||
yay wrapper All yay/AUR output captured to:
|
||||
~/.terminal_history/yay_YYYY-MM-DD_HH-MM-SS.log
|
||||
Kitty watcher watcher.py captures scrollback when Kitty closes
|
||||
|
||||
The tmux capture starts automatically when fish launches inside any tmux
|
||||
pane ($TMUX is set). It uses tmux's native pipe-pane to stream all pane
|
||||
output directly to disk without an intermediate process. Each fish shell
|
||||
session gets its own log file; a new log is created on each shell start
|
||||
(including exec fish and new splits). Before each new log, the oldest
|
||||
tmux_*.log files are pruned (by modification time) to keep the total within
|
||||
SCROLLBACK_HISTORY_MAX_FILES, matching the paru/yay wrapper behaviour.
|
||||
|
||||
The zellij capture works differently: Zellij has no live output-streaming
|
||||
facility like pipe-pane, so the log is taken as a one-shot snapshot when the
|
||||
shell exits, via `zellij action dump-screen --full`. A fish_exit handler
|
||||
(registered whenever $ZELLIJ is set) writes the pane's full scrollback and
|
||||
then prunes old zellij_*.log files the same way. Because the capture happens
|
||||
at exit, toggling __fish_config_op_logging takes effect on the next exit with
|
||||
no restart or sentinel coordination needed — the C5 guard is re-checked when
|
||||
the handler fires.
|
||||
|
||||
Logging coordination via sentinel file
|
||||
|
||||
C5 uses a sentinel file to synchronize state between the shell and
|
||||
@@ -1705,11 +1747,13 @@ Disabling __fish_config_op_logging:
|
||||
3. Kitty's watcher.py reads the sentinel on each save attempt and
|
||||
skips capture — no Kitty restart required.
|
||||
4. smart_exit stops saving scrollback logs.
|
||||
5. Stops tmux pipe-pane capture in every open fish shell inside tmux.
|
||||
|
||||
Re-enabling __fish_config_op_logging:
|
||||
1. Removes the sentinel in every open shell.
|
||||
2. Regenerates paru/yay logging wrappers in ~/.local/bin/.
|
||||
3. Kitty watcher resumes capture on the next session exit.
|
||||
4. Restarts tmux pipe-pane capture in every open fish shell inside tmux.
|
||||
|
||||
Changes propagate to all running shells through an event handler that fires
|
||||
whenever __fish_config_op_logging changes — no shell restart needed.
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Synchronises C5 logging state: creates or removes the Kitty sentinel
|
||||
# file ($XDG_CONFIG_HOME/fish/.logging_disabled) and generates or removes
|
||||
# the paru/yay AUR-helper log wrappers based on the combined value of
|
||||
# file ($XDG_CONFIG_HOME/fish/.logging_disabled), generates or removes the
|
||||
# paru/yay AUR-helper log wrappers, and starts or stops tmux pipe-pane
|
||||
# capture for the current pane — all based on the combined value of
|
||||
# __fish_config_opinionated (master) and __fish_config_op_logging (C5).
|
||||
# Called by --on-variable event handlers whenever either variable changes.
|
||||
# Safe to call at any time; wrapper removal only affects files bearing
|
||||
@@ -18,7 +19,7 @@
|
||||
#
|
||||
# EXAMPLE
|
||||
# __fish_config_sync_logging
|
||||
function __fish_config_sync_logging --description 'Sync C5 logging state: sentinel file and paru/yay wrappers'
|
||||
function __fish_config_sync_logging --description 'Sync C5 logging state: sentinel file, paru/yay wrappers, and tmux pipe-pane'
|
||||
set -l config_home $XDG_CONFIG_HOME
|
||||
if test -z "$config_home"
|
||||
set config_home "$HOME/.config"
|
||||
@@ -32,6 +33,9 @@ function __fish_config_sync_logging --description 'Sync C5 logging state: sentin
|
||||
# Logging enabled: remove sentinel and regenerate wrappers if binaries exist
|
||||
rm -f $sentinel
|
||||
|
||||
# Restart tmux pipe-pane for the current pane if inside tmux
|
||||
_tmux_pipe_log
|
||||
|
||||
if test -x /usr/bin/paru
|
||||
mkdir -p (dirname $paru_wrapper)
|
||||
printf '%s\n' \
|
||||
@@ -95,5 +99,10 @@ function __fish_config_sync_logging --description 'Sync C5 logging state: sentin
|
||||
and grep -q "# yay-wrapper-version:" $yay_wrapper 2>/dev/null
|
||||
rm -f $yay_wrapper
|
||||
end
|
||||
|
||||
# Stop tmux pipe-pane for the current pane if inside tmux
|
||||
if set -q TMUX
|
||||
tmux pipe-pane 2>/dev/null
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# _prune_terminal_logs <prefix>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Removes the oldest "<prefix>_*.log" files in SCROLLBACK_HISTORY_DIR
|
||||
# (default ~/.terminal_history) so the total count for that prefix stays
|
||||
# within SCROLLBACK_HISTORY_MAX_FILES (default 100). Files are sorted by
|
||||
# modification time, so the most recently written logs are kept — actively
|
||||
# appended logs (e.g. a tmux pipe-pane stream) survive.
|
||||
#
|
||||
# Uses `command ls`/`command rm` to bypass the C1 shadows: the bare `ls` is
|
||||
# the eza wrapper, which injects OSC-8 hyperlink escapes into paths, and the
|
||||
# bare `rm` is the trash wrapper. The glob is expanded via `set` first so a
|
||||
# no-match (empty dir / first run) yields an empty list instead of a hard
|
||||
# "No matches for wildcard" error.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# prefix Log-name prefix to prune (e.g. tmux, zellij)
|
||||
#
|
||||
# RETURNS
|
||||
# 0 Always
|
||||
#
|
||||
# EXAMPLE
|
||||
# _prune_terminal_logs tmux
|
||||
function _prune_terminal_logs --argument-names prefix --description 'Prune oldest <prefix>_*.log files beyond the configured max'
|
||||
set -q prefix[1]; or return 0
|
||||
set -l log_dir (set -q SCROLLBACK_HISTORY_DIR; and echo $SCROLLBACK_HISTORY_DIR; or echo "$HOME/.terminal_history")
|
||||
set -l max_files (set -q SCROLLBACK_HISTORY_MAX_FILES; and echo $SCROLLBACK_HISTORY_MAX_FILES; or echo 100)
|
||||
test -d $log_dir; or return 0
|
||||
|
||||
# Glob via `set` so a no-match yields an empty list rather than an error.
|
||||
set -l logs $log_dir/$prefix"_"*.log
|
||||
set -q logs[1]; or return 0
|
||||
|
||||
# Re-sort by mtime, oldest first. Pass explicit files (no glob) so ls can
|
||||
# never fail on no-match; `command ls` avoids the eza OSC-8 corruption.
|
||||
set logs (command ls -1tr $logs 2>/dev/null)
|
||||
if test (count $logs) -gt $max_files
|
||||
set -l excess (math (count $logs) - $max_files)
|
||||
command rm -f $logs[1..$excess]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# _tmux_pipe_log
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Starts tmux pipe-pane capture for the current pane, streaming its output
|
||||
# to a timestamped log in SCROLLBACK_HISTORY_DIR (default ~/.terminal_history).
|
||||
# Old tmux_*.log files are pruned via _prune_terminal_logs to stay within
|
||||
# SCROLLBACK_HISTORY_MAX_FILES. No-op when not inside a tmux session ($TMUX
|
||||
# unset) or tmux is missing. Shared by conf.d/tmux-logging.fish (shell
|
||||
# startup) and __fish_config_sync_logging (C5 re-enable) so both stay in sync.
|
||||
#
|
||||
# RETURNS
|
||||
# 0 Always
|
||||
#
|
||||
# EXAMPLE
|
||||
# _tmux_pipe_log
|
||||
function _tmux_pipe_log --description 'Start tmux pipe-pane capture for the current pane, with pruning'
|
||||
set -q TMUX; or return 0
|
||||
type -q tmux; or return 0
|
||||
|
||||
set -l log_dir (set -q SCROLLBACK_HISTORY_DIR; and echo $SCROLLBACK_HISTORY_DIR; or echo "$HOME/.terminal_history")
|
||||
set -l pane_id (tmux display-message -p '#{session_name}-w#{window_index}-p#{pane_index}' 2>/dev/null)
|
||||
set -l timestamp (date "+%Y-%m-%d_%H-%M-%S")
|
||||
set -l log_file "$log_dir/tmux_"$pane_id"_"$timestamp".log"
|
||||
|
||||
mkdir -p $log_dir
|
||||
_prune_terminal_logs tmux
|
||||
|
||||
tmux pipe-pane "cat >> $log_file" 2>/dev/null
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# _zellij_dump_log
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Captures the current Zellij pane's scrollback to a timestamped log in
|
||||
# SCROLLBACK_HISTORY_DIR (default ~/.terminal_history). Zellij has no live
|
||||
# output-streaming facility like tmux's pipe-pane, so this performs a one-shot
|
||||
# `zellij action dump-screen --full` — intended to run on shell exit. Old
|
||||
# zellij_*.log files are pruned via _prune_terminal_logs to stay within
|
||||
# SCROLLBACK_HISTORY_MAX_FILES.
|
||||
#
|
||||
# The C5 logging guard (__fish_config_op_logging) is evaluated here, at call
|
||||
# time, so toggling logging takes effect on the next exit without a restart.
|
||||
# No-op when logging is disabled, not inside Zellij ($ZELLIJ unset), or the
|
||||
# zellij binary is missing.
|
||||
#
|
||||
# RETURNS
|
||||
# 0 Always
|
||||
#
|
||||
# EXAMPLE
|
||||
# _zellij_dump_log
|
||||
function _zellij_dump_log --description 'Dump the current Zellij pane scrollback to a log file, with pruning'
|
||||
__fish_config_op_enabled __fish_config_op_logging; or return 0
|
||||
set -q ZELLIJ; or return 0
|
||||
type -q zellij; or return 0
|
||||
|
||||
set -l log_dir (set -q SCROLLBACK_HISTORY_DIR; and echo $SCROLLBACK_HISTORY_DIR; or echo "$HOME/.terminal_history")
|
||||
set -l session (set -q ZELLIJ_SESSION_NAME; and echo $ZELLIJ_SESSION_NAME; or echo unknown)
|
||||
set -l pane_id (set -q ZELLIJ_PANE_ID; and echo $ZELLIJ_PANE_ID; or echo unknown)
|
||||
set -l timestamp (date "+%Y-%m-%d_%H-%M-%S")
|
||||
set -l log_file "$log_dir/zellij_"$session"-p"$pane_id"_"$timestamp".log"
|
||||
|
||||
mkdir -p $log_dir
|
||||
zellij action dump-screen --full "$log_file" 2>/dev/null
|
||||
|
||||
_prune_terminal_logs zellij
|
||||
end
|
||||
Reference in New Issue
Block a user