Terminal-agnostic, PTY-based, regex-driven output colorizer in Rust.
tayf wraps your shell inside a pseudo-terminal and applies a small set of
regular expressions to the byte stream, so common patterns (IP addresses,
log levels, HTTP status codes, durations, FQDNs, file extensions, UUIDs,
URLs, emails, timestamps, file permissions, MAC addresses) appear colorized —
in any terminal emulator, with any shell, with no aliases or per-command
wrapping.
curl -fsSL https://raw.githubusercontent.com/beraartuc/tayf/main/install.sh | shDetects your OS/architecture, downloads the matching signed binary from the
latest release, verifies its
SHA256 (and its Sigstore provenance if an authenticated gh CLI is present),
and installs it to ~/.local/bin — no sudo. Linux binaries are static (musl),
so they run on any distribution (glibc or musl, old or new) and on x86-64 or
ARM64.
Prefer to read it first? curl -fsSL https://raw.githubusercontent.com/beraartuc/tayf/main/install.sh | less
Knobs:
TAYF_INSTALL_DIR=/usr/local/bin— install elsewhere.TAYF_VERSION=v0.11.0— pin a specific release (also the escape hatch if the GitHub API rate-limits the latest-version lookup).
brew install beraartuc/tayf/tayfHomebrew places the binary in /opt/homebrew/bin (Apple Silicon) or
/usr/local/bin (Intel). Both are typically on PATH after a standard
Homebrew install.
cargo install tayfInstalls to ~/.cargo/bin. Ensure that directory is on your PATH
(echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.zshrc).
Minimum supported Rust version: 1.88.
Download the binary for your platform from the GitHub releases page, then verify and install:
# Verify provenance (GitHub-signed SLSA attestation)
gh attestation verify ./tayf --repo beraartuc/tayf
# Verify checksum
shasum -a 256 -c SHA256SUMS
# Install
chmod +x ./tayf
sudo mv ./tayf /usr/local/bin/tayf# Replace your shell for one session:
tayf
# Or pass a specific shell:
tayf --shell /bin/bash
# Use a light-background theme:
tayf --theme light
# Use the classic named-ANSI palette (lighter SGR output):
tayf --theme classictayf automatically discovers your shell from $SHELL, falling back to
/etc/passwd and then /bin/sh. Override with --shell /path/to/shell or
request a login shell with --login.
When stdout is not a TTY (e.g. tayf | tee log.txt), colorization is
disabled automatically.
tayf ships a full-screen TUI for browsing and editing your colorization
rules live — no need to hand-edit TOML:
tayf configFour tabs (switch with Tab / Shift+Tab or the number keys 1–4):
- Patterns — toggle, override, reset, or delete any of the twelve built-in rules, add your own regex patterns, and pick colors interactively (ANSI 16, 256-color, or 24-bit hex, plus bold / italic / underline).
- Themes — browse and select a built-in or on-disk theme.
- Profiles — browse the built-in and on-disk profiles.
- Status — the resolved config state and recent hot-reload events.
A live preview strip shows how your changes colorize a sample line as you
edit. Ctrl+S saves back to config.toml (atomic write with a timestamped
backup; if the file changed on disk in the meantime, a three-way merge view
lets you resolve each conflict). Press ? or F1 for the full keybinding
cheat-sheet, and Ctrl+C or q to quit.
Two non-interactive helpers share the same subcommand:
tayf config dump # print the built-in pattern / theme / profile catalog as TOML
tayf config status # print the resolved config + recent reload eventsSet your terminal's shell/command to tayf so every new window uses it:
- iTerm2: Preferences → Profiles → General → Command →
select "Custom Shell" →
/opt/homebrew/bin/tayf - Terminal.app: Preferences → Profiles → Shell → "Run command" →
tayf - Alacritty (
~/.config/alacritty/alacritty.toml):[shell] program = "/opt/homebrew/bin/tayf"
- kitty (
~/.config/kitty/kitty.conf):shell /opt/homebrew/bin/tayf - GNOME Terminal / Konsole: Edit the profile and set the custom command to
tayf(or its full path).
Add this guard to ~/.zshrc or ~/.bashrc:
# ~/.zshrc or ~/.bashrc
[[ $- == *i* && -t 1 && -z $TAYF_SESSION ]] && exec tayfHow it works: tayf sets TAYF_SESSION=1 in the child shell it spawns. The
guard checks for an interactive shell (*i*), a TTY on stdout (-t 1), and
the absence of TAYF_SESSION — preventing an infinite re-exec loop.
tayf ships with twelve built-in patterns, listed in priority order
(most-specific first; first match wins). The default dark theme uses a
curated 24-bit "Neon" palette:
| Name | Color | Example |
|---|---|---|
| permission | dim gray (per-group colors) | -rw-r--r--, drwxr-xr-x |
| timestamp | dim gray (date=amber, time=green, tz=violet) | 2026-05-22T10:30:00Z, [22/May/2026:10:30:00 +0000] |
| uuid | magenta | 550e8400-e29b-41d4-a716-446655440000 |
| url | blue, underlined | https://example.com/path, ssh://host |
| lime green | user@example.com |
|
| ipv4 | azure (not bold) | 192.168.1.1 |
| ipv6 | indigo | fe80::1, 2001:db8::1 |
| mac | teal | aa:bb:cc:dd:ee:ff |
| log_level | hot-coral, bold | ERROR, WARN, INFO, DEBUG, TRACE, ... |
| filename | orange | claude.md, archive.tar.gz, config.json |
| fqdn | violet | api.example.com |
| duration | amber | 20.291 ms, 1.5 ms, 100ms, 2d3h |
Pattern notes:
- permission uses per-capture-group styling: type character (blue), owner bits (red), group bits (amber), other bits (green). The overall style is dim gray for the padding spaces.
- timestamp uses per-capture-group styling: date (amber), separator (dim), time (green), timezone (violet). Spans ISO-8601, syslog, Apache/nginx, and RFC 2822 formats.
- uuid matches the canonical 8-4-4-4-12 hex form.
- url matches
https?://,ssh://,ftp://, andgit@host:pathURLs. The entire span is rendered as a single underlined blue span. - email matches an RFC 5322 simplified shape.
- log_level is the one bold "alert" affordance in the Neon palette — hot-coral and bold for immediate visibility.
- ipv4 is azure (a medium blue), without bold (bold was removed in v0.9.1 as part of the Neon palette tuning).
- The filename rule covers a curated catalog of common extensions —
archives (
zip,tar.gz,7z, ...), source code (rs,py,ts,go, ...), configuration (json,yaml,toml, ...), documents (pdf,md, ...), media, and binary formats. Seesrc/rules.rsfor the full list.
tayf ships three built-in themes:
dark— the default Neon 24-bit palette described above. Best for dark-background terminals.light— hand-authored adaptation for light-background terminals, where the bright ANSI family renders washed out.classic— the pre-v0.9 named-ANSI palette, terminal-adaptive. Emits shorter SGR sequences (a byte-count advantage on heavily-matched streams compared to the 24-bit Neon palette).
24-bit palette note: The default dark (Neon) theme emits 24-bit
(\x1b[38;2;R;G;Bm) SGR sequences. On streams with many matches this
produces slightly more bytes than the classic named-ANSI palette. Use
--theme classic if byte overhead matters (e.g. very high-throughput log
tailing on slow connections).
Pick a theme from the CLI:
tayf --theme light
tayf --theme classicOr set a default in your config:
# ~/.config/tayf/config.toml
[general]
theme = "light"CLI --theme overrides the config field. Your own [[rules]] blocks still
win over the theme: theme styles are pre-loaded on top of built-in defaults,
and your user rules override either layer.
Unknown theme names exit with code 64 (EX_USAGE) and list the known themes
on stderr.
The theme selection is fixed at startup: changing [general] theme in your
config does not take effect on hot reload, so restart tayf to switch
themes. Your [[rules]] edits still hot-reload as usual.
In addition to the three built-in presets, you can drop your own theme files
into ~/.config/tayf/themes/<name>.toml (or
$XDG_CONFIG_HOME/tayf/themes/<name>.toml) and reference them by name:
# ~/.config/tayf/themes/my-dark.toml
[[rules]]
name = "log_level"
style = { fg = "cyan", bold = true }
[[rules]]
name = "ipv4"
style = { fg = "#ffaa00" }tayf --theme my-darkA disk theme follows the same schema as a built-in preset — it is a list of
[[rules]] blocks that override the style of an existing built-in pattern.
The following constraints apply:
- The file name must not match a built-in (
dark,light,classic); use a different name (my-dark,solarized-dark) and reference it with--theme <name>. The check is case-insensitive. - Theme rules must name an existing built-in pattern; they cannot introduce
new patterns. Use the user config (
config.toml) to add patterns. - Theme rules must not set
patternorenabled = false. - The theme file must not carry a
[general]section.
Validation surfaces every violation in one pass — fix all of them in a single
editor cycle. Invalid theme files exit EX_USAGE (64).
When you have not pinned a theme via --theme <name> or [general] theme,
tayf tries to detect your terminal's background color at startup and apply the
matching preset:
COLORFGBGenv var (rxvt/urxvt; some xterm configs).- OSC 11 query against
/dev/ttywith a 100 ms timeout (xterm, iTerm2, kitty, Alacritty, foot, WezTerm, modern tmux withset -g allow-passthrough on). - Fallback:
dark.
Limitations. Modern terminals have dozens of light/dark variants —
Solarized, Nord, Gruvbox, Dracula, Tokyo Night, Catppuccin, and many more.
tayf's built-in dark and light presets give reasonable contrast for
"default-ish" terminals; they do not claim to match every specific palette.
This detection is a starting point, not an authoritative choice.
Opt-out. Pin a theme explicitly with --theme dark (or light,
classic) on the CLI, or set [general] theme = "dark" in your config.
Explicit choices always win over detection.
Custom palettes. For fine-grained matching to a specific terminal palette,
override rule styles in your config under [[rules]], or drop a full theme
file under ~/.config/tayf/themes/<name>.toml (see
Custom themes above).
Multiplexers. tmux is supported when passthrough is enabled — tmux ≤3.2
enables it by default; tmux ≥3.3 requires set -g allow-passthrough on. GNU
screen is not supported (the OSC 11 path is skipped and tayf falls back to
dark). Detection is also skipped when stdout is not a TTY, when --no-color
is set, or when TERM=dumb.
tayf reads an optional TOML config from
$XDG_CONFIG_HOME/tayf/config.toml (falling back to
~/.config/tayf/config.toml). Pass --config <path> to use a different
file. Without a config file, all twelve built-in rules described above are
active with their default styles.
# ~/.config/tayf/config.toml
# Override a built-in: change the log_level color to yellow (loses the
# built-in's bold attribute — style overrides REPLACE wholesale).
[[rules]]
name = "log_level"
style = { fg = "yellow", bold = true }
# Disable a built-in by name.
[[rules]]
name = "fqdn"
enabled = false
# Append a new custom rule.
[[rules]]
name = "kubernetes-pod"
pattern = '\b[a-z][a-z0-9-]+-[a-z0-9]{5}-[a-z0-9]{5}\b'
style = { fg = "magenta", italic = true }
[[rules]]
name = "git-sha"
pattern = '\b[0-9a-f]{7,40}\b'
style = { fg = "#888888" }- ANSI names:
"red","bright_cyan", etc. (case-insensitive). - 256-color palette:
"color(178)". - 24-bit hex:
"#ff8800"(six digits). - 24-bit functional:
"rgb(255, 136, 0)".
When the terminal cannot display a requested color depth (TERM=dumb,
no COLORTERM=truecolor, etc.) tayf automatically downgrades — Rgb values
collapse to the closest 256-indexed or ANSI 16 entry, attributes like bold
and italic are preserved.
style = { fg, bg, bold, italic, underline, dim }. Every field is optional,
but a rule whose style would produce no visible effect is rejected at load
time — use enabled = false to disable a rule instead.
The twelve names you can override or disable, in priority order:
permission, timestamp, uuid, url, email, ipv4, ipv6, mac,
log_level, filename, fqdn, duration.
Malformed configs exit with code 64 (EX_USAGE) and print a friendly
diagnostic to stderr that includes the file path and the offending line number
when available.
tayf watches your config file and reloads it whenever you save:
- Editing
~/.config/tayf/config.toml(or the file passed to--config) takes effect within ~250 ms — no restart, no shell respawn. pkill -HUP tayf(orkill -HUP <pid>) forces an immediate reload that bypasses the file-watcher debounce window. Useful from scripts.- If your edit produces invalid TOML or a bad regex,
tayfkeeps the previous rule set in effect and logs a warning to stderr (TAYF_LOG=warn, the default). Your terminal session is never disrupted by a typo in the config. - The child shell is never restarted.
tayfonly swaps its in-process rule set; the PTY, signal handlers, and the running shell are untouched.
If tayf was launched without a config file, the file watcher is not active
— but SIGHUP still works as a manual reload trigger. If you create a config
later, send SIGHUP to pick it up.
Note: SIGHUP is forwarded to the child process group in every
configuration, mirroring SIGINT / SIGTERM. If you have a script that
relies on tayf swallowing SIGHUP, install your own trap
(trap '' HUP) in the child shell.
If your input already contains ANSI color sequences (git log --color=always,
journalctl with colors, colored compiler output piped through tayf), you can
tell tayf to leave those lines alone:
# ~/.config/tayf/config.toml
[general]
respect_existing_colors = true # defaultWhen true, any line containing at least one SGR sequence (\e[…m) is
written to stdout byte-for-byte; tayf does not apply its own rules to it.
Lines without SGR are colored normally per your config.
Caveat: line-level only. A multi-line color block (e.g. git log with
\e[31m on one line and \e[0m later) is honored per SGR-bearing line, but
rules may still run on intermediate plain lines.
When you need tayf to get out of the way — a noisy program you are debugging, an output stream whose patterns conflict with tayf's built-ins, or a CI invocation where you want raw passthrough — set either the CLI flag or the env var. CLI takes precedence.
# One-shot:
TAYF_DISABLE=1 my-tool
# Conditional wrap in ~/.zshrc:
[[ -n "$TAYF_DISABLE" ]] || exec tayfTAYF_DISABLE accepts 1, true, or yes (case-insensitive). Any other
value (including unset) means bypass is off.
In bypass mode tayf still:
- Wraps the PTY (your child shell still has a controlling terminal).
- Forwards
SIGWINCH,SIGINT,SIGTERM,SIGHUPto the child process group (soCtrl-Cand tmux detach still work). - Protects the terminal via its raw-mode RAII guard (Ctrl-C / panic cleanup is still safe).
Bypass mode does NOT:
- Match patterns or inject SGR.
- Detect the terminal background (
bg_detectis skipped — zero startup latency). - Spawn the file watcher or reload orchestrator threads.
- Read your config file (so
--config <path>is silently ignored under--bypass).
Note: TAYF_DISABLE is different from TAYF_DISABLE_BG_DETECT
(test-only OSC 11 bypass — do not use in production).
Skip the file watcher and the reload orchestrator. Config is still loaded at startup; only re-loading is off.
tayf --no-hot-reloadWith no config file present, --no-hot-reload is a no-op — no watcher would
have spawned anyway. SIGHUP is forwarded to the child process group
regardless of this flag.
When set, tayf writes a one-line dim banner to /dev/tty after each
successful hot reload:
tayf: config reloaded
[general]
show_reload_banner = trueDefault: false (opt-in). The banner is wrapped in cursor save/restore
(DECSC/DECRC) so most multi-line shell prompts (zsh ZLE, RPROMPT) keep
their visual cursor position. Exotic prompt frameworks (Powerlevel10k
transient prompt) may show small redraw artifacts; press Ctrl-L to clean up.
When you are inside vim / less / any program using the alt-screen, the
banner is drawn into the alt-screen buffer and vanishes when the program
exits.
Banner is suppressed on reload failures — those still surface via the
existing TAYF_LOG=warn stderr line.
tayf detects when a program enters a full-screen or interactive mode and
gets out of the way. Specifically, when a program activates any of:
- alt-screen (
\x1b[?1049hand legacy47/1047variants) - bracketed paste (
\x1b[?2004h) - mouse tracking (
\x1b[?1000h,1002,1003,1006)
…tayf switches to passthrough until the program clears all of those modes.
This covers vim, less, htop, neovim, Claude Code, lazygit, k9s, gum,
bubbletea-based tools, and anything else that follows standard terminal
conventions. Their output is never altered by tayf.
A few built-in patterns can mis-fire on inputs that are shape-identical to their real targets. tayf's regex engine is linear-time (no backtracking, no look-around — a deliberate ReDoS-safety choice), so it cannot use the surrounding context that would disambiguate these cases. They are rare in real terminal output, but if one bothers you, disable the built-in (recipe below):
filenameon dotted prose. Prose ending in a single-letter source extension —a.b.c,u.v.w— can be styled as a filename, because real files likemain.c/top.vare suffix-identical and there is no path/command context to tell them apart.fqdnon JWT / base64url tokens. A 3-segment dotted base64url token (e.g. a JWT,eyJhbGc.eyJzdWI.signature) matchesfqdn. Telling it from a real domain would need a 4000+ entry TLD allowlist.ipv4on 5+-segment versions. A bare 5-segment dotted version such as1.2.3.4.5styles its leading1.2.3.4as an IPv4 address.
Recipe — disable a noisy built-in. Add an enabled = false rule to your
config:
# ~/.config/tayf/config.toml
[[rules]]
name = "fqdn"
enabled = falseUse name = "filename" or name = "ipv4" to disable those instead.
Set TAYF_LOG=debug to send diagnostic logs to stderr.
TAYF_DISABLE_BG_DETECT=1 (or true/yes, case-insensitive) is a
test-only escape hatch that skips the startup background-color detection
and assumes a dark theme. Use it in CI environments where /dev/tty is a
pseudo-terminal slave that cannot respond to OSC 11 queries. For production
use, prefer --theme dark / --theme light or [general] theme = "..."
in your config.
tayf sits in the I/O path of every command in your shell. Its built-in
patterns are hand-tuned to be linear-time (no catastrophic backtracking), its
line buffer is hard-capped at 64 KB, and its only ANSI emission is through a
single audited SGR sequence. See SECURITY.md for the
vulnerability reporting policy and the full threat model.
See benches/throughput.rs and
benches/BASELINE.md for current numbers.
Target: <20% overhead vs native cat (spec §7).
Reproduce locally with:
cargo bench --bench throughputDual-licensed under either of:
at your option.
ARCHITECTURE.md— internal design and module mapCONTRIBUTING.md— how to contributeSECURITY.md— vulnerability reporting and threat model