Widget Protocol Reference
February 8, 2026 · View on GitHub
Widgets are executable scripts that output structured text to stdout. ServerHub parses this output to render dashboard widgets.
Protocol Elements
title: <text>
Sets the widget title displayed in the header.
title: CPU Usage
refresh: <seconds>
Suggested refresh interval in seconds. Note: The config file refresh value takes precedence over this.
refresh: 5
row: <content>
A display row. Supports inline elements and Spectre.Console markup.
row: Hello World
row: [status:ok] All systems operational
row: [progress:75:inline]
row: [grey70]Muted text[/]
action: [flags] <Label>:<command>
Defines an interactive action that users can execute from the expanded widget view.
Basic Syntax:
action: Label:command
action: [flags] Label:command
Flags (comma-separated in brackets):
| Flag | Description |
|---|---|
danger | Shows warning indicator, requires confirmation |
sudo | Executes with elevated privileges (prompts for password if needed) |
refresh | Refreshes the widget after successful execution |
timeout=N | Custom timeout in seconds (default: 60) |
Timeout Values:
timeout=N- Command times out after N secondstimeout=0- No timeout (runs indefinitely until terminated)- No flag - Uses default 60 second timeout
Examples:
# Simple action
action: View logs:journalctl -n 50
# Action with flags
action: [danger,refresh] Restart nginx:systemctl restart nginx
action: [sudo] Update packages:apt update
action: [sudo,danger,refresh] Reboot:reboot
# Actions with custom timeout
action: [sudo,timeout=120] Update cache:apt update
action: [sudo,danger,timeout=600] Full upgrade:apt full-upgrade -y
action: [timeout=0] View live log:journalctl -f -n 100
UI Behavior:
- Actions appear as buttons in the expanded widget view
dangeractions show a warning banner before executionsudoactions show a password prompt if credentials aren't cachedtimeout=0actions show a "no timeout limit" warning- Progress bar shows elapsed time vs timeout (or pulsing animation for infinite)
Inline Elements
Status Indicator
Displays a colored status icon before the row content.
[status:STATE]
| State | Color | Use Case |
|---|---|---|
ok | Green | Normal operation |
info | Blue | Informational |
warn | Yellow | Warning condition |
error | Red | Error/critical state |
Example:
row: [status:ok] Service running
row: [status:warn] High memory usage
row: [status:error] Disk full
Progress Bar
Displays a visual progress bar with optional gradient coloring.
[progress:VALUE]
[progress:VALUE:GRADIENT]
[progress:VALUE:GRADIENT:STYLE]
[progress:VALUE:STYLE]
- VALUE: Integer 0-100
- GRADIENT: Optional gradient name or custom gradient (see Gradients section)
- STYLE:
inline(default) orchart
Default Behavior (no gradient specified):
- Green for 0-69%
- Yellow for 70-89%
- Red for 90-100%
Predefined Gradients:
cool- Blue to cyan (temperature/load themes)warm- Yellow to orange to red (heat/pressure themes)spectrum- Blue to green to yellow to red (full range)grayscale- Grey11 to grey100 (monochrome)
Custom Gradients:
- Two colors:
blue→red - Multiple colors:
blue→green→yellow→red
Example:
row: [progress:75] # Default threshold colors
row: [progress:45:inline] # Default with explicit style
row: [progress:90:warm] # Warm gradient
row: [progress:75:cool:inline] # Cool gradient with style
row: [progress:60:blue→green→red] # Custom gradient
Sparkline
Displays an inline mini-graph using smooth Unicode block characters for compact trend visualization.
[sparkline:VALUES]
[sparkline:VALUES:COLOR]
[sparkline:VALUES:COLOR:WIDTH]
[sparkline:VALUES:GRADIENT]
[sparkline:VALUES:GRADIENT:WIDTH]
- VALUES: Comma-separated numbers (e.g.,
10,20,15,25,30) - COLOR: Optional Spectre.Console color (default:
grey70) - GRADIENT: Optional gradient name or custom gradient (see Gradients section)
- WIDTH: Optional character width (default: 30, pads with background if data points < width)
Character Set: Uses Unicode block characters (▁▂▃▄▅▆▇█) for smooth visualization
Predefined Gradients:
cool,warm,spectrum,grayscale
Custom Gradients:
blue→red,green→yellow→red, etc.
Example:
row: Load trend: [sparkline:45,48,52,55,50,53:green] # Solid color, default width (30)
row: Memory: [sparkline:60,62,65,70,68,72,75:warm] 75% # Warm gradient, default width
row: CPU: [sparkline:10,20,30,40,50:blue→red] # Custom gradient, default width
row: Traffic: [sparkline:5,10,15,20:green:15] # Narrow sparkline (15 chars)
Mini Progress Bar
Displays a compact inline progress indicator with optional gradient coloring.
[miniprogress:VALUE]
[miniprogress:VALUE:WIDTH]
[miniprogress:VALUE:WIDTH:GRADIENT]
- VALUE: Integer 0-100
- WIDTH: Character width 3-20 (default: 10)
- GRADIENT: Optional gradient name or custom gradient (see Gradients section)
Default Behavior (no gradient specified):
- Green for 0-69%
- Yellow for 70-89%
- Red for 90-100%
Predefined Gradients:
cool,warm,spectrum,grayscale
Custom Gradients:
blue→red,green→yellow→red, etc.
Example:
row: CPU: [miniprogress:75] 75% # Default threshold colors
row: RAM: [miniprogress:85:15] 85% # Default with custom width
row: Temp: [miniprogress:60:12:cool] 60°C # Cool gradient
row: Load: [miniprogress:90:10:warm] # Warm gradient
row: Disk: [miniprogress:45:15:blue→green→red] # Custom gradient
Multi-Column Table
Displays structured data in aligned columns. Table directives are used without the row: prefix.
[table:HEADER1|HEADER2|HEADER3]
[tablerow:VALUE1|VALUE2|VALUE3]
[tablerow:VALUE1|VALUE2|VALUE3]
- Headers: Pipe-separated column names
- Rows: Pipe-separated values (supports Spectre.Console markup)
Example:
[table:Service|Status|Memory]
[tablerow:nginx|[green]running[/]|45MB]
[tablerow:mysql|[green]running[/]|320MB]
[tablerow:redis|[yellow]warning[/]|89MB]
Horizontal Divider
Displays a full-width horizontal line to separate sections.
[divider]
[divider:CHARACTER]
[divider:CHARACTER:COLOR]
- CHARACTER: Single character (default:
─) - COLOR: Optional Spectre.Console color (default:
grey70)
Example:
row: [bold]Section 1[/]
row: Content here...
row: [divider]
row: [bold]Section 2[/]
row: More content...
row: [divider:═:cyan1]
Multi-Line Graph
Displays a smooth block character chart for data visualization (4 lines tall) with optional gradient coloring.
[graph:VALUES]
[graph:VALUES:COLOR]
[graph:VALUES:COLOR:LABEL]
[graph:VALUES:COLOR:LABEL:MIN-MAX]
[graph:VALUES:COLOR:LABEL:MIN-MAX:WIDTH]
[graph:VALUES:GRADIENT]
[graph:VALUES:GRADIENT:LABEL]
[graph:VALUES:GRADIENT:LABEL:MIN-MAX]
[graph:VALUES:GRADIENT:LABEL:MIN-MAX:WIDTH]
- VALUES: Comma-separated numbers
- COLOR: Optional Spectre.Console color (default:
cyan1) - GRADIENT: Optional gradient name or custom gradient (see Gradients section)
- LABEL: Optional label text
- MIN-MAX: Optional fixed scale range (e.g., `0-100$ \text{for} \text{percentage} \text{graphs})
- \text{WIDTH}: \text{Optional} \text{character} \text{width} (\text{default}: 30, \text{pads} \text{with} \text{background} \text{if} \text{data} \text{points} < \text{width})
\text{Character} \text{Set}: \text{Uses} \text{Unicode} \text{block} \text{characters} (▁▂▃▄▅▆▇█) \text{for} \text{smooth} \text{visualization} \text{with} 36 \text{vertical} \text{levels} (4 \text{rows} \times 9 \text{levels})
\text{Predefined} \text{Gradients}:
- $cool` - Blue to cyan (temperature/load themes)
warm- Yellow to orange to red (heat/pressure themes)spectrum- Blue to green to yellow to red (full range)grayscale- Grey11 to grey100 (monochrome)
Custom Gradients:
blue→red,green→yellow→red, etc.
Example:
row: [graph:10,20,15,25,30,28,35,40:green:CPU Load] # Solid color, default width (30)
row: [graph:60,62,65,70,68,72,75,78:warm:Temperature] # Warm gradient
row: [graph:5,10,15,20,25,30:blue→red:Load] # Custom gradient
row: [graph:45,50,55,60,58,62:cool] # Cool gradient no label
row: [graph:45,50,62,58,65,70:cool:CPU %:0-100] # Fixed 0-100% scale
row: [graph:10,15,20,25:green:Load:0-100:40] # Custom width (40 chars)
Line Graph
Displays a smooth line chart with connected points for time-series visualization. Supports both Braille characters (default, smooth) and ASCII style rendering.
[line:VALUES:COLOR:LABEL:MIN-MAX:WIDTH:HEIGHT]
[line:VALUES:COLOR:LABEL:MIN-MAX:WIDTH:HEIGHT:STYLE]
[line:VALUES:GRADIENT:LABEL:MIN-MAX:WIDTH:HEIGHT]
[line:VALUES:GRADIENT:LABEL:MIN-MAX:WIDTH:HEIGHT:STYLE]
- VALUES: Comma-separated numbers (e.g.,
10,20,15,30,25) - COLOR: Optional Spectre.Console color (default:
cyan1) - GRADIENT: Optional gradient name or custom gradient (see Gradients section)
- LABEL: Optional label text displayed above the graph
- MIN-MAX: Optional fixed scale range (e.g.,
0-100for percentage graphs) - WIDTH: Character width of the graph (default: 60)
- HEIGHT: Character height of the graph (default: 8)
- STYLE: Optional rendering style -
braille(default, smooth) orascii(block characters)
Rendering Styles:
- braille (default): Uses Braille Unicode characters for smooth, high-resolution line rendering
- ascii: Uses block characters (█, ▀, ▄) for a more traditional ASCII art look
Predefined Gradients:
cool- Blue to cyan (temperature/load themes)warm- Yellow to orange to red (heat/pressure themes)spectrum- Blue to green to yellow to red (full range)grayscale- Grey11 to grey100 (monochrome)
Custom Gradients:
blue→red,green→yellow→red, etc.
Example:
row: [line:10,20,15,30,25,35,40,30,20,10:cyan:Sample Data:0-50:60:8] # Braille style (default)
row: [line:10,20,15,30,25,35,40,30,20,10:cyan:Sample Data:0-50:60:8:ascii] # ASCII style
row: [line:5,15,10,25,20,35,30,45,40,50:warm:CPU Usage:0-100:50:6] # Warm gradient, compact
row: [line:10,15,20,25,30,25,20,15,10:blue:Memory:0-40:40:4] # Small graph
row: [line:25,25,25,25,25:grey70:Constant:0-50:30:4] # Flat line
Storage-Based Elements
ServerHub includes a built-in time-series storage system that allows widgets to persist data and query historical metrics. These elements interact with the storage backend.
Datastore Directive
Persists metric data to the SQLite database using InfluxDB-style line protocol.
datastore: MEASUREMENT[,tag=val,tag=val] field=val[,field=val] [timestamp]
- MEASUREMENT: Required alphanumeric identifier (e.g.,
cpu_usage,memory_used) - tags: Optional comma-separated key=value pairs for grouping (e.g.,
core=0,host=srv01) - fields: Required comma-separated key=value pairs (numeric, boolean, or quoted strings)
- timestamp: Optional Unix timestamp in seconds (auto-generated if omitted)
Examples:
# Simple metric
echo "datastore: cpu_usage value=75.5"
# With tags
echo "datastore: cpu_usage,core=0,host=srv01 value=75.5,temp=65"
# Multiple fields
echo "datastore: disk_io,device=sda reads=1500,writes=2300"
# Explicit timestamp
echo "datastore: metric,tag=x value=100 1707348000"
Note: Data is automatically scoped to the widget ID. Each widget's data is isolated in the database.
Datafetch (Inline)
Retrieves and displays a single value from stored data.
[datafetch:KEY]
[datafetch:KEY:AGGREGATION:TIMERANGE]
- KEY: Measurement key in format
measurement.field(e.g.,cpu_usage.value) - AGGREGATION:
latest(default),avg,max,min,sum,count - TIMERANGE: Time range like
30s,5m,1h,24h,7d, orlast_10
Examples:
# Latest value
echo "row: CPU: [datafetch:cpu_usage.value]%"
# Average over last 30 seconds
echo "row: 30s avg: [datafetch:cpu_usage.value:avg:30s]%"
# Maximum over 1 hour
echo "row: Peak (1h): [datafetch:cpu_usage.value:max:1h]%"
# Count of samples
echo "row: Samples: [datafetch:cpu_usage.value:count:1h]"
If no data is available, renders as --.
History Graph
Renders stored time-series data as a vertical bar chart (4 lines tall).
[history_graph:KEY:TIMERANGE]
[history_graph:KEY:TIMERANGE:COLOR]
[history_graph:KEY:TIMERANGE:COLOR:LABEL]
[history_graph:KEY:TIMERANGE:COLOR:LABEL:MIN-MAX]
[history_graph:KEY:TIMERANGE:COLOR:LABEL:MIN-MAX:WIDTH]
- KEY: Measurement key (e.g.,
cpu_usage.value) - TIMERANGE: Time range to query (e.g.,
30s,1m,1h,24h) - COLOR/GRADIENT: Optional color or gradient name
- LABEL: Optional label text
- MIN-MAX: Optional fixed scale (e.g.,
0-100) - WIDTH: Character width (default: 30)
Examples:
# Short-term (last 60 seconds)
echo "row: [history_graph:cpu_usage.value:60s:cool:Load %:0-100]"
# Medium-term (last hour)
echo "row: [history_graph:cpu_usage.value:1h:cyan:CPU Load:0-100:40]"
# Long-term (24 hours)
echo "row: [history_graph:cpu_usage.value:24h:warm:Temperature:0-100]"
History Sparkline (Inline)
Renders stored time-series data as an inline sparkline.
[history_sparkline:KEY:TIMERANGE]
[history_sparkline:KEY:TIMERANGE:COLOR]
[history_sparkline:KEY:TIMERANGE:COLOR:WIDTH]
- KEY: Measurement key (e.g.,
cpu_usage.value) - TIMERANGE: Time range to query
- COLOR/GRADIENT: Optional color or gradient name
- WIDTH: Character width (default: 30)
Examples:
# Inline trend for last 30 seconds
echo "row: CPU trend: [history_sparkline:cpu_usage.value:30s:cool:20]"
# Memory trend over 5 minutes
echo "row: Mem: [history_sparkline:memory.used:5m:warm:25]"
History Line Graph
Renders stored time-series data as a smooth line chart.
[history_line:KEY:TIMERANGE:COLOR:LABEL:MIN-MAX:WIDTH:HEIGHT]
[history_line:KEY:TIMERANGE:COLOR:LABEL:MIN-MAX:WIDTH:HEIGHT:STYLE]
- KEY: Measurement key (e.g.,
cpu_usage.value) - TIMERANGE: Time range to query (e.g.,
5m,1h,24h) - COLOR/GRADIENT: Optional color or gradient name
- LABEL: Optional label text
- MIN-MAX: Optional fixed scale (e.g.,
0-100) - WIDTH: Character width (default: 60)
- HEIGHT: Character height (default: 8)
- STYLE:
braille(default) orascii
Examples:
# 1 minute history
echo "row: [history_line:cpu_usage.value:1m:cyan:CPU:0-100:60:8:braille]"
# 5 minute history with gradient
echo "row: [history_line:cpu_usage.value:5m:warm:Load:0-100:50:6:braille]"
# 24 hour history
echo "row: [history_line:memory.used:24h:cool:Memory:0-100:80:10:braille]"
Time Range Format
All storage elements support these time range formats:
- Seconds:
10s,30s,60s - Minutes:
1m,5m,15m,30m - Hours:
1h,6h,12h,24h - Days:
7d,30d - Samples:
last_10,last_30,last_100
Complete Storage Example
#!/bin/bash
echo "title: CPU Monitor"
echo "refresh: 5"
# Get current CPU load
CPU_LOAD=$(awk '{print \$1}' /proc/loadavg)
CPU_PERCENT=$(awk "BEGIN {printf \"%.0f\", ($CPU_LOAD / $(nproc)) * 100}")
# Store the metric
echo "datastore: cpu_usage,host=$(hostname) value=$CPU_PERCENT"
# Display current + historical data
echo "row: [status:ok] Current: ${CPU_PERCENT}%"
echo "row: Latest: [datafetch:cpu_usage.value] | 30s avg: [datafetch:cpu_usage.value:avg:30s]"
echo "row: Trend: [history_sparkline:cpu_usage.value:1m:cool:20]"
echo "row: "
echo "row: [history_graph:cpu_usage.value:1m:cool:Last Minute:0-100:40]"
# Extended mode: longer history
if [[ "\$1" == "--extended" ]]; then
echo "row: "
echo "row: [bold]24 Hour History[/]"
echo "row: [history_line:cpu_usage.value:24h:warm:CPU Load:0-100:80:12:braille]"
fi
Spectre.Console Markup
Row content supports Spectre.Console markup for styling:
Colors
[red]Error text[/]
[green]Success text[/]
[yellow]Warning text[/]
[blue]Info text[/]
[cyan1]Accent text[/]
[grey70]Muted text[/]
Styles
[bold]Bold text[/]
[italic]Italic text[/]
[underline]Underlined text[/]
[dim]Dimmed text[/]
Combined
[bold red]Bold red text[/]
[grey70 italic]Muted italic[/]
Complete Example
#!/bin/bash
# my-widget.sh - Example custom widget with rich elements
echo "title: System Monitor"
echo "refresh: 5"
# Status with sparkline trend
cpu_history="45,48,52,55,50,53,58,60,57,62"
echo "row: CPU: 62% [sparkline:${cpu_history}:green]"
echo "row: [miniprogress:62:15] Current load"
# Divider
echo "row: [divider]"
# Table for services
echo "row: [bold]Services[/]"
echo "[table:Name|Status|Memory]"
echo "[tablerow:nginx|[green]running[/]|45MB]"
echo "[tablerow:mysql|[green]running[/]|320MB]"
# Divider
echo "row: [divider:─:cyan1]"
# Graph for extended mode
if [[ "\$1" == "--extended" ]]; then
memory_history="60,62,65,70,68,72,75,78,80,85"
echo "row: [graph:${memory_history}:yellow:Memory usage (10 samples)]"
fi
Security
ServerHub enforces mandatory checksum validation to protect against widget tampering.
Trust Hierarchy
| Source | Checksum Location | Trust Level | Bypass with --dev-mode? |
|---|---|---|---|
| Bundled | Hardcoded at build | Highest (maintainer reviewed) | No - always validated |
| Config sha256 | User adds to YAML | High (user verified) | Yes |
| --discover | Captured at approval | High (user reviewed code) | N/A |
Security Principle
Checksums must come from a trusted source at a trusted moment:
- Bundled: Build-time (maintainer reviewed code)
- Third-party: Author provides checksum, user verifies
- Self-developed: Author calculates after code is finalized
- --discover: Captured after user reviews code preview
Checksums should NOT be auto-generated from current file state (attacker could modify then regenerate).
Custom Widgets (Required)
All custom widgets (in ~/.config/serverhub/widgets/) require SHA256 checksum validation:
widgets:
my-widget:
path: my-widget.sh
sha256: a1b2c3d4e5f6... # REQUIRED for custom widgets
refresh: 10
Without a checksum, custom widgets will fail to load with a helpful error showing the required checksum.
Adding Widgets
Option 1: Discovery (Recommended)
# Discover and add new widgets interactively
serverhub --discover
# Review code preview → approve → auto-added with checksum
Option 2: Manual
# Calculate checksum
sha256sum ~/.config/serverhub/widgets/my-widget.sh
# Add to config.yaml with checksum
Verifying Checksums
# Verify all widget checksums (shows VALID/MISMATCH/MISSING)
serverhub --verify-checksums
Development Mode
For widget development, use --dev-mode to skip custom widget checksum validation:
# Skip checksum validation for custom widgets only
serverhub --dev-mode --widgets-path ./my-dev-widgets
Important:
- Bundled widgets are always validated, even in dev mode
- Dev mode shows prominent warnings (status bar, orange border, startup dialog)
- Never use --dev-mode in production
| Validation | Normal Mode | --dev-mode |
|---|---|---|
| Path restrictions | Enforced | Enforced |
| Symlink detection | Enforced | Enforced |
| Executable check | Enforced | Enforced |
| Bundled checksum | Enforced | Enforced |
| Custom widget checksum | Enforced | Skipped |
Bundled Widgets
Bundled widgets (in ~/.local/share/serverhub/widgets/) are pre-validated at build time with hardcoded checksums and don't require checksums in your config.
Workflow Examples
Third-Party Widget:
# 1. Download widget
wget https://example.com/monitoring.sh
# 2. Verify author's checksum
sha256sum monitoring.sh
# Compare with author's published checksum
# 3. Move to widgets directory
mv monitoring.sh ~/.config/serverhub/widgets/
# 4. Discover and add
serverhub --discover
Self-Developed Widget:
# 1. Develop with --dev-mode
serverhub --dev-mode --widgets-path ./my-widgets
# 2. When done, add via --discover
cp my-widgets/custom.sh ~/.config/serverhub/widgets/
serverhub --discover
# 3. Run normally
serverhub
Extended Mode
When a user opens the expanded view (double-click or Enter on a widget), the script is re-executed with the --extended argument. This allows scripts to output additional detail that wouldn't fit in the dashboard view.
Detecting Extended Mode
Check for --extended in your script arguments:
#!/bin/bash
EXTENDED=false
if [[ "\$1" == "--extended" ]]; then
EXTENDED=true
fi
Usage Pattern
echo "title: My Widget"
echo "refresh: 5"
# Always show summary
echo "row: [status:ok] Service running"
# Show additional detail only in extended mode
if [ "$EXTENDED" = true ]; then
echo "row: "
echo "row: [bold]Extended Details:[/]"
echo "row: PID: 12345"
echo "row: Uptime: 5 days"
echo "row: Memory: 256MB"
# ... more detailed info
fi
Best Practices
- Dashboard view: Keep output concise (fits within
max_lines) - Extended view: Include full details, logs, stats, etc.
- Scripts without
--extendedhandling work normally (same output in both views) - Extended mode is optional - scripts don't need to handle it
Output Requirements
- Scripts must be executable (
chmod +x) - Output to stdout only
- One protocol element per line
- Exit code 0 for success (non-zero shows error state)
- Keep execution time short (< refresh interval)