- `devdesk attach --repo NAME` / `devdesk repo NAME`: git state of the session's folder (branch, ahead/behind, uncommitted changes) plus the Gitea commit status of HEAD and open PR count. Uses the anonymous API for public repos, or /etc/devdesk/gitea-token when present. Runs over the ttyd channel, so no sidecar is needed. The panel links to the repo, the CI run and PRs, and a notification fires when HEAD's CI finishes while the tab is in the background. - /etc/devdesk/env.local is read after the packaged env and never shipped, so upgrades no longer stop at a conffile prompt. - Hooks and status line only record for panes on DevDesk's own tmux server; the Claude wrapper re-reads env before every launch (so the Remote Control toggle applies on the next Claude restart). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.1 KiB
5.1 KiB
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).
- Packaging: ships as a
.debfrom Gitea's apt registry.
Browser ─https─▶ NPM (wildcard cert, Authelia two_factor, injects ttyd credential)
─http─▶ ttyd :7681 [devdesk.service] ── /ws?arg=<name> → `devdesk attach <name>` (tmux client)
tmux server [devdesk-tmux.service] ── one session per project:
window "claude": devdesk-claude-run → claude --resume <recorded id>
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 -Nnever starts a server). It can restart freely, and does so on package upgrades. devdesk-tmux.serviceis 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, ortest-codefortest code) starts there. - Anything else (
pbs-upgrade,ceph-check) starts in$DEVDESK_DEFAULT_DIR, which is/root/codeby default. mainis the projects root.
- A name matching a project folder (
- Opening one:
https://dev.lotusguild.org/?arg=<name>opens or creates that session.- With no
?arg=, an fzf picker opens, listing running (●), dormant (○) and new (+) sessions. - The Sessions panel's form 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
claudewindow (0) and ashellwindow (1). - Conversation tracking: a Claude
SessionStarthook 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. - Claude Context panel: a Claude
statusLinecommand (devdesk-statusline) snapshots each session's model, context-window use, cost and edits to/var/lib/devdesk/context/<session>.json. The panel shows the current session's numbers, and Claude's own footer shows a one-line summary. Sessions started before 0.2.0 need Claude restarted (/exit, then ⏎ in the menu) before they report. - SSH:
devdesk attach <name>,devdesk new <name> [project],devdesk rename <old> <new>,devdesk end <name>,devdesk ls,devdesk pick.
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 |
Use the Full button for fullscreen. In Chromium it 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 packagedenvand never shipped, soapt upgradenever asks about it. Example:DEVDESK_REMOTE_CONTROL=1. /etc/devdesk/envholds the packaged defaults; don't edit it. Its keys:- port, bind address, projects root and
DEVDESK_DEFAULT_DIR DEVDESK_GITEA_URL/DEVDESK_GITEA_ALIASESfor the Repo/CI panel. Public repos need no token; for private ones put aread:repositorytoken in/etc/devdesk/gitea-tokenCLAUDE_BINandCLAUDE_ARGSDEVDESK_REMOTE_CONTROL=1, which also starts each session with Claude Remote Control so it can be continued from the Claude app
- port, bind address, projects root and
/etc/devdesk/secret.envis generated on install. It holds the ttyd credential that NPM must inject; see docs/DEPLOY.md./etc/devdesk/tmux.local.confis optional and is sourced after the packagedtmux.conf.
Build
Needs Node ≥ 18, debhelper, bats, tmux, jq, shellcheck:
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 tohttps://code.lotusguild.org/api/packages/LotusGuild/debianand creates a release.
Roadmap
- v1.1:
devdesk-apisidecar for the Repo/CI panel, Matrix notifications when you're away, and auto-deploy. - v2: Ops Alerts (Grafana) with "Ask Claude" prompts, and file drop.