tirith prompt-status is a fast status emitter designed to be called from
your shell prompt on every redraw. It shows the operator's current
protection posture (guarded / warn-only / degraded / off) plus
the currently-active kubernetes / AWS / SSH / sudo context — at a glance.
This doc is the manual-install reference. tirith init --shell <name> --prompt-status does the same thing automatically for the four supported
shells; reach for the snippets below when you'd rather hand-edit your
rc file, or for shells that the auto-install path can't safely splice
(like nushell).
For the longer "what is TIRITH_STATUS" companion piece — the
non-exported shell variable that the shell hook itself sets — see
docs/prompt-status.md. The two docs serve different needs:
| Doc | Audience |
|---|---|
docs/prompt-status.md |
Just want the protection level? Read TIRITH_STATUS. |
docs/prompt-integration.md |
Want everything — protection + context + sudo + ssh — in one line? Run tirith prompt-status from your prompt. |
$ tirith prompt-status --short
[tirith:guarded][aws:prod][kube:payments-prod]
$ tirith prompt-status
tirith: guarded; aws: prod; kube: payments-prod; sudo: session active
$ tirith prompt-status --json
{"schema_version":1,"protection_mode":"guarded","contexts":{"aws":"prod","kube":"payments-prod"},"ssh_remote":false,"sudo_active":true}The JSON envelope is the stable interface — schema_version is pinned
at 1 and additive fields are allowed without bumping it; structural
changes will bump the version. An in-shell wrapper that already forwards
TIRITH_STATUS (the snippets below) can parse the JSON form for its own
formatting. Note that a fully external renderer that shells out as a
separate process (Starship) cannot see the non-exported TIRITH_STATUS and
would read off regardless of output format — see the
Starship section for the export-based approach it needs.
tirith init --shell <name> --prompt-status emits the rc-file snippet
for your shell. It's intended to be eval-ed at the same point you
already eval tirith init:
# ~/.zshrc — single line that loads both the hook and the prompt
eval "$(tirith init --shell zsh --prompt-status)"The snippet is idempotent: a guard variable
(_TIRITH_PROMPT_STATUS_LOADED, $global:_TIRITH_PROMPT_STATUS_LOADED,
etc.) prevents double-wrapping if your rc file is re-sourced.
# ~/.zshrc
setopt PROMPT_SUBST
PROMPT='$(TIRITH_STATUS="${TIRITH_STATUS:-}" tirith prompt-status --short) '"$PROMPT"Use single quotes around the substitution. With double quotes, the
command substitution happens once when PROMPT is assigned — your prompt
then shows a frozen status. With single quotes zsh defers the substitution
to prompt-render time.
Forward TIRITH_STATUS. The shell hooks expose the live status as a
deliberately NON-exported variable, so a bare tirith prompt-status child
process cannot see it (it would always show off). The
TIRITH_STATUS="${TIRITH_STATUS:-}" prefix passes it into the child for that one
call without exporting it session-wide. The ${VAR:-} form (rather than bare
$VAR) keeps the prompt safe under set -u / setopt NO_UNSET when the hook
has not set the variable yet.
# ~/.bashrc
PS1='$(TIRITH_STATUS="${TIRITH_STATUS:-}" tirith prompt-status --short) '"$PS1"Same rules as zsh: single quotes around the substitution, and the
TIRITH_STATUS="${TIRITH_STATUS:-}" prefix to forward the hook's non-exported
status into the child (the ${VAR:-} form is set -u-safe).
# ~/.config/fish/config.fish
function fish_right_prompt
env TIRITH_STATUS="$TIRITH_STATUS" tirith prompt-status --short
endThe env TIRITH_STATUS="$TIRITH_STATUS" prefix forwards the hook's
non-exported status into the child (fish has no bare VAR=val cmd form).
The right prompt keeps tirith's status out of the way of your main
prompt. If you'd rather have it on the left, override fish_prompt
instead and prepend the substitution.
# $PROFILE
function global:prompt {
# The hook stores $global:TIRITH_STATUS (a PowerShell variable, not $env:),
# which a child process cannot see — forward it via $env: for this one call,
# restored in finally so it does not leak into the session.
$prev = $env:TIRITH_STATUS; $env:TIRITH_STATUS = $global:TIRITH_STATUS
try { $line = (& tirith prompt-status --short) 2>$null }
finally { if ($null -eq $prev) { Remove-Item Env:\TIRITH_STATUS -ErrorAction SilentlyContinue } else { $env:TIRITH_STATUS = $prev } }
"$line PS $($executionContext.SessionState.Path.CurrentLocation)> "
}If you already have a prompt function (oh-my-posh, starship, etc.),
prefix its output rather than replacing it.
The nushell hook deliberately exposes no TIRITH_STATUS variable: every
nushell $env entry is exported (inherited by child processes), and nushell
has no non-exported session variable a prompt closure could read, so there is
no safe way to surface a live status. A tirith prompt-status call in a
nushell prompt would therefore always print off. nushell protection is
warn-only regardless — see the nushell note in docs/prompt-status.md. If you
just want a plain prompt, render $env.PWD directly without a tirith segment:
# ~/.config/nushell/config.nu
$env.PROMPT_COMMAND = {|| $"($env.PWD)> "}Starship is an external renderer: it shells out to a separate child
process each prompt, which cannot read the hook's non-exported
TIRITH_STATUS. So a Starship custom module that runs tirith prompt-status would always show off. To surface the status in Starship you
must opt back into exporting the variable yourself (accepting that child
processes then inherit it) and read it directly — export TIRITH_STATUS after
the hook is sourced, then command = "echo $TIRITH_STATUS". The full Starship
recipe (with the custom module TOML) is in docs/prompt-status.md.
On a warm cache (the common case — every prompt redraw within 30s of
the previous one), prompt-status reads a single JSON file from
$XDG_RUNTIME_DIR/tirith/ (Linux) or state_dir() (macOS) and exits.
Measured cold-cache latency on a M-series Mac is around 10 ms; warm
cache is sub-millisecond plus the binary startup overhead (~10 ms in
total for the tirith invocation itself).
If you find the binary-startup overhead distracting on a slow box, a
future plan is to ship tirith prompt-status as a static no-fork
helper. For v1 the milestone is "doesn't measurably slow down a
prompt".
- Location:
$XDG_RUNTIME_DIR/tirith/prompt-<uid>.cachewhen XDG runtime dir is set, elsestate_dir()/prompt-<uid>.cache. The per-uid suffix avoids collisions on multi-user systems. - Perms:
0700on the parent dir,0600on the file. Protection state is per-user information; we don't want it visible to other users. - TTL: 30 seconds. After a
kubectxswap your prompt may lag for up to 30s before re-detecting;tirith context statusalways bypasses the cache. - Invalidation: delete the cache file. The next
prompt-statusinvocation refreshes from authoritative sources.
| Source | Detected? |
|---|---|
TIRITH_STATUS env var |
yes |
TIRITH_SSH_REMOTE env var |
yes |
| Sudo-session file (M8 ch4) | yes |
Kubeconfig current-context |
yes |
AWS AWS_PROFILE / default |
yes |
gcloud config list |
no — too slow for the prompt budget; use tirith context status |
az account show |
no — too slow for the prompt budget; use tirith context status |
The gcloud / az shell-outs are skipped specifically because they can
take 100ms – 1.5s in the worst case and would blow the prompt latency
budget. The richer detection still runs from tirith context status
and from the engine hot path when a gcloud … / az … command is
actually invoked.
prompt-status is operator-trust, not adversary-resistant. The labels
file, the kubeconfig, the AWS env vars are all user-writable — an
attacker who already has shell access can re-label any context. This
is a fast operational indicator, not a security boundary.