# 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 <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. |

## 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 <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`:

```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-<uid>.cache` when XDG
  runtime dir is set, else `state_dir()/prompt-<uid>.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.
