README.md

September 5, 2026 · View on GitHub

 ____   _____        _______ ____  _     ___ _   _ _____ ____  ____
|  _ \ / _ \ \      / / ____|  _ \| |   |_ _| \ | | ____|  _ \/ ___|
| |_) | | | \ \ /\ / /|  _| | |_) | |    | ||  \| |  _| | |_) \___ \
|  __/| |_| |\ V  V / | |___|  _ <| |___ | || |\  | |___|  _ < ___) |
|_|    \___/  \_/\_/  |_____|_| \_\_____|___|_| \_|_____|_| \_\____/

// RUST PORT OF python's powerline-status. STATUSBAR-AS-A-NATIVE-BINARY. ZERO PYTHON RUNTIME.

Read the Docs · Engineering Report


Status Tests Parity Bugs Fixed Source Language Target License: MIT

[SIGNAL // POWERLINE WITHOUT THE PYTHON IMPORT COST]

// jacking your prompt off the python interpreter — same segments, same theme grammar, native exec speed //


> SYSTEM OVERVIEW

powerliners is a Rust port of powerline-status — the canonical Python-driven statusline/prompt renderer used in tmux, zsh, bash, vim, ipython, and shell continuation lines. The Python implementation pays a ~50–150 ms interpreter-startup tax on every render (every prompt redraw, every tmux refresh). powerliners is a single static binary: zero-import, zero-GC, sub-millisecond render.

Drop-in compatible with the existing powerline/config JSON theme + segment files so users can keep their themes unchanged.


> WHY A PORT?

[x] python startup is the killer — ~100 ms per render on the default tmux+powerline setup
[x] tmux refreshes the statusline every interval, and per-window — startup cost compounds
[x] zsh's prompt redraws after every keystroke when `precmd` hooks fire
[x] a 100 ms latency tax on every keystroke-induced redraw turns interactive shells into slideshows
[x] rust gives us: a static binary, microsecond startup, zero runtime deps, cross-arch builds
[x] preserve the exact powerline theme grammar — users keep their .json themes verbatim

> TARGETS

[x] tmux statusline / continuation lines
[x] zsh prompt (PS1 / RPROMPT)
[x] bash prompt (PS1 / PROMPT_COMMAND)
[x] vim statusline
[x] ipython / python REPL prompt (via shell hook, not embedded)

> STATUS

[port progress]   134 / 137 upstream .py files at DONE tier (97.8%)
[remaining]       3 lipstick-only py files — zero-fn class shells, see Status
[partial/sparse]  0 / 0 — no degraded files
[lib tests]       2529 passing, 0 failing, 0 ignored
[parity tests]    462 against live upstream Python — every assertion runs the
                  Python interpreter on the upstream powerline source and
                  compares byte/value identical with the Rust port
[port bugs fixed] 11 surfaced by the parity harness and corrected in the
                  Rust port (see git log for the full list)
[drift gate]      green — every ported fn name matches docs/powerline_py_functions.txt
[citation rule]   every Rust body line annotated // py:NNN against the upstream source line

The port is structurally complete at the function level. Citation-density tier classifier (scripts/gen_port_checklist.py) requires // py:NNN citation density >= 0.5 plus a /// Port of <py_fn>() doccomment per Python function for DONE classification. All upstream Python files with function bodies are at DONE.

The 3 remaining NEAR files are Python lipstick — there is no behavior left to port:

  • renderers/shell/readline.py (14 LOC) — a class shell with two string constants (escape_hl_start = '\x01', escape_hl_end = '\x02') and one module-level alias. py_fn_total == 0.
  • renderers/shell/zsh.py (16 LOC) — same shape, different escape constants. py_fn_total == 0.
  • bindings/i3/powerline-i3.py (52 LOC) — one render function which is already 100% ported (see docs/port_report.html); the classifier still tags it NEAR because the file structure trips its class-only branch.

These exist in upstream because Python's renderer registry needs a class per shell flavour to subclass ShellRenderer; the Rust port holds the same escape-marker constants directly on the equivalent renderer struct without ceremony. The classifier's py_fn_total == 0 denominator simply can't promote 0/0 → DONE — a classifier amendment would resolve it cosmetically, but there is no real port gap. The remaining behavioral surface (every Python function with a body) is at DONE.

What's wired end-to-end

BinaryMirrorsWhat it does
powerlineclient/powerline.cNative Rust client — forwards argv + cwd + env to the daemon over a Unix socket via the upstream wire format, falls back to powerline-render exec if the daemon is unreachable
powerline-configscripts/powerline-configtmux / shell known-function dispatch, plus the non-upstream vim source-path extractor
powerline-lintscripts/powerline-lintargparse + full check pipeline (markedjson loader + Spec checks + orchestrator integration)
powerline-renderscripts/powerline-renderargparse + ext lookup + full direct-render path through the Powerline orchestrator (used as daemon-less fallback)
powerline-daemonscripts/powerline-daemonUNIX-socket bind + daemonize + pidfile lock + accept loop + EOF shutdown + end-to-end statusline rendering against a real ~/.config/powerline/themes/... JSON tree, rendered on a worker pool so no request blocks another

End-to-end render

powerline-daemon produces real #[fg=…,bg=…]… tmux markup from a user's powerline config root via the wire format compatible with the upstream Python powerline C client. The render path covers:

  • Config cascade load (_find_config_files + load_json_config + mergedicts)
  • Colorscheme alias chasing + cterm color resolution (Colorscheme::get_highlighting)
  • Segment preparation via gen_segment_getter returning a Theme.segments table
  • Segment dispatch through process_segment / process_segment_lister
  • Renderer loop (do_render / _render_segments / _render_length) with hard/soft divider insertion and per-side outer padding
  • TmuxRenderer #[…] markup emission with term_truecolor cterm path

Render isolation

Upstream's daemon renders inline in its select loop, so one slow segment stalls every client behind it. Here the loop only does socket work: requests go to a worker pool (POWERLINERS_RENDER_THREADS, default 4) and completions arrive through a self-pipe that sits in the poll set alongside the sockets.

Within a single render, the segments themselves run concurrently. They are almost entirely subprocess and network waits — netstat, top, git, ioreg, an HTTP call for the weather — so running them in sequence made a statusline cost the sum of its parts. A 20-segment right-hand bar measured ~900 ms serial against ~520 ms concurrent, the latter being simply the slowest single segment (network_load). The status-interval that asked for the render is 2 s, so the serial cost was close enough to the budget that tmux abandoned requests mid-flight; the concurrent cost is not.

Each segment still answers to its own deadline, fixed when it starts rather than when its result is collected, so a slow segment early in the batch cannot eat the budget of the ones behind it. The per-segment time in the log is the segment's own, not the batch wall — otherwise every segment would report the slowest one's number and the log would stop being able to answer "which segment is slow".

Four bounds keep a misbehaving segment from reaching the statusline:

BoundDefaultBehavior on breach
Per-segment budget2000 ms ("timeout": <ms> per segment in the theme)Serve the segment's last good value (max 60 s old), and don't re-enter it until the overdue call returns
Subprocess budgetper call siteSIGKILL the child so a wedged helper can't pin a worker
Connection lifetime10 s, or immediately on client hangupClose the socket and cancel any render still queued for it
Panic containmentalwaysAnswer that one request with the panic message, keep the worker, and replace it if it is lost anyway

Watchdog trips, panics and connection reaps are logged to ~/.powerliners/powerliners.log. Panics are logged from a hook rather than left on stderr, which daemonize points at /dev/null.

Panic containment is the newest of the four, and it exists because a render that panicked used to kill its worker outright. Nothing replaced the thread and nothing recorded the death, so four panics emptied the pool; from then on the daemon accepted every connection and closed it with zero bytes. tmux reads that as "this side renders to nothing", so the statusline went blank and stayed blank — for 15 hours in the case that prompted this — while powerline-render kept working and the daemon kept looking healthy in ps. A panicking render now costs one request.

The concrete failure this replaced: the Spotify segment asked System Events for the process list on every render. When the daemon's login session stopped matching the active console session, LaunchServices could no longer supply System Events and each call blocked ~30 s, so renders took 30–120 s against a 2 s tmux status-interval and the bar showed <'…' not ready>. That question is now answered from the process table directly (libproc on macOS, /proc on Linux) with no AppleScript at all when the player isn't running.

58 segment adapters wired in src/bin/shared/render_runtime.rs (59 ADAPTERS keys including the bare exec alias; shared between powerline-daemon and powerline-render): battery, branch, clementine, cmus, containers, cpu_load_percent, cwd, date, dbus_player, disk_io, disk_usage, disk_usage_percent, email_imap_alert, environment, exec, external_ip, fuzzy_time, gpu_usage_percent, gpu_vram, hostname, internal_ip, itunes, jobnum, kubecontext, last_pipe_status, last_status, mem_swap, mem_swap_percentage, mem_usage, mem_usage_percent, mocp, mpd, network_load, process_count, rhythmbox, spotify, stash, system_load, thermal, uptime, user, virtualenv, weather, plus git_status, ci_status, aws.context, gcp.context, fusevm.jit_cache, and the rkyv_cache / version adapters for zshrs, stryke, awkrs, vimlrs, and elisprs.

Point it at a config root via POWERLINE_CONFIG_PATHS:

POWERLINE_CONFIG_PATHS=~/.config/powerline ./target/debug/powerline-daemon --foreground --socket /tmp/powerliners

The Python powerline C client already installed via pip talks to it unchanged — same argc\0arg\0arg\0cwd\0KEY=VAL\0...\0\0 wire format and same EOF\0\0 shutdown sentinel.

Regenerate the per-file tier table from the live source via:

python3 scripts/gen_port_checklist.py > docs/PORT_CHECKLIST.md

Regenerate the function-coverage report via:

python3 scripts/gen_port_report.py

> MIGRATION TUTORIAL

Drop-in replacement for the Python powerline-daemon. The C client shipped with powerline-status (installed via pip install powerline-status) talks to our daemon unchanged — same wire format, same EOF\0\0 shutdown, same socket path (/tmp/powerline-ipc-$UID on macOS / BSD, abstract \0powerline-ipc-$UID on Linux).

Step 1: Install or build

Fastest path — Homebrew tap (auto-bumped by each release):

brew tap MenkeTechnologies/menketech
brew install powerliners
# installs powerline, powerline-daemon, powerline-config, powerline-render, powerline-lint

Or build from source (entire 5-binary suite):

git clone https://github.com/MenkeTechnologies/powerliners
cd powerliners
cargo build --release --locked \
  --bin powerline --bin powerline-daemon \
  --bin powerline-config --bin powerline-render --bin powerline-lint

Release binaries land at target/release/{powerline,powerline-daemon,powerline-config,powerline-render,powerline-lint}.

Step 2: Verify parity against your config

Before swapping anything, run the daemon against a copy of your real config root and confirm the rendered tmux markup matches what you currently see:

# Spawn our daemon on a throwaway socket
POWERLINE_CONFIG_PATHS=~/.config/powerline \
  ./target/release/powerline-daemon \
  --foreground \
  --socket /tmp/powerliners-probe &

# Render the right side via the wire protocol
python3 <<'PY'
import socket
s = socket.socket(socket.AF_UNIX)
s.connect("/tmp/powerliners-probe")
s.send(b"2\x00tmux\x00right\x00/tmp\x00HOME=/tmp\x00\x00")
print(s.recv(8192).decode("utf-8", "replace"))
PY

# Compare against the Python upstream (powerline-status must be installed)
powerline-render tmux right -p ~/.config/powerline

If the two outputs match byte-for-byte for your common segments, proceed. If they don't, file an issue with the divergence — the daemon_parity suite covers 45 byte-for-byte scenarios but real configs hit combinations we haven't asserted on.

Step 3: Stop the Python daemon

powerline-daemon -k

Step 4: Replace the binary in $PATH

The simplest approach is symlinking the Rust binary into a directory that comes before powerline-status's ~/.local/bin (or wherever pip put it) in your $PATH:

ln -sf "$(pwd)/target/release/powerline-daemon" /usr/local/bin/powerline-daemon
# verify the new resolution
which powerline-daemon   # must report the symlink, not the Python script

The Python C client at ~/.local/bin/powerline (or powerline-render as fallback) does NOT need to be replaced — it speaks the same wire protocol to whichever daemon is bound to the socket.

Step 5: Restart tmux

Your existing ~/.tmux.conf invocations work unchanged. The canonical lines:

run-shell "powerline-config tmux setup"
run-shell -b "powerline-daemon -q &>/dev/null || exit 0"

powerline-config tmux setup is install-method-agnostic as of 0.2.3 — the 8 tmux conf files (powerline-base.conf plus 7 version-specific variants) are embedded into the binary via include_str! and extracted to $XDG_CACHE_HOME/powerliners/tmux/ (default ~/.cache/powerliners/tmux/) on first call. Works identically for cargo install, brew install, and manual cp into $PATH — no compile-time path baking required.

It is also server-implementation-agnostic as of 0.2.20. Setup drives the multiplexer you are actually running rather than a hardcoded tmux: POWERLINE_TMUX_EXE wins if set, otherwise the binary is chosen from $TMUX's socket path (a ztmux-<uid>/ socket selects ztmux), then from whichever multiplexer has a live socket directory for your uid, then from PATH with tmux first. Before 0.2.20 a ztmux session got error connecting to /tmp/tmux-<uid>/default — or nothing at all on a host without tmux installed — and the status bar silently stayed at the server default.

A status-left-length / status-right-length you have tuned yourself survives setup as of 0.2.23. powerline-base.conf hardcodes 20 and 150, and tmux source applies them unconditionally, so re-running powerline-config tmux setup against a live server used to revert the tuning. Since tmux keeps only the head of an over-long status-right, a 350 → 150 revert clipped the tail segments and pushed the rest against the right edge. Setup now reads both lengths before sourcing and re-applies whatever differs from tmux's own defaults (10 / 40); a fresh server, still sitting at those defaults, gets powerline's wider budget as before.

Kill and reattach tmux to confirm:

tmux kill-server
tmux new-session

The status bar should look identical. The powerline-daemon process in ps aux should now be a Rust-port invocation rather than the Python shebang.

Step 6: Confirm

ps -p $(cat /tmp/powerline-ipc-$UID.pid) -o args=
# expected: /usr/local/bin/powerline-daemon -q
ls -la $(which powerline-daemon)
# expected: lrwxr-xr-x  ... -> .../target/release/powerline-daemon

Rollback

Re-resolve powerline-daemon to the Python script:

powerline-daemon -k
rm /usr/local/bin/powerline-daemon
which powerline-daemon   # should now resolve to ~/.local/bin/powerline-daemon (Python)
powerline-daemon -q

Everything is byte-compatible — no config edits, no .tmux.conf edits, no shell-rc edits.


> VIM SETUP

Vim statusline + tabline rendering ships in the same powerline binary — no +python3 requirement, no pip install powerline-status. The bundled powerline.vim plugin is embedded in the binary via include_str! and extracted to ~/.cache/powerliners/vim/powerline.vim on first source.

Works on vim 7.4+, vim 9, and neovim — the plugin uses vim8-compatible syntax only (no vim9script lock-in).

Step 1: Source the plugin from .vimrc

The one-line install that extracts and sources the plugin on every vim launch:

if executable('powerline-config')
  execute 'source' trim(system('powerline-config vim source-path'))
endif

powerline-config vim source-path extracts the bundled plugin and prints its path; the :execute 'source' … then sources it. Caches in $XDG_CACHE_HOME/powerliners/vim/ (default ~/.cache/powerliners/vim/) so subsequent vim launches re-source from the already-extracted file.

Or the manual two-step (pin the path, skip the per-launch fork):

# one-time extraction
powerline-config vim source-path
# /Users/<you>/.cache/powerliners/vim/powerline.vim
" .vimrc
set runtimepath+=~/.cache/powerliners/vim
source ~/.cache/powerliners/vim/powerline.vim

The plugin shells out via system('powerline vim left …') on every statusline refresh. With the daemon running, that's a microsecond Unix-socket round-trip; without it, every refresh fork-execs powerline-render which is ~30 ms cold per call. Start the daemon at shell login (zsh .zshrc or bash .bash_profile):

powerline-daemon -q

The -q flag double-forks and detaches; vim sees the socket immediately. No per-vim daemon — one process per UID handles tmux, shell prompts, and every running vim simultaneously.

Step 3: Verify

Open a fresh vim session and check:

:echo &statusline
" expected: a %#Pl_…# markup string ending in segment chunks

:PowerlinersRefresh
" manually re-runs the refresh; useful for debugging

:messages
" no errors should appear; if 'powerline: write() to daemon failed'
" shows up, the daemon isn't reachable — re-run `powerline-daemon -q`

The plugin sets laststatus=2 automatically so the statusline shows even in single-window sessions.

What the plugin wires up

Triggered autocmds (augroup powerliners):

EventWhen it fires
VimEnterinitial render on launch
WinEnter / BufWinEnter / BufEnter / TabEnterrefresh on context switch
ModeChanged (vim ≥ 8.2.2871)mode transitions (normal → insert etc)
CursorMoved / CursorMovedI (legacy fallback)every cursor move on ancient vim

Per-request keys sent to the renderer (matches upstream's powerline.bindings.vim so theme JSON written for Python upstream renders identically here):

  • mode — current vim mode (n / i / v / R / …)
  • bufnrbufnr('%')
  • winnrwinnr()
  • buf_nameexpand('%:p') (omitted when empty)

The bundled powerline.vim shows the actual wiring at the source path you printed above.

Override the binary name

If you've installed under a non-default name or want to test a build from target/release/, set the global before sourcing the plugin:

let g:powerliners_binary = expand('~/code/powerliners/target/release/powerline')
if executable(g:powerliners_binary)
  execute 'source' trim(system(g:powerliners_binary . '-config vim source-path'))
endif

Customize the theme

The vim statusline pulls from ~/.config/powerline/themes/vim/default.json plus ~/.config/powerline/colorschemes/vim/default.json. The bundled defaults live under src/ported/config_files/themes/vim/ and src/ported/config_files/colorschemes/vim/ for reference; copy any of them into ~/.config/powerline/ and edit.

Same JSON shape as upstream powerline — segments listed under segments.left / segments.right, theme inheritance via extends, per-mode highlight overrides via mode_translations.

Troubleshooting

SymptomCause / fix
empty statuslineset laststatus=2 got stomped by something else in .vimrc; re-set after sourcing
garbled escape codesterminal doesn't support truecolor; renderer falls back to cterm but vim must be in a 256-color tty ($TERM=xterm-256color)
colors don't match terminalcolorscheme JSON missing your custom palette — copy colorschemes/vim/default.json into ~/.config/powerline/colorschemes/vim/
powerline: write() to daemon faileddaemon isn't running; run powerline-daemon -q
refresh stutters / flickersthe legacy CursorMoved fallback is firing on every keystroke (vim < 8.2.2871) — upgrade vim or pin a let g:powerliners_no_cursormoved = 1 patch
E121: Undefined variable: g:powerliners_binary before source lineg:powerliners_binary is set BY the plugin; reference it only inside autocmds that fire after sourcing

Known divergences from Python upstream

These are the only areas where output may differ. Each is documented under the test suite's "inherent divergence" notes:

  1. Live-data segments (cpu_load_percent, network_load, ...) are sampled per-render via subprocess probes in our daemon; the Python upstream uses psutil with a different sampling cadence. Numeric values may differ by a sampling-window's worth of data; the markup framing is identical.
  2. Threaded segment caching: Python's ThreadedSegment polls in a background thread and renders the last-known value; our daemon samples on-demand under a per-segment deadline and serves the last good value when a segment overruns it. Latency profile differs; output content matches.
  3. psutil-only features: Python upstream errors loudly when psutil is missing and skips affected segments. Our daemon resolves the same data via OS subprocess probes (top, vm_stat, netstat, pmset, uptime) and renders successfully.
  4. tmux setup ordering: upstream sources the tmux config files and only then sets POWERLINE_COMMAND in the tmux server environment — and skips that step entirely when the variable is already exported in the calling shell. Since tmux 2.1 the status-left in powerline_tmux_2.1_plus.conf expands a bare $POWERLINE_COMMAND at source time, so the upstream order bakes an empty command into it and the left side runs env tmux left …. (status-right is single-quoted, so it escapes the problem — which is why the breakage is left-only.) We publish POWERLINE_COMMAND into the tmux environment first, propagating an exported value rather than skipping.
  5. tmux setup and user-tuned status lengths: upstream lets powerline-base.conf's status-left-length 20 / status-right-length 150 overwrite whatever the server already has, so a setup run that lands after ~/.tmux.conf reverts a tuned length. We snapshot both options before sourcing and re-apply any value that differs from tmux's own defaults (10 / 40). tmux reports defaults and explicit settings identically, so a length pinned to tmux's default is indistinguishable from an unset one and still loses to the bundled value.

For everything else — markup, escaping (###[], control chars via translate_np), dividers (hard/soft/multi-char/empty/single-char), colorscheme resolution (alias chains, fallback groups, gradients, cterm/truecolor encoding with falsy-hex fallback), attrs (bold + italics + underline bit-packed), outer_padding, spaces, left/right side handling, empty sides, before/after wrapping, Unicode contents — the byte stream is identical.


> CUSTOM SEGMENTS

Upstream Python powerline lets you drop a .py file in ~/.config/powerline/segments/ and reference it by dotted-path in theme JSON; __import__ makes it callable. The Rust binary has no dynamic import, so the dispatch layer in src/extensions/exec_segment.rs (src/bin/shared/render_runtime.rs adapter wiring) provides two equivalent surfaces that fall through to a subprocess.

Pattern A — explicit exec adapter

Reference the built-in exec adapter directly in theme JSON. Use when you want the script path, args, and format string spelled out inline:

{
  "function": "exec",
  "args": {
    "command": "/usr/local/bin/cpu_temp.sh",
    "args": ["--unit", "C"],
    "format": "%s°C",
    "highlight_groups": ["cpu_load"]
  }
}

Pattern B — dotted-path filesystem dispatch

Reference your segment by dotted path and drop the script under <config_path>/segments/. The daemon resolves myseg.cpu_temp<config_path>/segments/myseg/cpu_temp.{sh,py,rb,pl,lua,js,executable} (first hit wins; order documented in src/extensions/exec_segment.rs::SCRIPT_EXTENSIONS):

{
  "function": "myseg.cpu_temp"
}
mkdir -p ~/.config/powerline/segments/myseg
cat > ~/.config/powerline/segments/myseg/cpu_temp.sh <<'EOF'
#!/bin/sh
echo "$(osx-cpu-temp | sed 's/°C//')"
EOF
chmod +x ~/.config/powerline/segments/myseg/cpu_temp.sh

The dotted-path resolution honors the daemon's full config-path cascade (POWERLINE_CONFIG_PATHS, --config-path, ~/.config/), so segments dropped in any config root that's on the cascade become available.

Output protocol

Both patterns parse the script's stdout on the first non-whitespace byte:

Stdout starts withTreated asWrapped how
[ (valid JSON array)Verbatim segment listUsed directly — full control over highlight_groups, gradient_level, divider_highlight_group, multi-chunk output
anything elsePlain text[{"contents": <trimmed-stdout>, "highlight_groups": [...]}]format template applies (%s → contents, %% → literal %)

Plain-text scripts:

#!/bin/sh
echo "CPU $(cat /proc/loadavg | cut -d' ' -f1)"

JSON scripts (control gradient color, attach divider group, emit multiple chunks):

#!/usr/bin/env python3
import json, psutil
print(json.dumps([
    {"contents": "🌡 ", "highlight_groups": ["thermal_icon"]},
    {"contents": f"{psutil.cpu_percent():.0f}%",
     "highlight_groups": ["thermal_gradient", "background"],
     "gradient_level": psutil.cpu_percent()}
]))

Highlight-group caveat (Rust-port divergence)

The current Rust port's gen_segment_getter (at src/ported/segment.rs:1011-1023) uses the segment's function_name as the only highlight group for "type": "function" segments — it ignores the theme's "highlight_groups" override. Upstream Python respects the override.

Practical impact: name your colorscheme group to match the function name ("exec" for pattern A, "cpu_temp" for pattern B), e.g.

"groups": {
  "exec": { "fg": "white", "bg": "gray0", "attrs": [] },
  "cpu_temp": { "fg": "yellow", "bg": "black", "attrs": [] }
}

Pattern B sidesteps this nicely because the dotted path's trailing component (the function name) is also a natural colorscheme group name. Pattern A requires either a literal "exec" group, or pinning the highlight via a JSON-array stdout that emits explicit highlight_groups inline.

Bundled extensions

src/extensions/ ships several net-new segments above what upstream powerline-status offers, dispatched as standard built-ins (no filesystem lookup needed):

Dotted pathWhat
powerlinemem.mem_usage.mem_usage1:1 port of mKaloer/powerline_mem_segment's USED/TOTAL formatted bytes
powerlinemem.mem_usage.mem_usage_percentPercentage variant
powerlinemem.mem_usage.mem_swapSwap USED/TOTAL
powerlinemem.mem_usage.mem_swap_percentageSwap percent
powerliners.disk.disk_usageFilesystem USED/TOTAL for any mount
powerliners.disk.disk_usage_percentFilesystem percent-used
powerliners.disk.disk_ioLive read/write throughput for any device
powerliners.gpu.gpu_usage_percentVendor-dispatched GPU compute percent (nvidia-smi → rocm-smi → intel_gpu_top → ioreg fallback)
powerliners.gpu.gpu_vramGPU VRAM USED/TOTAL via same dispatch chain
powerliners.thermal.thermalCPU/GPU temp + fan RPM (/sys/class/hwmon on Linux, powermetrics/istats on macOS)
powerliners.vcs.git_statusp10k-style single-chunk VCS segment — branch glyph + name + count badges for unstaged/untracked/staged/conflicts/ahead/behind/stashed from one git status --porcelain=v2 --branch fork; detached HEAD falls back to short SHA / tag; omitted outside a git work tree
powerliners.docker.containersDocker / OCI container counts ({running}/{total}/{images}/{stopped} tokens; probes via docker ps, falls through silently when the daemon is unreachable)
powerliners.k8s.kubecontextCurrent kubectl context + active namespace (honors $KUBECONFIG cascade and in-context kubectl config set-context --namespace overrides; hide_default arg suppresses the namespace when it equals the configured default)
powerliners.proc.process_countPOSIX process tally via ps -eo stat= ({total}/{running}/{sleeping}/{zombie}/{dwait}/{stopped} tokens; warn_zombie flips the highlight group when defunct processes are present)
powerliners.github.ci_statusCurrent branch's HEAD check-runs via gh api repos/:o/:r/commits/:sha/check-runs, cached on disk by SHA (ttl_secs default 30). Tokens: {icon}/{state}/{passed}/{failed}/{running}/{total}. Highlight groups split into github_ci_success / github_ci_failure / github_ci_pending. Falls through silently outside a GitHub work tree or when gh is missing
powerliners.aws.contextActive AWS profile + region. Pure-fs probe of $AWS_PROFILE/$AWS_REGION/$AWS_DEFAULT_REGION then ~/.aws/config (honors the [profile NAME] quirk). Tokens: {icon}/{profile}/{region}. hide_default_profile drops {profile} + adjacent separator when profile is default
powerliners.gcp.contextActive gcloud configuration's project + account. Pure-fs probe of ~/.config/gcloud/active_config + configurations/config_<NAME> ([core] section). Env overrides $CLOUDSDK_ACTIVE_CONFIG_NAME / $CLOUDSDK_CORE_PROJECT / $CLOUDSDK_CORE_ACCOUNT win. Tokens: {icon}/{project}/{account}/{config}. hide_account strips the account fragment
powerliners.fusevm.jit_cachefusevm Cranelift JIT cache stats (entry count + bytes) for the zshrs/stryke runtime. Recursively walks path (theme arg) or $FUSEVM_JIT_CACHE / $XDG_CACHE_HOME/fusevm-jit / ~/.cache/fusevm-jit. Tokens: {icon}/{entries}/{size} (du -sh block allocation, human K/M/G)/{bytes} (raw disk bytes)/{logical_size}/{logical_bytes} (stat-style content sum). show_when_empty controls whether a missing/cold cache still renders
powerliners.zshrs.rkyv_cacheSingle-file stat of the zshrs authoritative rkyv archive at $ZSHRS_RKYV_CACHE / $ZSHRS_HOME/scripts.rkyv / $XDG_DATA_HOME/zshrs/scripts.rkyv / ~/.zshrs/scripts.rkyv. Same {size} / {bytes} / {logical_*} token surface as fusevm.jit_cache. Disk-bytes default matches du -sh
powerliners.stryke.rkyv_cacheSingle-file stat of ~/.stryke/scripts.rkyv (stryke's authoritative bytecode store, Cranelift-JIT'd via the shared fusevm runtime). Same token surface
powerliners.awkrs.rkyv_cacheSingle-file stat of ~/.awkrs/scripts.rkyv. Same token surface
powerliners.vimlrs.rkyv_cacheSingle-file stat of ~/.vimlrs/scripts.rkyv (the Rust VimL reimplementation's bytecode store). Same token surface
powerliners.elisprs.rkyv_cacheSingle-file stat of ~/.elisprs/scripts.rkyv (the Rust Emacs-Lisp reimplementation's bytecode store). Same token surface
powerliners.zshrs.versionLatest installed zshrs version (parsed from <bin> --version). In-process TTL cache (default 300 s) so the daemon doesn't fork on every prompt tick. Tokens: {icon}/{version}
powerliners.stryke.versionSame for stryke (handles the This is stryke vX.Y.Z — ... prefix shape).
powerliners.awkrs.versionSame for awkrs.
powerliners.vimlrs.versionSame for vimlrs.
powerliners.elisprs.versionSame for elisprs.
powerliners.exec.execThe explicit exec adapter (also resolves via bare "function": "exec")

These each live in src/extensions/<module>.rs and are wired into the daemon's ADAPTERS table — adding more follows the same pattern (no new fn-name rules apply per docs/PORT.md's src/extensions/ carve-out).

src/extensions/ also holds non-segment infrastructure that upstream has no equivalent for: render_pool (the daemon's worker pool), segment_watchdog (per-segment budgets), proc_timeout (subprocess kill-on-overrun), proc_lookup (process-table queries), watch (Reactive Prompt Push), and diag_log.

> Cache-size segments — shared resolution chain

The six cache-size segments (fusevm.jit_cache, zshrs.rkyv_cache, stryke.rkyv_cache, awkrs.rkyv_cache, vimlrs.rkyv_cache, elisprs.rkyv_cache) share a uniform 4-level resolution chain and an identical {size} / {bytes} / {logical_size} / {logical_bytes} token surface so the same theme JSON works across all six. Each is a pure filesystem probe — no subprocess, no daemon RPC.

For the rkyv segments, <NAME> ∈ {ZSHRS, STRYKE, AWKRS, VIMLRS, ELISPRS}:

  1. $<NAME>_RKYV_CACHE — explicit override, used verbatim
  2. $<NAME>_HOME/scripts.rkyv
  3. $XDG_DATA_HOME/<name>/scripts.rkyv — used only when the file exists
  4. ~/.<name>/scripts.rkyv — final fallback

fusevm.jit_cache uses the analogous 3-level chain ($FUSEVM_JIT_CACHE$XDG_CACHE_HOME/fusevm-jit~/.cache/fusevm-jit) but recursively walks a cache directory rather than stat-ing a single archive, and also tracks an {entries} token for the recursive file count. Symlinks count as a single entry and are never followed (avoids infinite loops across re-symlinked cache dirs).

The resolution chain is unit-tested via a pure-functional default_path_with(get_env, path_exists) seam in src/extensions/{zshrs,stryke,awkrs,vimlrs,elisprs}_rkyv.rs and a matching default_root_with(get_env) seam in src/extensions/fusevm_jit.rs — no env-var mutation in tests, no thread-safety hazard, every precedence level pinned.

Missing-archive behavior: returns None by default (no chunk renders). Set "show_when_empty": true to render zeroed stats (0B/0) instead.

Highlight-group chain (3 levels): <segment>_rkyv_cache<segment>information:regular. The trailing information:regular is a neutral fallback so the chunk renders in any colorscheme.

> Reactive Prompt Push

Upstream powerline renders on a pull: your shell asks for a prompt on each precmd (and, in tmux, on an interval). Nothing tells the prompt to redraw when its inputs change out from under it — so if you git checkout in another pane, this pane's branch name is stale until you press Enter.

The Reactive Prompt Push extension (src/extensions/watch.rs) adds an orthogonal push path. The warm powerline-daemon watches the filesystem inputs backing each client's currently-displayed prompt — its cwd, .git/HEAD, .git/index, and the active branch ref — using real OS events (kqueue/FSEvents on macOS, inotify on Linux via the notify crate). On a real change it writes a single wake byte to a per-client FIFO the shell is watching, and the shell redraws the prompt in place. The branch flips the instant you check out in another pane — between keystrokes, no Enter, no interval timer.

It is edge-triggered and de-duplicated: the OS delivers an event only on a real change, and the wake byte is written only when the recomputed prompt fingerprint differs from the one captured at the last render — so there are zero wasted redraws. When no watch backend can be created the client silently degrades to the existing pull-only behavior; the 1:1 ported render path is untouched.

Install (zsh). The shell side is shipped as a binding at src/extensions/shell_hooks/reactive.zsh (also available in-process as powerliners::extensions::shell_hooks::zsh_reactive()). Source it from ~/.zshrc after powerline's own zsh binding, with a running daemon:

source /path/to/powerliners/src/extensions/shell_hooks/reactive.zsh
_powerline_reactive_setup

_powerline_reactive_setup creates a per-shell FIFO, exports its path as $POWERLINE_RESET_FIFO (which the shell passes into every powerline invocation and thus to the daemon), and wires the read end into the line editor via zle -F <fd> → zle reset-prompt. _powerline_reactive_teardown (also run automatically on shell exit) removes the FIFO and unhooks the fd. The binding is zsh-only: ZLE is the one mainstream line editor that can watch an arbitrary fd and redraw the prompt mid-line without a timer.


> LICENSE

MIT. Theme JSON files in powerline/config/themes/ remain under their upstream licenses.


// END OF FILE // PROMPT LOCKED, NATIVE //