# Configuration

bee-tui's configuration is a single TOML file. With no config
at all, the cockpit talks to `http://localhost:1633` against
a node with no auth token — the most common dev setup. As
soon as you have a real Bee node with a Bearer token, or
multiple nodes you want to switch between, you'll want a
config.

## Where the config lives

bee-tui looks for `config.toml` in this order, taking the
first hit:

1. The path in the `BEE_TUI_CONFIG` environment variable, if set
2. `$XDG_CONFIG_HOME/bee-tui/config.toml`
3. `~/.config/bee-tui/config.toml`
4. (built-in default — single `local` node, no token)

The directory does **not** need to exist before launch;
bee-tui only reads, never writes. Create it yourself the
first time:

```sh
mkdir -p ~/.config/bee-tui
$EDITOR ~/.config/bee-tui/config.toml
```

## Minimal example

```toml
[[nodes]]
name    = "prod-1"
url     = "http://10.0.1.5:1633"
token   = "@env:BEE_TOKEN_PROD1"
default = true
```

That's the whole file: one node, named `prod-1`, with its
auth token resolved from `$BEE_TOKEN_PROD1` at startup.

## Schema reference

### `[[nodes]]` — the node array

You can declare any number of `[[nodes]]` entries. Exactly
one should have `default = true`; that's the profile bee-tui
loads on launch. The others are reachable via `:context <name>`.

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | Identifier shown in the top bar and used by `:context <name>`. Keep short — `prod-1`, `lab`, `staging`. |
| `url` | string | yes | Base URL of the Bee node, e.g. `http://localhost:1633` or `https://bee.example.com:1633`. Trailing slash optional. |
| `token` | string | no | Bearer token. May be the literal token string, or `@env:VAR_NAME` to resolve from an environment variable at startup. Empty / missing = no auth header sent. |
| `default` | bool | no | If true, this profile is loaded on launch. Exactly one entry should have it. |

### `[ui]` — UI preferences

```toml
[ui]
theme           = "default"
ascii_fallback  = false
```

| Field | Type | Default | Description |
|---|---|---|---|
| `theme` | `"default"` \| `"mono"` | `"default"` | Slot-based palette. `default` is vibrant green/yellow/red. `mono` is greyscale only — useful on terminals where colour is muted or distracting, or when piping to a recording tool that doesn't preserve colour. |
| `ascii_fallback` | bool | `false` | If true, every component renders ASCII glyphs (`OK / X / ! / > / # / .`) instead of Unicode (`✓ ⚠ ✗ ▶ ▇ ░`). Equivalent to passing `--ascii` on the command line. Use on Windows Terminal pre-Win11, screen readers, or SSH chains that mangle Unicode. |
| `refresh` | `"live"` \| `"default"` \| `"slow"` | `"default"` | Polling cadence preset. `live` matches the original 2 s health / 5 s topology+tags rates (chatty; use when actively diagnosing). `default` doubles the fast-tier intervals (4 s / 10 s) — about half the request volume, no perceptible loss for monitoring. `slow` is minimal (8 s / 20 s / 60 s / 120 s) for leave-it-open-all-day operators. |

Unknown values for `theme` fall back to `"default"` with a
single tracing warning so a typo doesn't break startup.

### `[bee]` — spawn Bee from bee-tui (optional)

When set, bee-tui launches Bee itself before opening the
cockpit, captures its stdout + stderr to `$TMPDIR/bee-tui-spawned-<ts>.log`,
waits for `/health` to respond, then enters the TUI. Quit
sends SIGTERM to Bee's process group; a 5-second grace window
is followed by SIGKILL if needed.

```toml
[bee]
bin    = "/home/operator/bee/dist/bee"
config = "/home/operator/bee/testnet.yaml"
```

| Field | Type | Required | Description |
|---|---|---|---|
| `bin` | path | yes | Path to a `bee` binary. Bee is invoked as `<bin> start --config <config>`. Relative paths resolve against the working directory. |
| `config` | path | yes | Path to the Bee YAML config the binary should be started with. |

If `[bee]` is **omitted**, bee-tui falls back to its legacy
mode: connect to whatever's already running on the URL of the
default `[[nodes]]` entry. Use this when Bee runs under
systemd / docker / k8s — bee-tui shouldn't spawn it then.

If Bee crashes mid-session, a red `bee exited (code N)` chip
appears in the top bar. There is no auto-restart — the
operator decides whether to investigate (the captured log is
the place to start) or quit and relaunch.

CLI flags `--bee-bin` and `--bee-config` override the
`[bee]` block. Both must be set together; setting only one
errors at startup.

### `[metrics]` — Prometheus scrape endpoint (optional)

```toml
[metrics]
enabled = true
addr    = "127.0.0.1:9101"   # default; only opt into 0.0.0.0 if you mean it
```

Off by default. When enabled, bee-tui serves Prometheus
exposition-format gauges on the configured address — the unique
synthesised metrics (worst-bucket per batch, depth-vs-radius gap,
predicted TTL, pending-tx age, bee-tui's own request percentiles)
that Bee's own `/metrics` doesn't expose. See the [Prometheus
metrics reference](./reference/metrics.md) for the full list.

### `[economics]` — cost-context oracles (optional)

```toml
[economics]
gnosis_rpc_url      = "https://rpc.gnosischain.com"   # required by :basefee + Market tile gas line
enable_market_tile  = true                            # default false; turns on the S3 SWAP Market tile
```

Two facets:

- **Verbs** (`:price`, `:basefee`) work without the section being
  present — `:price` always hits the public Swarm token service;
  `:basefee` errors with a clear "configure
  `[economics].gnosis_rpc_url`" hint when unset.
- **Market tile** on S3 SWAP is opt-in via `enable_market_tile = true`.
  When on, bee-tui polls `tokenservice.ethswarm.org` (and, if
  `gnosis_rpc_url` is set, the Gnosis RPC) every 60 s and renders a
  one-line tile showing `BZZ ≈ $X.XXXX` and `gas: B base + T tip = N
  gwei`. Off by default — fresh installs make no outbound traffic.

### `[durability]` — chunk-graph walker tuning (optional)

```toml
[durability]
swarmscan_check  = true                                          # default false
swarmscan_url    = "https://api.swarmscan.io/v1/chunks/{ref}"   # default
```

Off by default — fresh installs make no outbound traffic to a
third-party indexer. When `swarmscan_check = true`, every
completed `:durability-check` (single-shot or via the
`:watch-ref` daemon) probes `swarmscan_url` for an independent
"does the network see this ref?" answer. The literal `{ref}`
substring in the URL template is replaced with the hex-encoded
reference at request time; the probe times out after 5 s.

The result lands in `DurabilityResult.swarmscan_seen` and shows
up in:

- The verb's summary line: `swarmscan: seen` /
  `swarmscan: NOT seen` (or omitted when the probe was skipped or
  errored).
- The S12 Watchlist row detail: ` · scan: seen` /
  ` · scan: NOT seen`.
- `--once durability-check`'s JSON: `swarmscan_seen` field
  (`true` / `false` / `null`).

A `NOT seen` answer doesn't flip the `is_healthy()` flag — it's
an independent signal, useful for catching cases where the local
node returns a chunk from cache that no peer in the network
actually still has. Pair with `[alerts].webhook_url` to ping on
gate transitions and use `swarmscan_seen` as a manual sanity
check.

### `[pubsub]` — pubsub history file + rotation (optional)

```toml
[pubsub]
history_file   = "/var/lib/bee-tui/pubsub.jsonl"  # off by default
rotate_size_mb = 64    # active file rolls over at this size; 0 disables (default 64)
keep_files     = 5     # retain .1 .. .5; older rotations unlinked (default 5)
```

Off by default — fresh installs don't write any pubsub messages
to disk. When `history_file` is set, every PSS / GSOC frame
delivered to S15 is also appended to the JSONL file (one
JSON-encoded message per line) so overnight subscriptions can be
analysed offline. The file is created with mode `0600`
(owner-only) since payloads can be sensitive on multi-user hosts.

Rotation keeps disk usage bounded. When the active file crosses
`rotate_size_mb` MiB, bee-tui renames it to `<path>.1` (older
rotations shift to `.2`, `.3`, …, `.keep_files`; oldest beyond
`keep_files` is unlinked) and re-opens `<path>` empty.
Concurrent watchers serialise through the same mutex that orders
appends, so no rename races. Set `rotate_size_mb = 0` to disable
rotation (file grows unbounded).

Pair with [`:pubsub-replay <path>`](./commands/bar.md) to load a
prior session's JSONL back into S15 for visual analysis without
restarting any subscription.

### `[alerts]` — webhook ping when a health gate flips (optional)

```toml
[alerts]
webhook_url    = "https://hooks.slack.com/services/T000/B000/XXX"
debounce_secs  = 300   # default; per-gate cool-down so a flapping gate doesn't pin Slack
```

Off by default — without `webhook_url`, no outbound traffic. When
set, every health-gate transition (e.g. `Reachability: Pass → Fail`,
`StorageRadius: Warn → Pass`, `Stamp TTL: Pass → Warn` when a batch
crosses the 7-day topup-planning threshold) becomes one POST with a
Slack/Discord-compatible `{"text": "..."}` body. Transitions to or
from `Unknown` (data-not-loaded-yet) are suppressed so cockpit
startup never spams the channel. After firing for gate X, no further
alert for X until `debounce_secs` elapses, regardless of how many
times that gate flapped in between.

### CLI overrides

Three command-line flags override the config file:

```sh
bee-tui --ascii        # forces ascii_fallback = true
bee-tui --no-color     # forces theme = "mono"
NO_COLOR=1 bee-tui     # same as --no-color, per <https://no-color.org>
```

Resolution order (highest priority first):

1. `--ascii` flag → ascii glyphs (regardless of config)
2. `--no-color` flag OR `NO_COLOR` env (any non-empty value) → mono palette
3. `[ui].ascii_fallback` from config → ascii glyphs
4. `[ui].theme` from config → palette

## The `@env:VAR` token form

Every Bee API endpoint that's not explicitly public requires
a Bearer token. Hard-coding the token in `config.toml` is
fine for a lab node, but for production it's the wrong shape —
the file lands in dotfiles backups, screenshots, support
threads, etc. The `@env:VAR` form keeps the token out of
the file:

```toml
token = "@env:BEE_TOKEN_PROD1"
```

bee-tui reads `$BEE_TOKEN_PROD1` **once at startup** and uses
the resolved value for every request. The literal string
`@env:BEE_TOKEN_PROD1` is never logged, never captured in
`:diagnose` bundles, never sent to Bee. If the variable is
unset, bee-tui logs a tracing warning and proceeds without
an auth header (the request will then 401).

You can mix forms across nodes — one `@env:` and one literal
in the same config is fine.

## Multi-node setups

```toml
[[nodes]]
name    = "prod-1"
url     = "http://10.0.1.5:1633"
token   = "@env:BEE_TOKEN_PROD1"
default = true

[[nodes]]
name  = "prod-2"
url   = "http://10.0.1.6:1633"
token = "@env:BEE_TOKEN_PROD2"

[[nodes]]
name = "lab"
url  = "http://localhost:1633"

[ui]
theme = "default"
```

Launch picks `prod-1` (the default). At runtime, switch with:

- `:context prod-2` — swap to the second prod node
- `:context lab` — swap to the local lab node
- `:context` — list every configured profile name

The switch is fast (no restart) but **not stateful across
launches** — every run starts on the `default = true`
profile.

See [`:context`](./commands/context.md) for the deep dive
on what's preserved vs reset on switch.

## Validating your config

If bee-tui fails to start with a config error, the message is
the first thing on stderr — common ones:

| Error | Fix |
|---|---|
| `no Bee node configured (config.nodes is empty)` | Add at least one `[[nodes]]` entry. |
| `no default node selected` | Mark exactly one `[[nodes]]` with `default = true`. |
| `invalid url: …` | Quote URLs that contain ports: `url = "http://10.0.1.5:1633"` (TOML accepts unquoted in a TOML number-shape unrelated to URLs, leading to confusing parses). |
| `unknown theme name "X" — falling back to default` | Just a warning; not fatal. Set `theme` to `"default"` or `"mono"`. |

To dump the resolved config for debugging:

```sh
:diagnose
```

The bundle in `$TMPDIR/bee-tui-diagnostic-<ts>.txt` includes
the active profile name and endpoint URL. Tokens are never
captured — they live in HTTP headers, not URLs.
