Context-aware terminal colorizer with magnitude spectrum visualization for network device output.
๐ธ View Screenshots: Visit the GitHub repository to see the dual spectrum coloring system in action!
Juniper interface output showing dual spectrum: neutral colors for traffic stats, warm colors for errors
JunOS configuration diff with syntax highlighting
RainbowTerm is a high-performance Rust-based terminal colorizer designed for network engineers. It provides intelligent syntax highlighting for network device output with advanced features like dual magnitude spectrum visualization (neutral vs. error-based) and context-aware coloring.
RainbowTerm uses context-aware coloring with two distinct spectrum systems:
For traffic counters, packet counts, and other metrics where bigger isn't bad:
Input bytes: 1298458 # Green โ Blue โ Purple (millions)
Output packets: 1234567890 # Orange โ Yellow โ Green โ Blue โ Purple (billions)
Input bytes: 2342779625172 # Orange โ Yellow โ Green โ Blue โ Purple (trillions)
Input bytes: 0 # Gray (idle)
- Rightmost 3 digits: Purple (base color)
- Next group (thousands): Blue โ Purple
- Millions: Green โ Blue โ Purple
- Billions: Yellow โ Green โ Blue โ Purple
- Trillions+: Orange โ Yellow โ Green โ Blue โ Purple
- Zero: Gray (idle/no traffic)
For errors, drops, and problems where bigger IS bad:
Errors: 724 # Yellow (minor - hundreds)
Drops: 1750520 # Orange โ Yellow (moderate - millions)
Errors: 1234567890 # Magenta โ Dark Red โ Crimson โ Yellow (billions)
Errors: 0 # Green (healthy - no errors!)
- Rightmost 3 digits: Yellow (base color)
- Thousands: Orange โ Yellow
- Ten thousands: Red โ Orange โ Yellow
- Hundred thousands: Dark Red โ Red โ Orange โ Yellow
- Millions: Crimson โ Dark Red โ Red โ Orange โ Yellow
- Billions+: Magenta/Violet โ Crimson โ Dark Red โ Red โ Orange โ Yellow
- Zero errors: Green (healthy state!)
Same number, different meaning, different color - The philosophy behind context-aware coloring.
RainbowTerm automatically detects the correct profile based on:
- Banner/MOTD content (e.g., "Versa FlexVNF", "JUNOS", "Cisco IOS")
- CLI prompts (e.g.,
user@hostname>,hostname#) - Interface naming patterns (e.g.,
ge-0/0/0,vni-0/2,GigabitEthernet0/1) - Configurable hostname prefixes (e.g.,
jr,vr,cs)
No flags needed - just pipe and go:
ssh router | rt # Auto-detects from banner/prompt
cat output.txt | rt # Auto-detects from content- โ Interface names by speed (ge, xe, et, mge, vcp, ae)
- โ BGP states (Established/Idle)
- โ OSPF states (Full/Down)
- โ STP states (FWD/BLK) and roles (DESG/DIS)
- โ STP port costs with quality indicators
- โ Physical link status (Up/Down)
- โ Duplex modes (Full-duplex/Half-duplex)
- โ Log severity levels (Critical/Warning/Info)
- โ Active alarms and defects
- โ Routing table markers (* and >)
- โ Configuration diff output (+/-/!)
- โ Interface types (eth, vni, tvi, ptvi, dtvi)
- โ VRF coloring (Control-VR, LAN-VR, Transport-VR)
- โ WAN link identifiers (WAN1, WAN2, WAN3)
- โ SD-WAN session details (natted, sdwan, offload)
- โ SLA metrics (delay, jitter, loss)
- โ LLDP neighbor information
- โ Operational/configuration mode prompts
- โ Branch/site naming patterns
- โ Interface names (GigabitEthernet, TenGigabitEthernet, etc.)
- โ BGP/OSPF states
- โ STP states and roles
- โ VTP/VLAN information
- โ IPv4 addresses
- โ MAC addresses (colon and dot formats)
- โ Serial numbers and model numbers
- โ Status keywords (up/down, error/warning)
- โ Packet/byte counters with magnitude spectrum
Multi-line state machine tracks context across output:
- Interface state (up/down) affects duplex coloring
- Half-duplex on UP link = red warning
- Half-duplex on DOWN link = gray (irrelevant)
- Group-based coloring: Different colors for each regex capture group
- Priority system: Fine-grained control over pattern precedence
- Profile inheritance: Extend base patterns with vendor-specific rules
- TOML configuration: Human-readable, version-controllable config
- ChromaTerm converter: Migrate existing YAML configs to TOML
Choose your platform:
Step 1: Install Rust
Using rustup (recommended):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shAfter installation, restart your terminal or run source ~/.cargo/env.
Alternative: Homebrew (macOS) or system package manager (Linux)
Homebrew (macOS):
brew install rustImportant: Homebrew doesn't add Cargo to your PATH automatically. Run:
# For zsh (default on macOS) echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc # For bash echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
Linux package managers:
# Debian/Ubuntu
sudo apt install cargo
# Fedora
sudo dnf install cargo
# Arch
sudo pacman -S rustNote: System packages may be older versions. If you encounter build issues, use rustup instead.
Step 2: Install RainbowTerm
cargo install rainbowtermStep 3: Verify installation
rt --versionYou're done! Config is created automatically on first run.
Windows requires additional setup for compiling Rust programs and for interactive SSH sessions.
Step 1: Install Git for Windows
Required for Git Bash, which supports interactive SSH sessions (PowerShell does not).
winget install Git.GitOr download from git-scm.com.
Step 2: Install Visual Studio Build Tools
Required for compiling Rust programs.
winget install Microsoft.VisualStudio.2022.BuildTools --override "--wait --passive --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"Or download from Visual Studio Build Tools and select "Desktop development with C++".
Step 3: Install Rust
winget install Rustlang.RustupOr download from rustup.rs.
Step 4: Restart your terminal, then verify Rust is installed:
cargo --versionStep 5: Install RainbowTerm
cargo install rainbowtermStep 6: Add Cargo to your PATH (if rt command is not found):
# Check if rt is installed
ls $env:USERPROFILE\.cargo\bin\rt.exe
# Add to PATH permanently
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";$env:USERPROFILE\.cargo\bin", "User")Restart your terminal after updating the PATH.
Step 7: Add Git Bash to Windows Terminal
Note: If you installed Git via the GUI installer (not winget), Git Bash may already appear in Windows Terminal. Skip to step 8 if it's there.
Run these commands in PowerShell to add Git Bash as a profile:
$settingsPath = "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json"
$settings = Get-Content $settingsPath | ConvertFrom-Json
$gitBashProfile = @{
name = "Git Bash"
commandline = "C:\\Program Files\\Git\\bin\\bash.exe"
icon = "C:\\Program Files\\Git\\mingw64\\share\\git\\git-for-windows.ico"
startingDirectory = "%USERPROFILE%"
guid = "{" + [guid]::NewGuid().ToString() + "}"
}
$settings.profiles.list = @($settings.profiles.list) + $gitBashProfile
$settings | ConvertTo-Json -Depth 100 | Set-Content $settingsPathStep 8: Use Git Bash for interactive SSH
- Open Windows Terminal
- Click the dropdown (โผ) next to the tab and select Git Bash
- Run:
ssh <remote-host> | rt
Example:
ssh admin@192.168.1.1 | rt# Clone the repository
git clone https://github.com/Legendberg/rainbowterm.git
cd rainbowterm
# Build and install
cargo install --path .- Rust 1.70+ (for building)
- macOS, Linux, WSL2, or Windows (with Git Bash for interactive SSH)
Setup automatic SSH colorization - no | rt needed:
rt init # Preview what will be installed
rt init --install # Install to your shell configThis adds a shell function that automatically pipes SSH through RainbowTerm:
ssh router # Automatically colorized!# Pipe any command through RainbowTerm (auto-detects profile)
ssh router | rt
# Use with specific profile (skip auto-detection)
cat output.txt | rt --profile juniper
# Disable auto-detection, use default profile from config
cat output.txt | rt --no-auto-detect
# Disable context awareness
tail -f /var/log/messages | rt --no-context
# Preserve ANSI codes from input (default: stripped for cleaner matching)
cat pre-colored.txt | rt --preserve-ansi
# List available profiles
rt --list-profilesFor interactive SSH sessions on Windows, use Git Bash (see Windows installation above). Non-interactive commands work in PowerShell:
ssh router "show version" | rtComprehensive test files are included for each vendor:
# Test Juniper profile
cat tests/networking/juniper/common/sample.txt | rt --profile juniper
# Test Cisco profile
cat tests/networking/cisco/ios/sample.txt | rt --profile cisco
# Run integration tests
cargo test --test integration_tests
# Run all tests (unit + integration)
cargo testTest files include realistic output from various commands: interfaces, BGP, OSPF, STP, logging, and more.
Same output, different colors based on context:
Physical interface: ge-0/0/0, Enabled, Physical link is Up
Traffic statistics (NEUTRAL SPECTRUM - cool colors):
Input bytes : 1298458 0 bps
Output bytes : 909181029 32112 bps
Input errors (ERROR SPECTRUM - warm colors, same magnitudes!):
Errors: 1298458, Drops: 909181029, Framing errors: 0
Queue counters: Queued packets Transmitted packets Dropped packets
0 1644910 1644910 724
1 14362000 14362000 1750520
^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^^
neutral/cool neutral/cool error/warm
Notice how 1298458 appears twice with different colors - once as traffic (neutral spectrum) and once as errors (error spectrum). Context determines meaning!
On first run, RainbowTerm creates a default config file at the OS-standard location:
| OS | Config Path |
|---|---|
| Linux | ~/.config/rainbowterm/config.toml |
| macOS | ~/Library/Application Support/rainbowterm/config.toml |
| Windows | C:\Users\<user>\AppData\Roaming\rainbowterm\config.toml |
Use -c <path> to specify a custom config file.
Tip: The config file includes a version comment at line 3. Check it to verify you're using the latest patterns:
head -3 ~/.config/rainbowterm/config.toml # Linux
head -3 ~/Library/Application\ Support/rainbowterm/config.toml # macOS# Set default profile (used when auto-detection fails)
default_profile = "juniper"
# Customize hostname prefixes for your organization
# These help auto-detection identify devices by hostname
[hostname_prefixes]
juniper = ["jr", "js", "mx", "ex"] # Your Juniper naming convention
versa = ["vr", "sdwan"] # Your Versa naming convention
cisco = ["cs", "sw", "rtr", "cat"] # Your Cisco naming convention
# Add custom colors to palette
[palette]
my-blue = "#0080ff"
# Create custom patterns
[[profiles.juniper.patterns]]
description = "Custom pattern"
regex = '''my-regex-here'''
color = "my-blue"
priority = 100- base: Universal patterns (IPs, MACs, dates, status)
- juniper: JunOS-specific patterns (inherits from base)
- versa: Versa SD-WAN patterns (inherits from base)
- cisco: Cisco IOS/NX-OS patterns (inherits from base)
- ๐ข Green: Good/Active (FWD, Established, Up)
- ๐ก Orange: Warning (BLK, threshold)
- ๐ด Red: Critical (Down, Error, Idle)
- โช Gray: Inactive (DIS, zero counters)
- ๐ต Cyan: Info (DESG, configuration)
- ๐ฃ Purple: Virtual interfaces (ae, irb, lo)
- ๐ Bright green: 100G+ (et, fte)
- ๐ฟ Green-lime: 10G (xe)
- ๐ Cyan: 2.5G (mge)
- ๐งก Orange: 1G (ge)
- ๐ Amber: <1G (fe)
src/
โโโ main.rs # CLI interface, stdin processing, output rendering
โโโ config.rs # TOML parsing, profile management, shared types
โโโ matching.rs # Pattern compilation and application
โโโ context.rs # State machine for context-aware rules
โโโ convert.rs # ChromaTerm YAML converter (optional feature)
- Patterns compiled at startup with
regexcrate - O(n) processing with efficient overlap detection
- Chunk-based reading (8192 bytes) for SSH compatibility
- ANSI escape sequence preservation
- No performance degradation on large outputs
Higher priority = applied first:
- 200+: Critical keywords (error, warning, failure)
- 168: Error counter zeros (green = healthy)
- 166-160: Error spectrum (trillions down to hundreds)
- 155-150: Dropped packets column (error spectrum)
- 155-145: Neutral spectrum (trillions down to hundreds) and queue counters
- 100: Interface names and service patterns
- 90: Protocol states (BGP, OSPF, STP)
- 85: Routing markers and roles
- 80: Speed indicators
- 10: Generic up/down keywords
Contributions are welcome! Please feel free to submit issues or pull requests on GitHub.
- Add Cisco IOS/IOS-XE/NX-OS profile
- Comprehensive test files for Juniper, Cisco, Arista
- Dual spectrum system (neutral vs. error-based coloring)
- Context-aware coloring philosophy
- Auto-create config on first run
- Public release to crates.io (v0.1.0)
- v0.2.0 release with dual spectrum and screenshots
- v0.2.3 improved documentation for both platforms
- Unit test suite (15 tests for config and matching)
- Integration test suite (Juniper, Cisco profiles)
- Versa SD-WAN profile (v0.2.12)
- Automatic profile detection from content/banners
- User-configurable hostname prefixes
- Shell integration (
rt init) for automatic SSH colorization - Shell completions (
rt completions <shell>) - Complete Arista EOS profile implementation
- Performance benchmarks
- Additional vendor profiles (Palo Alto, F5, etc.)
- PTY wrap mode for serial console access (see Future Development)
A PTY (pseudo-terminal) wrap mode was prototyped to support serial console access (e.g., rt screen /dev/ttyUSB0). This would allow colorizing output from direct console connections where pipe mode isn't possible.
Why it was paused:
The implementation revealed fundamental limitations with the PTY approach for serial/console access:
-
Inconsistent colorization - Serial data arrives in unpredictable chunks due to baud rate timing. Pattern matching requires complete lines, but data often splits mid-line, causing the same command to colorize differently on each run.
-
Tab completion broken - The PTY layer intercepts terminal control sequences, preventing tab completion from working on the remote device.
-
Space pagination broken - Similar issue: pressing space for "more" pagination doesn't work properly through the PTY wrapper.
-
Password visibility - Unlike SSH pipe mode (where the SSH client handles password prompts directly), PTY wrap mode would display typed passwords on screen, creating security concerns for screen sharing, terminal logging, and shoulder surfing scenarios.
Current recommendation: Use pipe mode (ssh router | rt) for the best experience. For serial console access, colorization is not currently supported.
What we tried:
The prototype used portable-pty to spawn commands in a pseudo-terminal with two threads: one passing stdin to the PTY, another reading PTY output, colorizing it, and writing to stdout. Several buffering strategies were attempted:
-
Direct passthrough - Process data immediately as it arrives. Result: Highly inconsistent colorization because serial data splits unpredictably.
-
Line buffering - Only process complete lines (wait for newline). Result: Prompts (which don't end with newlines) would get stuck and not display until the next line arrived.
-
Accumulation delay - Add 5-20ms delay after each read to let more data accumulate before processing. Result: Improved consistency but still unreliable; also added noticeable latency.
-
Hybrid approach - Buffer complete lines, flush incomplete data (like prompts) after accumulation. Result: Still inconsistent due to the fundamental timing unpredictability of serial data.
The core issue is that serial/console data doesn't arrive in logical units - it arrives based on baud rate timing, which means a single line like x1oz@BuffLab> might arrive as x1o, then z@Buff, then Lab> in separate reads. No amount of buffering can reliably reassemble this without either blocking indefinitely or accepting inconsistent results.
Future possibility: This could be revisited if:
- A reliable line-buffering solution is found for serial timing issues
- Terminal control sequence passthrough can be implemented
- A secure password handling mechanism is developed
The prototype code demonstrated that colorization does work when timing aligns, so the core concept is sound - it's the edge cases and user experience that need solving.
Dual licensed under MIT OR Apache-2.0
- Inspired by ChromaTerm
- Built with Rust
- Developed with assistance from Claude Code
Note: RainbowTerm is designed for network engineers working with CLI output from routers, switches, and firewalls. It significantly improves readability and reduces eye strain during troubleshooting sessions.