111 lines
5.2 KiB
Markdown
111 lines
5.2 KiB
Markdown
# 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
|
|
|
|
```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/utils/` — shared helpers: `subprocess.py` (`run`/`run_async` return
|
|
`(rc, stdout, stderr)` and never raise — missing binary or timeout is
|
|
rc -1 with the reason in stderr; `run_json*` variants parse stdout as
|
|
JSON), `sysfs.py` (`read_str`/`read_int`/`read_float`), `gpu.py`
|
|
(`shorten`), `window.py` (the `/api/history` windowing), `systemd.py`
|
|
(unit listing/detail/actions; `list-units`/`list-unit-files` use
|
|
`--output=json`, needs systemd ≥ ~246, `show` has no JSON).
|
|
- `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/journal.py` — `journalctl -o json` parser (one JSON object per line)
|
|
with cursors.
|
|
- `app/plugins/` — `base.Plugin` (optional `open`/`close` lifecycle hooks run
|
|
from app lifespan) + llamacpp plugin (talks to a router-mode `llama-server`
|
|
on port 8080) + sleep plugin (lists block-mode `systemd-inhibit` locks;
|
|
holds its own sleep lock via a `systemd-inhibit ... sleep infinity` child
|
|
while the UI switch is on, reaps stale locks by `who` marker 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 with
|
|
`app/utils/gpu.py:shorten` like the overview card).
|
|
|
|
## Conventions
|
|
|
|
- Google-style docstrings for every function and class: one-line imperative
|
|
summary, an `Args:` section for each parameter, and `Returns:`/`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 `Sample` dataclass 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` is queried with `-o json` on purpose: the old `-o export`
|
|
output contains NUL bytes (grep treats it as binary) and multi-line
|
|
values need continuation-line handling, while JSON escapes both.
|
|
journalctl still rejects negated matches (`!`/`!=`) — filter entries in
|
|
Python instead.
|
|
- `nvidia-smi --format=json` keys are underscored and **values are
|
|
strings**; `lspci` has no JSON (use the locale-stable `-mm`
|
|
double-quoted format); `systemctl show`, `systemctl is-system-running`,
|
|
`iw`, and `lact cli` have no JSON output at all.
|
|
- 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 <if> link` prints `SSID: name` **unquoted**; the working regex is
|
|
`SSID:\s+(\S.*)` (a `$` anchor fails without MULTILINE).
|
|
- `/api/history` window-averages the ring buffer down to at most
|
|
`chart_max_points` (default 200) points, emitting `{avg, min, max}` per key,
|
|
and pads each of the three arrays with `null` for 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 the `k10temp`
|
|
hwmon (fallback `acpitz` thermal zone), both in millidegrees.
|