# DevDesk A persistent browser terminal for Claude Code, at **https://dev.lotusguild.org**. - **Persistent sessions:** closing the tab, restarting the web service or upgrading the package never stops Claude. - **Survives reboots:** after a reboot, every named session resumes its own conversation. - **Styling:** uses the LotusGuild Terminal Design System ([web_template](https://code.lotusguild.org/LotusGuild/web_template)). - **Packaging:** ships as a `.deb` from Gitea's apt registry. ``` Browser ─https─▶ NPM (wildcard cert, Authelia two_factor, injects ttyd credential) ─http─▶ ttyd :7681 [devdesk.service] ── /ws?arg= → `devdesk attach ` (tmux client) tmux server [devdesk-tmux.service] ── one session per project: window "claude": devdesk-claude-run → claude --resume window "shell" ``` ## Why two units The tmux server runs in its own unit, so every Claude process lives in **that** unit's cgroup. - ttyd (`devdesk.service`) only starts tmux *clients* (`tmux -N` never starts a server). It can restart freely, and does so on package upgrades. - `devdesk-tmux.service` is installed with `--no-stop-on-upgrade`, so apt never touches it. - **Only `systemctl stop devdesk-tmux` (or removing the package) ends the sessions.** ## Sessions - **Any name works.** Sessions don't have to be repo work: - A name matching a project folder (`tinker_tickets`, or `test-code` for `test code`) starts there. - Anything else (`pbs-upgrade`, `ceph-check`) starts in `$DEVDESK_DEFAULT_DIR`, which is `/root/code` by default. - `main` is the projects root. - **Opening one:** - `https://dev.lotusguild.org/?arg=` opens or creates that session. - With no `?arg=`, DevDesk reattaches the session you used last in this browser (or the most recently active one). The fzf picker, listing running (●), dormant (○) and new (+) sessions, is under the session chip's menu. - **Session chip** (header): click to switch sessions; its dot shows Claude's state. - **+ New** in the Sessions panel takes any name plus an optional project folder. - **Rename / end:** the ✎ and × buttons on each session in the panel. Renaming keeps the session, its running Claude and its recorded conversation. Ending stops Claude; the conversation stays in Claude's history. - **Windows:** each session has a `claude` window (0) and a `shell` window (1). - **Conversation tracking:** a Claude `SessionStart` hook records each session's conversation ID in `/var/lib/devdesk/registry.json`. After a reboot the session resumes *that* conversation, even when other Claude instances, such as code-server, use the same directory. - **Session status:** Claude hooks (`devdesk-state-hook`) mark each session **working**, **waiting on you** (with Claude's message) or **idle**. - The sidebar shows it with context % and a context bar; the session chip's dot shows the current session's state. - A "… needs you" chip jumps to other sessions that are waiting, and a desktop notification fires when one starts waiting. With the panels closed, the **Panels** button shows a count of waiting sessions and Grafana alerts. - **Matrix "Claude needs you":** when a session has waited for `DEVDESK_MATRIX_DELAY` seconds (30) and no browser tab or SSH client is attached to it, `devdesk-notify-matrix` posts one message per wait, with a link, to the hookshot webhook URL in `/etc/devdesk/matrix-webhook` (mode 600; no file = off). - **File & image drop:** drag files onto the terminal or paste a screenshot. - They're uploaded through ttyd to `/var/lib/devdesk/uploads//` (25 MB max, pruned after 7 days), and the path is pasted into Claude. - For images, Claude attaches them itself. - **Ops Alerts panel:** firing and pending Grafana alert rules, each with a link to the rule. - **Ask Claude** types an investigation prompt into the current session without sending it. - Configure `DEVDESK_GRAFANA_URL`, plus a Viewer token in `DEVDESK_GRAFANA_TOKEN_FILE`. - **Repo / CI panel:** branch, ahead/behind, uncommitted changes, HEAD's Gitea CI status and open PRs for the session's folder. When CI fails, **Ask Claude to fix CI** types a prompt to read the failed job's log. - **Context:** a Claude `statusLine` command (`devdesk-statusline`) snapshots each session's model, context-window use and cost to `/var/lib/devdesk/context/.json`. The Sessions panel shows it as a bar (cost in the tooltip), and Claude's own footer shows a one-line summary. - **Light mode:** the terminal switches palettes with the page. DevDesk starts Claude with its `dark-ansi` theme so Claude draws with the terminal's 16 colours, and the light palette maps bright white to ink; tmux and the status line use ANSI colours too. A session picks up Claude's theme the next time Claude starts in it (`/exit`, then ⏎ resumes the same conversation). - **Phone:** a key bar replaces the shortcut hints (Esc, Tab, ⇧Tab, ^C, ↑, ↓, ⏎, Claude/Shell window), and the panels open as a drawer. - **SSH:** `devdesk attach `, `devdesk new [project]`, `devdesk rename `, `devdesk end `, `devdesk ls`, `devdesk pick`, `devdesk repo `, `devdesk alerts`. ## Keys | Key | Action | |---|---| | Shift+Enter | Newline in Claude's prompt (sends Ctrl+J) | | Esc | Interrupt Claude | | Ctrl-b 0 / 1 | Claude window / shell window | | Ctrl-b s | Switch session (the URL follows) | | Ctrl-b d | Detach (the session keeps running) | | Shift+drag | Native browser selection (plain drag goes to tmux copy-mode, which copies via OSC 52) | | Ctrl+Shift+C / V | Copy selection / paste | | Ctrl+Shift+P | Command palette (also the ⌕ button) | The **☀/◐** button switches light and dark mode (saved per browser, shared with the other web_template apps' `lt_theme` preference). Text size, desktop notifications (with a mute), the keyboard shortcuts sheet and fullscreen are in the **⋯** menu; everything is also in the command palette. In Chromium, fullscreen also captures Ctrl+W/Ctrl+T, which browsers otherwise keep for themselves. ## Configuration - **Put your changes in `/etc/devdesk/env.local`.** It's read after the packaged `env` and never shipped, so `apt upgrade` never asks about it. Example: `DEVDESK_REMOTE_CONTROL=1`. - `/etc/devdesk/env` holds the packaged defaults; don't edit it. Its keys: - port, bind address, projects root and `DEVDESK_DEFAULT_DIR` - `DEVDESK_GITEA_URL` / `DEVDESK_GITEA_ALIASES` for the Repo/CI panel. Public repos need no token; for private ones put a `read:repository` token in `/etc/devdesk/gitea-token` - `CLAUDE_BIN` and `CLAUDE_ARGS` - `DEVDESK_REMOTE_CONTROL=1`, which also starts each session with Claude Remote Control so it can be continued from the Claude app - `DEVDESK_PUBLIC_URL`, `DEVDESK_MATRIX_WEBHOOK_FILE` and `DEVDESK_MATRIX_DELAY` for the Matrix notifications - `/etc/devdesk/secret.env` is generated on install. It holds the ttyd credential that NPM must inject; see [docs/DEPLOY.md](docs/DEPLOY.md). - `/etc/devdesk/tmux.local.conf` is optional and is sourced after the packaged `tmux.conf`. ## Build Needs Node ≥ 18, `debhelper`, `bats`, `tmux`, `jq`, `shellcheck`: ```bash make # dist/index.html (single inlined page) + build/ttyd (pinned, sha256-verified) make lint test dpkg-buildpackage -us -uc -b && lintian ../devdesk_*_amd64.deb apt install ../devdesk_*_amd64.deb ``` CI (`.gitea/workflows/build-deb.yml`): - Every push lints, tests, builds and smoke-tests the package and uploads it as an artifact. - A `v*` tag also publishes to `https://code.lotusguild.org/api/packages/LotusGuild/debian`, creates a release, and auto-deploys to CT 126. A signed webhook runs `devdesk-deploy`, which upgrades the package; see [docs/DEPLOY.md](docs/DEPLOY.md). ## Roadmap Tracked in [Gitea issues](https://code.lotusguild.org/LotusGuild/devdesk/issues) by milestone: v0.6.0 file viewer, v0.7.0 editor + inbox, backlog.