10 KiB
Werkregie-Dashboard
A web app showing what the coding agents and the build pipeline are doing right now and what happened recently — read-only, links back to the tools.
| Source | Data | Shown |
|---|---|---|
| Claude Code (Desktop and terminal) | ~/.config/Claude/claude-code-sessions/**.json, ~/.claude/sessions/<pid>.json |
busy / waiting / closed sessions, resume command |
| Codex (ChatGPT Desktop, IDE, CLI) | ~/.codex/state_5.sqlite (read-only), rollout files |
threads, running turns, resume command |
| Werkator, any number of instances | /api/repos, /api/repos/{repo}/builds/history |
running and finished builds; @ai-review builds as werkrevisor |
| Gitea, any number of instances | /api/v1 |
open PRs with commit statuses (Werkator, werkrevisor run and verdict), own activity |
| Matrix, with the user's own account | client-server API through Trixnity (de.connect2x.trixnity) |
direct messages, mentions in every room, every message of configured rooms |
The page follows Werkator's design (werkregie.css mirrors werkator.css: IBM Plex, petrol accent, light/dark with a toggle).
The page installs as an app (PWA): manifest.webmanifest, icons and the service worker sw.js in static/.
The service worker keeps the page, styles and fonts for a start without network, fetching them anew first whenever it can;
it never touches /api/.
The page itself keeps the last state of /api/dashboard in the browser's localStorage (werkregie-last):
when the server does not answer, it shows that state under an amber banner with its age.
Demo data is never kept, and forgetting the access token deletes the state.
The PNG icons are rendered from the SVGs, e.g. inkscape icon-maskable.svg -w 512 -h 512 -o icon-maskable-512.png.
Modes
One program, three ways to run it:
┌──────────── Linux machine with coding agents ─────────────┐
│ Claude Code, Codex ──► werkregie-dashboard relay ──┐ │
└─────────────────────────────────────────────────────┼─────┘
HTTPS POST every 5 s, │ outgoing only:
bearer token ▼ nothing reaches in
┌──────────── host, e.g. Hostsharing Managed Webspace ──────┐
browser ─►│ Apache (.htaccess) ─► werkregie-dashboard server ◄── Werkator, Gitea
(cookie) └───────────────────────────────────────────────────────────┘
| Mode | Reads | Serves |
|---|---|---|
local (default) |
Claude Code, Codex, Werkator, Gitea | 127.0.0.1 only, no token |
server |
Werkator, Gitea, and whatever the relays report | real data only with the access token, invented demo data without |
relay |
Claude Code, Codex | nothing: posts its state to relay.serverUrl |
- Access token: the server creates it on its first start in
<stateDir>/access-token(mode 0600);werkregie-dashboard tokenprints it, deleting the file rotates it. The browser enters it with the key button and keeps it as thewerkregie_tokencookie; relays send it asAuthorization: Bearer. The relay endpoint does not accept the cookie, so no other site can make a logged-in browser post to it. - Silent relays: after
server.relayStaleSeconds(30) the relay's chip turns yellow with "zuletzt vor …"; afterserver.relayGoneSeconds(120) its running and open sessions move to the history. - Retired relays: a relay that reports no more stays as stale until it is forgotten:
werkregie-dashboard forget-relay NAMEon the server host asks the running server (with the token from its state), or editsrelays.jsonwhen no server runs. A relay still running simply reports again. - State: the last report of every relay is kept in
<stateDir>/relays.json, so a restarted server is filled at once.stateDirdefaults to${XDG_STATE_HOME:-~/.local/state}/werkregie.
Run locally
./gradlew installDist
build/install/werkregie-dashboard/bin/werkregie-dashboard # → http://localhost:4747/
or ./gradlew run. To start it with the session, see packaging/werkregie-dashboard.service.
Server on a host (Hostsharing Managed Webspace)
The same shape as Werkator's: a runtime bundle with its own JRE, a systemd user service on the
port of the booked "eigener Serverdienst", and the platform's Apache as TLS-terminating proxy via .htaccess.
- Build the bundle on a Linux x86_64 machine (the JRE is Temurin 25, built against an old glibc; werkregie ADR 0001):
./gradlew runtimeBundle # → build/distributions/werkregie-dashboard-runtime-linux-x64.tar.gz - On the host, unpack it to
~/optand write the configuration, see dashboard.server.example.yml (portis the assigned port,server.publicBaseUrlthe domain). - Generate and install the unit and the
.htaccess; the command prints the remaining steps:~/opt/werkregie-dashboard/bin/werkregie-dashboard init server ~/werkregie-init ~/opt/werkregie-dashboard/bin/werkregie-dashboard tokenprints the access token for the browser and the relays.
To update: stop the service, unpack the new bundle over ~/opt/werkregie-dashboard, start the service.
Relay on a machine with coding agents
Configuration as in dashboard.relay.example.yml — relay.serverUrl and the token,
best in a file of its own (relay.tokenFile, mode 0600). Then:
build/install/werkregie-dashboard/bin/werkregie-dashboard init relay ~/werkregie-init # prints how to enable the unit
Matrix
Werkregie reads Matrix as a device of the user's own account,
so that end-to-end encrypted rooms are readable once the device has their keys.
It runs in the modes local and relay, never on the server: the device keys stay on the user's machine.
- Configure
matrix.homeserver; there is no default. - Stop the relay or the local mode, then sign in; it asks for user and password:
werkregie-dashboard matrix login - Verify the device with the recovery key, formerly "Security Key", or the security phrase;
this also unlocks the key backup, so that older encrypted messages become readable:
werkregie-dashboard matrix verify - Start the relay or the local mode again; it decrypts the rows shown as "verschlüsselt" once more.
The session and the keys live in <stateDir>/matrix/ (mode 0700):
an H2 database client.mv.db and the shown rows in messages.json.
To sign in anew, delete that directory and remove the device "Werkregie " in Element.
A relay reports only the start of each text, at most 80 characters, to the server.
One process per machine runs the client, the one that holds sync.lock:
a relay and the local mode share the state directory, and two clients with the same keys would spoil each other's sessions.
The other process shows the same messages from messages.json and takes over when the first one ends.
Until the device has the keys, an encrypted message shows as "verschlüsselt", with room, sender and time.
Each direct chat and configured room keeps its last message in the panel, however old.
A message the user has not read yet is marked "Neu"; read ones are subdued.
Read means up to the later of the user's read receipts and Element's read marker m.fully_read,
since an Element without read receipts only moves its marker.
Configuration
~/.config/werkregie/dashboard.yml, or --config FILE, or WERKREGIE_DASHBOARD_CONFIG.
See dashboard.example.yml for the local mode; without a file only Claude Code and Codex are shown.
-
Werkator: private repositories are redacted for anonymous readers.
controlToken(orcontrolTokenEnv) is sent as thewerkator_tokencookie, the way Werkator's own pages do;tools/remote --env-file .env.<instance> werkator control-tokenprints it. -
Gitea: a token with read scopes, as
token,tokenEnvorteaLogin(a login of~/.config/tea/config.yml, by name or user).pullRequests: minelists PRs created by, assigned to or awaiting review from the user,allevery open PR the user can see. Each PR is marked by what it asks of whom; the panel sorts by these markers, in this order: ⚠ Festgefahren (werkrevisor escalated, RED for an hour without a new commit, ERROR, or a run without verdict), ✋ Dein Approval (a foreign PR with a final ORANGE, then YELLOW, then without werkrevisor), ➜ Mergen (the user's own PR approved by another human, or a foreign PR approved by anyone but its author, with all checks green and no automerge scheduled), 🔧 Nacharbeit (conflict or red build on the user's own PR), ⏳ Wartet auf Review (the user's own PR waits for another human), ✓ Von dir approved. Unmarked PRs follow, drafts come last; drafts carry no marker. Within each group the PR that waits longest comes first.reviewBots(default[ai-reviewer]) names accounts whose approval is no human's. -
Taiga:
taskboardswithtype: taigalist the open user stories, tasks and issues assigned to the user in the configuredprojects, in the panel Aufgaben. There is no default instance or project. The API address comes from the fieldapiof<url>/conf.json, as for Taiga's own web app, else<url>/api/v1;apiUrloverrides it. Werkregie logs in with the user and the password of the variablepasswordEnvand logs in again when the token expires; on a server the variable stands indashboard.env, in the local mode it has to be in the environment.statusesnames the most important statuses first; other open statuses follow them, within one the latest change first.
Local sources refresh every 2–3 s, Werkator every 30 s (three repositories at a time), Gitea every 30 s, Taiga every 60 s; cached data is served at once while a due source refreshes in the background.