4.4 KiB
4.4 KiB
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, sleep, LACT). Licensed under the Unlicense
(see LICENSE).
Commands
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:
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:
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.pyruns them everyDASH_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 intemplates/and self-poll viahx-get+hx-trigger="every Ns"+hx-swap="outerHTML".templates/*.htmlauto-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 exportparser with cursors.app/plugins/—base.Plugin(optionalopen/closelifecycle hooks run from app lifespan) + llamacpp plugin (talks to a router-modellama-serveron port 8080) + sleep plugin (lists block-modesystemd-inhibitlocks; holds its own sleep lock via asystemd-inhibit ... sleep infinitychild while the UI switch is on, reaps stale locks bywhomarker on startup)- lact plugin (shells out to
lact cli: per-GPU profile dropdown with set/reload, active profile polled every 5 s, GPU names shortened withapp/collect/gpu.py:shortenlike the overview card).
- lact plugin (shells out to
Conventions
- Google-style docstrings for every function and class: one-line imperative
summary, an
Args:section for each parameter, andReturns:/Raises:where non-obvious. Complex functions (parsers, subprocess wrappers, anything touching the pitfalls below) get extra prose explaining the behaviour, not just the signature. - Inline comments are allowed only for
Sampledataclass field docs. - basedpyright is configured as linter, use with
uvx. - 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 exportoutput 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(usesocket);net_if_addrs()/net_if_stats()take no arguments;sensors_battery().power_pluggedcan beNone— use/sys/class/power_supply/*(type/capacity/status/online) for battery + AC state. iw dev <if> linkprintsSSID: nameunquoted; the working regex isSSID:\s+(\S.*)(a$anchor fails without MULTILINE)./api/historywindow-averages the ring buffer down to at mostchart_max_points(default 200) points, emitting{avg, min, max}per key, and pads each of the three arrays withnullfor windows missing a key so they 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 thek10temphwmon (fallbackacpitzthermal zone), both in millidegrees.