Files

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 token prints it, deleting the file rotates it. The browser enters it with the key button and keeps it as the werkregie_token cookie; relays send it as Authorization: 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 …"; after server.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 NAME on the server host asks the running server (with the token from its state), or edits relays.json when 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. stateDir defaults 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.

  1. 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
    
  2. On the host, unpack it to ~/opt and write the configuration, see dashboard.server.example.yml (port is the assigned port, server.publicBaseUrl the domain).
  3. Generate and install the unit and the .htaccess; the command prints the remaining steps:
    ~/opt/werkregie-dashboard/bin/werkregie-dashboard init server ~/werkregie-init
    
  4. ~/opt/werkregie-dashboard/bin/werkregie-dashboard token prints 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.

  1. Configure matrix.homeserver; there is no default.
  2. Stop the relay or the local mode, then sign in; it asks for user and password:
    werkregie-dashboard matrix login
    
  3. 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
    
  4. 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 (or controlTokenEnv) is sent as the werkator_token cookie, the way Werkator's own pages do; tools/remote --env-file .env.<instance> werkator control-token prints it.

  • Gitea: a token with read scopes, as token, tokenEnv or teaLogin (a login of ~/.config/tea/config.yml, by name or user). pullRequests: mine lists PRs created by, assigned to or awaiting review from the user, all every 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: taskboards with type: taiga list the open user stories, tasks and issues assigned to the user in the configured projects, in the panel Aufgaben. There is no default instance or project. The API address comes from the field api of <url>/conf.json, as for Taiga's own web app, else <url>/api/v1; apiUrl overrides it. Werkregie logs in with the user and the password of the variable passwordEnv and logs in again when the token expires; on a server the variable stands in dashboard.env, in the local mode it has to be in the environment. statuses names 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.