# AGENTS.md — sb-scout

## What this is

A standalone CLI tool that ranks [Hetzner Serverbörse](https://www.hetzner.com/sb/)
listings by value. It fetches the live public feed (default USD), caches it in an
XDG-compliant cache directory, filters servers, and scores them using weighted
geometric means across compute, RAM, and storage value per unit of net price.

Zero third-party runtime dependencies — pure Python 3.11+ stdlib.

## Project layout

```text
src/sb_scout/
├── __init__.py      # Package version
├── __main__.py      # python -m entry point
├── cli.py           # CLI parsing, orchestration, main()
├── config.py        # TOML config loading, profile composition, example config
├── fetch.py         # HTTP feed fetching
├── models.py        # Dataclasses, constants, built-in CPU specs
├── scoring.py       # Filtering, scoring, ranking engine
├── output.py        # Text, Markdown, CSV, JSON formatting and exports
├── history.py       # JSONL price history tracking and diff
└── alerts.py        # Threshold alerting, command hooks, msmtp email
tests/
├── conftest.py
├── test_alerts.py
├── test_cli.py
├── test_config.py
├── test_history.py
├── test_output.py
└── test_scoring.py
```

## Key concepts

### Config layering

1. Built-in defaults (`config.py` `BUILTIN_DEFAULTS`)
2. Config file `[defaults]`
3. Active profiles (composable, left-to-right)
4. CLI flags (highest priority)

Prices are net / ex-VAT.

Config search:
`--config` → `$SB_SCOUT_CONFIG` → `./sb-scout.toml` → `~/.config/sb-scout/config.toml`

### Profiles

Profiles are composable overlays.

Atomic built-ins:
- `ecc`
- `mixed-storage`
- `ssd-only`
- `high-memory`
- `gpu`
- `budget`
- `storage-dense`

Convenience built-ins:
- `ssd-compute` → includes `ecc + ssd-only + high-memory`
- `cultscale` → compatibility alias for `ecc + mixed-storage`

Profile includes are supported through `include = [...]` in profile definitions.
Cycle detection exists in `config.py`.

### CPU specs

`BUILTIN_CPU_SPECS` in `models.py`. Users should prefer `[cpu_specs]` in config
for local additions/overrides.

### Alerts

Alerts can trigger:
- a shell command via `notify_command`
- email via local `msmtp` (`email_to`, `email_from`, `email_subject`, `email_account`)

The feed cache lives under `$XDG_CACHE_HOME/sb-scout/feeds` (or `~/.cache/sb-scout/feeds`).

### Features

- `--json` for structured JSON to stdout
- `--watch N` for polling loop
- `--history-file` + `--diff` for price tracking over time
- `--alert-*` thresholds + `--alert-notify`
- `--alert-email-*` for msmtp-based email notifications
- `gpu_hints` configurable via config

## Rules for agents

### Adding a new setting

1. Add to `BUILTIN_DEFAULTS` in `config.py`
2. Add to `_LAYERED_KEYS` in `cli.py`
3. Add argparse flag in `parse_args()` in `cli.py`
4. Handle in `build_app_config()` in `cli.py`
5. Add to the relevant dataclass in `models.py`
6. Update `EXAMPLE_CONFIG` in `config.py`
7. Regenerate `config.example.toml`
8. Add/adjust tests

### Adding a new profile

Add to `BUILTIN_PROFILES` in `config.py`.
Prefer orthogonal/atomic profiles where possible. Use `include = [...]` for
convenience combos rather than baking unrelated concerns together.

### Adding a new CPU model

Prefer `[cpu_specs]` in config. Only add to `BUILTIN_CPU_SPECS` in `models.py`
if it is a common Hetzner model.

### Code conventions

- Python 3.11+ (`tomllib` from stdlib)
- `pytest` for tests
- Type hints + `from __future__ import annotations`
- Dataclasses for structured config
- Keep modules focused: fetch, scoring, output, config, alerts, history

### Bastions integration

- Nix artifact: `~/bastions/home/modules/artifacts/sb-scout/default.nix`
- Config deployed via home-manager: `~/.config/sb-scout/config.toml`
- Package added to `home.packages` in `environment.nix`
- `msmtp` should be present in bastions if email alerts are expected to work
