Metadata-Version: 2.4
Name: tetanus-rmm
Version: 0.1.9
Summary: Support TUI for the RMM server: agents, remote desktop launch, shell and scripts
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: textual>=1.0
Requires-Dist: httpx>=0.27
Requires-Dist: keyring>=25
Requires-Dist: websockets>=13
Requires-Dist: pyte>=0.8
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"

# tetanus-rmm: support TUI

A terminal app for support engineers. It runs on Linux, macOS and Windows,
and talks only to the RMM server's HTTPS API. It never connects to agents
directly. What it does:

- **Sign in** to any server with username, password and TOTP. The session
  token is stored in the OS keyring, so you stay signed in across launches.
- **Agents:** a live table with a green/red online/offline dot, and columns
  you choose: hostname, status, logged-in user, IP address, public IP,
  uptime, groups, last seen, CPU, RAM, disk, active sessions, type and agent
  ID.
- **Remote desktop:** starts the native viewer for the selected agent. The
  TUI doesn't render video itself. Quitting the TUI closes every viewer
  window it opened. The viewer's tabs show the agent's status and have
  command buttons (`cmd`, `ncpa.cpl`, `mstsc`, ...) that you choose in
  the TUI (`v`), plus file upload and download. Each viewer is drawn in
  the colour theme picked in the TUI when it starts.
- **Bitwarden:** unlock your vault once (`b`) and every viewer started
  afterwards can search it and type usernames, passwords and one-time codes
  on the remote machine. See [Bitwarden](#bitwarden).
- **Updates itself:** when the server publishes a newer TUI, you are offered
  it after signing in (or with `U`). See [Updating](#updating).
- **Company branding** (admins only): your name, logo and accent colour on
  what the people you support see. See [Company branding](#company-branding).
- **Shell console:** interactive PowerShell on the agent, in a pane. Needs
  **no viewer**.
- **Script runner:** runs a command or script on one or more agents, or on
  whole agent groups, and shows each agent's stdout, stderr and exit code.
  Also needs **no viewer**.
  Saved scripts live in a small local library.
- **Audit log:** recent entries and a chain check (admins and auditors).
- **New agent:** a single-use download link for installing an agent. On
  Windows that's an MSI which installs and enrolls it unattended, optionally
  straight into agent groups. The MSI can be saved from the TUI.
- **Deployment MSI:** one MSI for many PCs, to push with Group Policy or
  Intune. It installs silently and enrolls each PC with a reusable key
  that you can revoke.
- **Quick assist:** help someone whose computer has no agent, for one
  session: a six-digit code they type into a program they download from
  the server. The viewer opens when they have.
- **Groups:** view groups and their members. Admins can also create, rename,
  delete and fill them. The group list beside the agent table filters it,
  and the search box above it filters by hostname, IP address or group
  name.
- **Users** (admins only): add and remove users, change their role, choose
  which agents a support engineer may work on, and set their password or
  reset their TOTP secret.

## Install

For staff: open your server's install page, `https://<server>:8443/install`,
download the TUI there and follow its steps (an administrator fills the page
with `scripts/build-clients.sh`). Then run `tetanus-rmm` and sign in. No config
file, certificate or viewer has to be set up by hand: see
[First sign-in](#first-sign-in). The install itself is one command, with
[uv](https://docs.astral.sh/uv/) (which fetches Python itself) or pipx
(Python 3.11 or newer):

```sh
uv tool install tetanus_rmm-0.1.9-py3-none-any.whl     # or: pipx install tetanus_rmm-…whl
tetanus-rmm
```

For development, from the repository, with uv:

```sh
cd tui
uv venv && uv pip install -e .          # add '.[dev]' for the tests
.venv/bin/tetanus-rmm --help
```

With pip:

```sh
cd tui
python -m venv .venv
.venv/bin/pip install -e .              # Windows: .venv\Scripts\pip
.venv/bin/tetanus-rmm
```

`python -m tetanus_rmm` works too. Dependencies: `textual`, `httpx`, `keyring`,
`websockets` (the shell), `pyte` (terminal emulation for the shell pane).

The keyring uses Windows Credential Manager, the macOS Keychain, or Secret
Service on Linux (GNOME Keyring or KWallet). If no keyring is available, the
TUI still works, but you have to sign in on each launch; it tells you when
this happens.

## First sign-in

Type the server's address in **Server URL** (`rmm.example.com:8443`;
`https://` is assumed), then your username, password and TOTP code. The
server is remembered for next time.

**Trusting the server.** A server usually has its own certificate authority
(from `server gen-certs`), which your computer has never seen. The first
time you sign in to such a server, before anything is sent to it, the TUI
shows the SHA-256 fingerprint of that CA and asks whether to trust it.
Compare it with the fingerprint from your administrator (the server prints
it when the certificates are made and logs it at every start: `CA
certificate fingerprint`; or `openssl x509 -in ca.crt -noout -fingerprint
-sha256`). Once accepted, the CA is kept in the data directory
(`servers/<host>_<port>/ca.crt`) and that server is only ever verified
against it: you are not asked again.

If the server later presents a certificate that the accepted CA did not
sign, the TUI refuses to sign in rather than ask again. If the server's
certificates really were replaced, forget the old CA and accept the new one:

```sh
tetanus-rmm --forget-ca --server-url https://rmm.example.com:8443
```

A server whose API has a publicly trusted certificate (`RMM_API_TLS_CERT`)
needs no prompt at all. Setting `ca_path` turns the prompt off: the server
is then verified against that file only.

**The viewer.** The first time you start a remote desktop session the TUI
downloads the server's viewer build for your platform (Linux or Windows,
x86-64) into the data directory (`viewer/`), checks it against the SHA-256
the server publishes, and starts it. It is fetched again whenever the server
publishes a new build, so it always matches the server. The viewer is given
the CA accepted above. If the server publishes no viewer for your platform,
`viewer` on `PATH` is used; `viewer_path` names another.

## Configuration

Every setting is optional, and so is the file. Settings come from a TOML
file, and command-line flags override them. The file lives at:

| OS | Default path |
|---|---|
| Linux | `$XDG_CONFIG_HOME/tetanus-rmm/config.toml` (usually `~/.config/tetanus-rmm/config.toml`) |
| macOS | `~/Library/Application Support/tetanus-rmm/config.toml` |
| Windows | `%APPDATA%\tetanus-rmm\config.toml` |

Pass `--config PATH` or set `TETANUS_RMM_CONFIG` to use a different file.

Up to 0.1.1 the TUI was called `rmm-tui`. The first run moves that name's
config and data directories and saved sign-in over to `tetanus-rmm`; remove
the old tool with `uv tool uninstall rmm-tui`.

```toml
server_url = "https://rmm.example.com:8443"   # the HTTPS API
ca_path = "/etc/rmm/ca.crt"                   # optional: CA to trust for the server
viewer_path = "/opt/rmm/viewer"               # optional: native viewer binary
quic_addr = "rmm.example.com:4433"            # optional, see below
poll_interval = 5                             # seconds between agent refreshes
scripts_path = "~/rmm-scripts.json"           # optional; default is in the data dir
viewer_font_size = 9                          # optional; the viewer's text size
bw_path = "/usr/local/bin/bw"                 # optional: Bitwarden's CLI
check_updates = true                          # optional: look for a newer TUI at sign-in
```

| Setting | Flag | Default | |
|---|---|---|---|
| `server_url` | `--server-url` | `https://localhost:8443` | Must be `https://`. |
| `ca_path` | `--ca` | *(the CA accepted at first sign-in, else the system trust store)* | PEM CA certificate to verify the server against, instead of asking. Also given to the viewer. |
| `viewer_path` | `--viewer` | *(the server's build, else `viewer` on `PATH`)* | A viewer to use instead of the downloaded one, e.g. `target/release/viewer`. |
| `quic_addr` | `--quic-addr` | API host, port `4433` | The server's QUIC listener, for the viewer. The name is resolved to an address (IPv4 preferred), and the viewer checks the certificate against the name. |
| `poll_interval` | | `5` | At least 1. |
| `scripts_path` | | data dir `/scripts.json` | The saved-script library. |
| `viewer_font_size` | | *(viewer default, 9)* | Text size of the viewer's header and panels (6–32), until you pick one in a viewer (**Text** menu, or `Ctrl+Alt+Shift+-`/`=`). From then on viewers start at the size you last picked, remembered in `viewer.json` in the data directory; delete that file to go back to this setting. |

| `check_updates` | | `true` | Whether to look for a newer TUI on the server after signing in. `U` looks either way. See [Updating](#updating). |
| `bw_path` | | `bw` on `PATH` | Bitwarden's command line client, for [the vault](#bitwarden). On Windows, name the `.exe` (or npm's `bw.cmd`) by its full path. |

Relative paths in the file are relative to the file. Unknown keys are an
error, so typos get caught.

TLS is always verified: against `ca_path` if set, otherwise against the CA
accepted for the server at [first sign-in](#first-sign-in), otherwise
against the system trust store. There is no option to turn verification off.

The TUI also remembers the server you last signed in to, your agent-table
columns, your colour theme and the themes you made, in `state.json` in the
data directory.

Logs go to the data directory (`~/.local/share/tetanus-rmm/`,
`~/Library/Application Support/tetanus-rmm/` or `%LOCALAPPDATA%\tetanus-rmm\`), never
to the terminal: `tetanus-rmm.log` for the TUI and `viewer.log` for viewers it
launches.

## Running

```sh
# Against a dev server started from the repo root (see the main README):
tetanus-rmm --server-url https://localhost:8443 --ca ../dev-certs/ca.crt \
        --viewer ../target/debug/viewer
```

The server is `--server-url` if given, otherwise the one you last signed in
to, otherwise `server_url` from the config file. The login screen shows it in
the **Server URL** field, and you can type another. `https://` is assumed if
you leave it out. Each server keeps its own saved session, and the viewer's
`quic_addr` is only kept when the new server has the same host.

On start, the TUI checks the saved session with `GET /api/me`:
- If the server accepts it, you go straight to the agent table.
- If the token has expired or the server rejects it, the token is removed and
  you get the login screen.
- If the server can't be reached, the token is kept.

The server has no refresh endpoint, so a session lasts the server's
`RMM_SESSION_TTL_SECS` (12 h by default); then you sign in again. `l` signs
out (the server revokes the session and the token is removed from the
keyring). `tetanus-rmm --logout` only forgets the saved token locally; the
server-side session then expires on its own.

### Keys

**Agent table**

| Key | Action |
|---|---|
| `m` or `F10` | Open the menu bar (`←`/`→`: other menus, `Enter`: choose, `Esc`: close) |
| `d` or `Enter` | Remote desktop: launch the viewer for the selected agent |
| `s` | Shell console on the selected agent |
| `r` | Script runner (the selected agent is pre-selected) |
| `n` | New agent: download link / MSI (admins and support engineers) |
| `i` | Deployment MSI: a reusable MSI for Group Policy or Intune (admins and support engineers) |
| `h` | Quick assist: a one-time session by six-digit code (admins and support engineers) |
| `/` | Search by hostname, IP address or group name (`Enter`: back to the table, `Esc`: clear) |
| `f` | Jump to the group list |
| `k` | Classify the selected agent as server, desktop or other (admins) |
| `g` | Groups |
| `u` | Users: accounts, roles, access and passwords (admins; hidden otherwise) |
| `a` | Audit log (admins and auditors) |
| `c` | Choose columns |
| `p` | Show or hide the stats panel (kept between runs) |
| `t` | Choose the colour theme (kept between runs; `tetanus` is the default) |
| `e` | Theme editor: make, change and delete themes of your own |
| `v` | Viewer buttons: the remote viewer's command buttons |
| `b` | Bitwarden: unlock the vault for the viewers, or lock it again |
| `U` | Check for updates: is there a newer TUI on the server? |
| `B` | Company branding: your name, logo and colour on what users see (admins) |
| `F5` | Refresh now (the table also refreshes every `poll_interval`) |
| `l` | Sign out |
| `q` | Quit |

The **menu bar** over the table groups the same actions into Agent, View,
Manage and Session menus, each item with its key; click a menu or press `m`.

The **stats panel** under the table shows the selected agent's CPU, memory
and system disk, each with a graph of the last hour, and its uptime,
sessions, signed-in users and fixed disks. The figures come with the agent
list, so they appear at once; the graphs are fetched when the selection has
rested on an agent for a moment (a bar at the panel's top right shows while
they load), and are kept, so moving through the list or back to an agent
asks the server for nothing. A gap in a graph is time the agent was not
connected. `p` hides the panel, and hidden it fetches nothing. An older server
keeps no history: the panel then shows the figures alone.

The **theme editor** (`e`) lists every theme on the left. Pick one to start
from, change its colours (`#RRGGBB`) and the whole screen takes them on as
you type. **Save** (`Ctrl+S`) keeps it under the name in the form and makes
it your theme; a built-in theme is never changed, so starting from one makes
a copy under a new name. Your own themes are marked "yours" and can be
edited again or deleted. `Esc` leaves, putting back the theme you last saved
there or else the one you had.

The footer shows only the essentials (menu, remote desktop, shell, search,
quit); the other keys still work and are listed in the menus. Remote desktop
and shell are only offered where you may use them on the **selected agent**
(the menus grey out what you may not do). A
**support engineer** only sees the agents an admin has granted them, and
only gets remote desktop, shell or scripts where their grant covers that
(the server says so per agent). An **auditor** sees the agent table and the
audit log, but no control actions. The server enforces the same rules: a
refused request returns 403 and is audited as `permission.denied`.

The **Status** column has a green dot for online and a red one for offline
(grey: revoked). It says `online (ws)` for an agent connected over the
WebSocket fallback (its network blocks UDP). **Groups** lists its agent
groups.

**Columns** (`c`): tick columns with `Space`, reorder with `Shift+↑`/`Shift+↓`
(or the ▲/▼ buttons), then **Apply**. **Defaults** restores the standard
set. Hidden by default: Public IP and Agent ID.
- **Classification:** server, desktop or other. It follows what the agent
  detects (a Windows Server edition, or a headless machine, is a server;
  anything else a desktop; `Other` until the agent has reported) until an
  admin sets it with `k`; then it is marked `*` (e.g. `Server*`). Choosing
  **Automatic** hands it back to the agent. Changes are audited as
  `agent.classify`.
- **OS:** the operating system and version the agent reports, e.g.
  `Windows 11 Pro (build 26100)`.
- **Logged-in user:** everyone signed in to the machine (console or remote
  desktop, including disconnected sessions), or `none`.
- **IP address:** the agent's own address on its route to the server (its
  LAN address behind NAT).
- **Public IP:** the address the server sees it connect from.

Logged-in user and uptime show `–` while an agent is offline, since the last
values are stale. Logged-in user, IP address and OS need agents at protocol 9;
older agents show `–` until they update.

**Search:** type in the box above the table (`/` jumps to it) to show only
agents with the text in their hostname, IP address (their own or their
public one) or the name of a group they are in, ignoring case.

**Group list:** the list beside the table (`f` jumps to it) shows all
agents, those in no group, or one group's members, each with its agent
count; moving through it filters the table at once, and `Enter` goes back to
the table. It works together with the search, holds across refreshes, and
goes back to all if the group is deleted. The status line then says e.g.
`3 of 12 agents`.

**New agent** (`n`):
1. Pick the platform, and check **Server address** (`host:port` the agent
   connects to) and **TLS name** (a name on the server's certificate). They
   default to the server this TUI talks to: `quic_addr` if set, else its host
   on port 4433, and the host name you signed in with. Change the address
   when agents reach the server differently from you, e.g. an IP on their
   network.
2. Choose how long the link stays valid (1 hour, 24 hours or 7 days). As an
   admin, you can also tick groups the agent joins when it enrolls.
3. Press **Create link** (`Ctrl+G`).

You then get:
- **MSI link** (Windows): anyone with it can download an installer that
  installs the agent service and enrolls it. Run it by double-clicking, or
  `msiexec /i rmm-agent.msi /qn`.
- **Save MSI:** downloads it to the path shown (default
  `~/Downloads/rmm-agent.msi`, never overwriting).
- **Agent binary** link and the matching manual install command.

**Copy** puts a value on the clipboard (via your terminal, OSC 52). A link is
single use: once an agent enrolls with it, it's dead.

**Deployment MSI** (`i`): one MSI for as many PCs as you push it to. It
carries a deployment key instead of a single-use link, so every PC that
installs it enrolls as its own agent.
1. Say what the key is for (e.g. the customer), and check **Server address**
   and **TLS name** as for a new agent.
2. Choose how long the key is valid (30 days, 90 days, 1 year or never). As
   an admin, you can also tick groups the agents join.
3. Press **Create key** (`Ctrl+G`), then **Save MSI**. The key is shown only
   this once: the MSI cannot be fetched again later, so make a new key if
   you lose it.

Deploying it:
- **By hand or script:** `msiexec /i rmm-agent-deploy.msi /qn /norestart`
  from an elevated prompt. No dialogs; the agent service is installed,
  started and enrolled.
- **Group Policy:** put the MSI on a share that *Domain Computers* can read,
  then *Computer Configuration → Policies → Software Settings → Software
  installation → New → Package*, **Assigned**. It installs at the next boot.
- **Intune:** *Apps → Windows → Add → Line-of-business app*, upload the MSI,
  and assign it as **Required** to a device group. It installs in the device
  context.

The table at the top lists the keys with how many agents each has enrolled.
`Delete` revokes the highlighted key: its MSI enrolls nothing more, and the
agents already enrolled keep working. Anyone holding the MSI can enroll
agents until then, so treat it like a password.

Good to know:
- Installing it again on a PC that is already enrolled with this server
  (a redeployment, or uninstall then install) keeps the PC's existing agent.
  A PC whose agent was deleted on the server stays locked out: remove
  `C:\ProgramData\RMM\agent` on it before deploying again.
- Agents update themselves, so don't replace the package with a newer MSI in
  Intune or Group Policy: Windows Installer refuses a second MSI where the
  agent is already installed.
- The server's name is looked up when the MSI runs, so the PC needs the
  network (and DNS) at that point.
- It needs agent 0.1.6 or later published on the server.

**Groups** (`g`): the groups, their descriptions and counts, and the
highlighted group's members with online dots. Admins get:
- `n` new
- `e` rename / change description
- `m` members: tick agents with `Space`
- `Del` delete: its agents stay but lose access granted through it

**Users** (`u`, admins only): every user with their role and what they can
reach.
- `n` new user: username, password (at least 12 characters) and role. The
  new **TOTP secret** and its `otpauth://` URL are then shown **once**, to
  pass to the user for their authenticator app.
- `r` role: admin, support engineer or auditor. It applies to the user's
  next request. The server keeps at least one admin.
- `a` access (support engineers): their grants. `n` adds one, on every
  agent, a group or a single agent, for any of remote desktop, shell,
  scripts and file transfer; `Del` removes the highlighted one. Without a
  grant an engineer sees no agents.
- `p` password: set a new one. The user is signed out everywhere (changing
  your own keeps the session you are in).
- `t` reset TOTP: a new secret, shown once like a new user's. The old
  authenticator entry stops working and the user is signed out.
- `Del` delete: not yourself, and not the last admin.

Others see the same view read-only; the server enforces this too.

**Shell console:** type a command in the input box and press Enter. The
output pane is a VT terminal emulator, so colours and cursor movement render
correctly, and scrollback is kept.
- `Ctrl+C` sends an interrupt to the remote shell.
- `Up`/`Down` recall previous commands.
- `Esc` closes the console and ends the shell.
- The PTY follows the pane's size, so resizing your terminal resizes it too.

**Script runner:**
1. Tick targets with `Space`: agents (only those you may run scripts on),
   and/or groups. A group's members are decided by the server when the run
   starts, so an agent that joined it meanwhile is included.
2. Type a script, load a saved one with `Enter`, or press **New**
   (`Ctrl+N`) to create a library entry (name, optional timeout, body). It
   is saved and loaded into the editor.
3. Optionally set a timeout (1–3600 s; default 300).
4. Press `Ctrl+R`.

Each agent then shows its status, exit code and duration. The highlighted row
shows its stdout and stderr. Name a script and press **Save** to keep it, or
**Delete** to remove the highlighted one. The library is plain JSON.

A run is one request for all selected agents, so the server audits it as a
single run (`script.run` / `script.complete`, with one `run_id`). Results
appear together when the slowest agent finishes; until then every target
shows *running…*.

### Remote desktop

The TUI asks the server for a single-use viewer token (valid 60 s), then
starts:

```text
viewer --server <ip:port> --server-name <host> --ca <ca_path>
```

The token is passed in the `RMM_VIEWER_TOKEN` environment variable, not on
the command line, so other local users can't read it from the process list.
The viewer runs detached from the TUI, and you can open several at once.

For its panels the viewer also gets `--api-url <server_url>`, your
session token in `RMM_API_TOKEN` (the environment again), and one
`--command "LABEL=COMMAND"` per button. It acts through the API as you, so
the server applies your access and audits what it does. It also gets
`--remember-font-size <data dir>/viewer.json`: a text size you pick in a
viewer is saved there, and the next viewer starts at it (see
`viewer_font_size`). The display mode you pick (Scale, Stretch, Fill,
Original size) and the tab you leave open are saved there too, and viewers
start as the last one was left.

The viewer's header shows the machine's name and holds the Display, FPS,
Monitor and Text menus and the session's buttons: Refresh, Full screen,
Ctrl+Alt+Del and Disconnect. The rail down the right edge has three tabs,
each opening a panel beside the picture (click the open one to close it,
or press `Ctrl+Alt+Shift+P`): **Vault** ([the vault](#bitwarden) and the
password the remote user lends), **Status** (what the agent says about its
machine) and **Tools** (your command buttons and file transfer; a file
dropped on the window is uploaded too, except under Wayland). The footer
says how the session is carried, how fast, who is signed in and how full
the first disk is.

The viewer is drawn in the TUI's colour theme: the one picked when the
viewer starts (`t`, or one of your own from `e`) goes to it in the
`RMM_VIEWER_THEME` environment variable. A viewer already open keeps the
colours it started with, and with a theme of the terminal's own colours
(Textual's `ansi` themes) viewers use the default theme's.

**Quick assist** (`h`): for a computer with no agent installed.
1. Tell the user the page address shown (**Copy** puts it on the clipboard).
   They download quick assist there and open it; it makes them read a
   warning about scams for five seconds first.
2. Read them the six-digit code. It works once and expires after ten
   minutes; `Ctrl+G` makes a new one.
3. When they have typed it, the viewer starts by itself and they are asked,
   with your username, whether to allow you.

You get remote desktop and file transfer, no shell, scripts or command
buttons. The session lasts until they close quick assist. Until then their
computer is in the agent table, so if you close the viewer, press `d` on it
to open another (they are asked again).

**Viewer buttons** (`v`): the command buttons shown in the viewer's Tools
tab. Each starts its command on the agent's desktop as the signed-in
user, like the Run dialog: `cmd`, `ncpa.cpl`, `mstsc /v:server01`,
`services.msc`... Add one with a label and a command (**Enter** in the
command field adds it too), remove the highlighted one with **Remove** or
`Delete`, reorder with ▲/▼ or `Shift+↑`/`Shift+↓`, **Defaults** restores
the standard set (Command prompt, Network connections, Remote desktop, Task
manager, Services, Event viewer), and **Save** keeps the list in
`state.json`. Viewers started afterwards show it; with an empty list the
tab has no buttons.

### Bitwarden

The viewer's **Vault** tab searches your Bitwarden
vault and has the agent type a username, password or one-time code where
the remote keyboard focus is: at the lock screen and UAC prompts too.

It uses Bitwarden's own command line client. Install
[`bw`](https://bitwarden.com/help/cli/) and sign in once, in a terminal:

```sh
bw login          # self-hosted: `bw config server https://...` first
```

Then press `b` in the TUI and type your master password. Every viewer
started from then on has the vault unlocked. A viewer started before that
(or after the vault was locked) shows an **Unlock vault** button instead,
which asks for the master password for that window only.

Unlocked, the tab has a search field with the items found under it.
Click the field (or press `Ctrl+Alt+Shift+B`) so that keys go to it rather
than to the remote machine, type part of an item's name and press Enter.
Pick an item with the arrow keys, the wheel or the mouse, then **Type
username**, **Type password** or **Type TOTP** (`Ctrl+U`, `Ctrl+P`, `Ctrl+T`). Once a value is
typed the keyboard goes back to the remote machine, as it does with Esc or
a click on the picture; the search and its results stay where they are.

What is kept, and where:

- **Your master password: nowhere.** It is passed to one `bw unlock`, in
  that process's environment (never on a command line), and dropped.
- **The session key** `bw unlock` returns: in the TUI's memory, and in the
  environment of the viewers it starts. It is never written to disk. `b`
  again locks the vault, as do signing out and quitting the TUI, and that
  ends the viewers' access too. (`bw lock` is not per program: it also
  locks the vault for a `bw` session in another terminal.)
- **Search results:** names, usernames and addresses only. A password or
  code is fetched from `bw` when you have it typed, sent to the agent
  end-to-end encrypted, and wiped.
- **The audit log** records that it happened (`vault.typed`): who, on which
  agent, which kind, and the vault item's name. Never the value. An agent
  types nothing the server cannot audit, and an agent that predates this
  does not type vault text at all (the viewer says so): update it.

### Company branding

What the person at a supported computer sees is drawn in TetanusRMM's own
look: the request to allow a session, the prompt to lend a password, the
bar at the top of the screen while someone is connected (with its **End
session** button), the tray icon and its flyout, and quick assist. All of
it follows Windows' light or dark setting.

An admin can put the company's own branding on it with `B` (Manage menu):

- **Name**, up to 48 characters. Requests then come "From *name*", and the
  agent is "*name* Support Agent" in the tray, its toasts and About.
- **Accent colour**, as `#RRGGBB`, in place of TetanusRMM's rust on buttons,
  icons and progress bars. It has to be dark enough for white text; the
  server says so if it is not. Red for a live session, and for ending one,
  is never replaced.
- **Logo**, a PNG of 16 to 512 pixels a side and at most 128 KiB, in place
  of the TetanusRMM mark: in window title bars, the session bar, the tray
  and the flyout. Square works best. Leave its path empty to keep the one
  already set, or tick **No logo** to go back to the mark (on your colour).

**Save** applies it: connected agents show it at once and remember it for
the next start, others when they next connect. Quick assist downloads and
agent installers made afterwards carry it (the installer's entry in
Windows' Apps list takes the name and logo), and the server's `/install`
and `/assist` pages wear it. **Reset to TetanusRMM** removes it.

Layout, type, icons and the safety wording are the same for everyone, and
"Powered by TetanusRMM" stays in the tray flyout, About and the pages'
foot. The programs' file names and their own icons are TetanusRMM's.
Setting and resetting is in the audit log as `branding.update`.

### Updating

The server publishes one TUI build (the one its install page offers).
After you sign in, the TUI asks the server which, and if it is newer than
the one running it offers to update: **Update and restart** downloads the
wheel from the server, checks it against the SHA-256 the server gives,
closes the TUI (and the viewers it opened), installs it the way the TUI was
installed (`uv tool`, `pipx`, or `pip` in its environment) and starts the
TUI again. **Later** leaves things as they are until the next sign-in.

`U` checks at any time; `check_updates = false` in the config turns the
check at sign-in off. An older TUI can lack what the server and viewer
have since learned, so it is worth taking.

Where the TUI cannot install over itself it downloads the wheel and prints
the command to run: on Windows (a running program cannot be replaced), and
when it runs from a source checkout (update that with git). The same
happens if the install fails; the version you have keeps working.

## Tests

```sh
pip install -e '.[dev]'
pytest               # API client (mocked), keyring flow, script results,
                     # shell protocol + terminal, viewer command, headless UI
ruff check src tests && ruff format --check src tests
```

The tests don't need a server, keyring or display. The API is served by an
`httpx.MockTransport`, the keyring is an in-memory stand-in, and the UI runs
headless under Textual's test pilot.
