# AGENTS.md Guidance for AI coding agents working in this repository. ## What this is A single-host computer dashboard and task manager: FastAPI (Python ≥ 3.12, managed with `uv`) backend serving htmx + Chart.js HTML fragments to one browser page. Binds to `127.0.0.1:8501`, no auth by design (see the README security section). Tabs: Overview, Disks, Processes, Journal, Services (systemd), Plugins (llama.cpp). Licensed under the Unlicense (see `LICENSE`). ## Commands ```sh uv sync # install dependencies uv run python main.py # run the server on http://127.0.0.1:8501 ``` There is no test suite. Verify changes with: ```sh uv run python -m compileall -q app curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8501/api/overview # ... other endpoints: /api/disks /api/processes /api/journal /api/services # /api/plugins /api/history grep -c Traceback /tmp/dash.log ``` ### Restarting the dev server The server usually runs detached in the background. To restart it: ```sh PID=$(pgrep -f "python main\.py" | head -1) [ -n "$PID" ] && kill "$PID" setsid nohup uv run python main.py > /tmp/dash.log 2>&1 < /dev/null & disown ``` Never use `pkill -f "uv run python main.py"` — the pattern also matches the agent's own shell command line and kills the session. ## Architecture - `app/collect/*` — collectors (cpu/mem/gpu/disks/procs/net/power) read psutil + sysfs; `app/sampling.py` runs them every `DASH_SAMPLE_INTERVAL` (default 2 s) into an in-memory ring buffer (`app/state.py`). - `app/routers/*` — each tab endpoint is an idempotent GET returning an htmx HTML fragment; templates live in `templates/` and self-poll via `hx-get` + `hx-trigger="every Ns"` + `hx-swap="outerHTML"`. - `templates/*.html` auto-reload on file change — no restart needed for template-only edits. Python changes require a restart. - `app/systemd/units.py` — systemd unit listing/detail/actions; `app/journal.py` — `journalctl -o export` parser with cursors. - `app/plugins/` — `base.Plugin` + llamacpp plugin (talks to a router-mode `llama-server` on port 8080). ## Conventions - No code comments (the codebase has none). - basedpyright is configured as linter - Match surrounding style; keep functions small and typed where the codebase already is. - Keep polling endpoints cheap: collectors may cache lookups (unit names, enabled-state maps, SSID, temperature paths) with short TTLs. ## Hard-won pitfalls - Jinja autoescape renders `↓` as literal text — use literal unicode (e.g. `↓`) in templates. - `journalctl -o export` output contains NUL bytes (grep treats it as binary); journalctl rejects negated matches (`!`/`!=`) — filter entries in Python instead. - psutil gotchas: there is no `psutil.AF_INET` (use `socket`); `net_if_addrs()` / `net_if_stats()` take no arguments; `sensors_battery().power_plugged` can be `None` — use `/sys/class/power_supply/*` (type/capacity/status/online) for battery + AC state. - `iw dev link` prints `SSID: name` **unquoted**; the working regex is `SSID:\s+(\S.*)` (a `$` anchor fails without MULTILINE). - `/api/history` pads series with `null` for samples missing a key so all series stay aligned with the timestamps — keep that behaviour if you touch it. - AMD sysfs: GPU busy/VRAM/temp under `/sys/class/drm/card*/device` (+ `hwmon`), CPU temp from the `k10temp` hwmon (fallback `acpitz` thermal zone), both in millidegrees.