# Wiring `tirith prompt-status` into your shell prompt `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 --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. | ## Output forms ```sh $ 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](#starship) section for the export-based approach it needs. ## Auto-install (recommended) `tirith init --shell --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`: ```sh # ~/.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. ## Manual install — per shell ### zsh ```zsh # ~/.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. ### bash ```bash # ~/.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). ### fish ```fish # ~/.config/fish/config.fish function fish_right_prompt env TIRITH_STATUS="$TIRITH_STATUS" tirith prompt-status --short end ``` The `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. ### PowerShell ```powershell # $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. ### nushell 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: ```nu # ~/.config/nushell/config.nu $env.PROMPT_COMMAND = {|| $"($env.PWD)> "} ``` ### Starship 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`. ## Latency 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". ## Cache semantics - **Location:** `$XDG_RUNTIME_DIR/tirith/prompt-.cache` when XDG runtime dir is set, else `state_dir()/prompt-.cache`. The per-uid suffix avoids collisions on multi-user systems. - **Perms:** `0700` on the parent dir, `0600` on the file. Protection state is per-user information; we don't want it visible to other users. - **TTL:** 30 seconds. After a `kubectx` swap your prompt may lag for up to 30s before re-detecting; `tirith context status` always bypasses the cache. - **Invalidation:** delete the cache file. The next `prompt-status` invocation refreshes from authoritative sources. ## What `prompt-status` detects (and what it doesn't) | 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. ## Honest scope `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.