jaredandClaude Opus 5.5 bf8821f3f9
build-deb / Lint, test, package (push) Successful in 2m17s
build-deb / Deploy to CT 126 (push) Successful in 8s
build-deb / Notify on failure (push) Skipped
feat: v0.9.5 context in tokens as well as percent
Claude's footer reads "ctx 34% 346k/1M"; the Sessions panel and the
session switcher use the same label via ctxLabel(). Closes #43

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHRLCCdXYm41Qx5kNhehaP
2026-10-11 23:07:04 -04:00

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 .deb from 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 -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=<name> 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.
    • The 🔔 inbox lists what needs you (waiting sessions, firing Grafana alerts) and what happened while you were away (a background session finished or started waiting, CI passed or failed); its badge counts them.
    • Answer from the inbox: a session waiting on a permission shows the exact command/file and Allow / Deny (the key is pressed only if that prompt is still on screen); other waits and finished sessions offer Peek & reply: the last exchange and a reply box.
  • 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/<session>/ (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 and the Proxmox guest or node it's about, e.g. → CT 110 draupnir · monitor-02 · stopped.
    • Ask Claude starts Claude in a new session (alert-<name>) with the investigation already sent. The prompt has the rule, its query, summary and labels, and where it runs: the CT/VM id, the node it's on right now, and the ssh <node> pct exec <id> -- line. Clicking it again just opens that session.
    • Guests are matched by the alert's instance IP (or host name), else by name in the job or alert ("by name"). Aggregate rules with no labels (count(up == 0)) get a hint to run the inner query instead.
    • devdesk guests [ID|IP|NAME] lists Proxmox CTs/VMs with the node each runs on now, status, HA state and IPs. Claude is told to use it before acting, because HA guests move. Stopped guests keep the IP they had when last seen running.
    • Configure DEVDESK_GRAFANA_URL, plus a Viewer token in DEVDESK_GRAFANA_TOKEN_FILE. For the guest lookup, set DEVDESK_PVE_NODES to a few Proxmox nodes reachable by root SSH key. VM IPs need the QEMU guest agent.
  • 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.
  • Files (Ctrl+Shift+E), laid out like VS Code: an explorer tree (git colours, gitignored entries dimmed) and an editor with tabs (a click opens a preview tab, a double-click keeps it), Go to file (Ctrl+P in the pane), markdown previews (sanitized, with relative images), images, and a Changes view of uncommitted files with line counts, a C badge on files Claude edited, and diffs against HEAD. @Claude types @path into the prompt. Tabs and open folders are remembered per session. Paths can't leave the session folder.
    • Editing: text files are editable; ● marks unsaved tabs; Ctrl+S (or Save) writes atomically and keeps the file's mode and owner. If the file changed on disk since you opened it (Claude edited it), saving asks: Compare (a diff of disk vs yours), Overwrite or Cancel; a change on disk while you edit shows a banner instead of replacing your text. Unsaved edits are kept as drafts in the browser, so a reload or a session switch doesn't lose them. + creates a file (folders as needed). The editor bundle (/usr/share/devdesk/assets/editor.js) loads over ttyd the first time and is cached by the browser.
  • Paths are links: a file path Claude prints (web/src/files.js, notes.md:12) opens in Files when clicked, at that line if given.
  • Ops guard: devdesk-guard (a PreToolUse hook) makes Claude ask before destructive commands (pct destroy, zfs destroy, Ceph pool/OSD removal, rm -r /…, git push --force, reboot…), even in auto mode. DEVDESK_GUARD=0 turns it off; /etc/devdesk/guard.local adds rules (regex<TAB>reason). Bash commands are logged per session, and sessions are tagged with the hosts they touch (ssh targets, CT/VM ids).
  • Search past conversations (⋯ menu, palette): full text over every Claude conversation on the host; Resume it in a new session or Fork it.
  • Plan usage: the 5-hour and weekly windows as meters in the footer (warning at 90 %); a PR chip on sessions whose branch has a pull request.
  • Quiet phone at the desk: while a tab shows a session, Remote Control holds back phone pushes for it (client-presence marker, expires ~2 min after you leave).
  • Context: a Claude statusLine command (devdesk-statusline) snapshots each session's model, context-window use, plan usage and cost to /var/lib/devdesk/context/<session>.json. The Sessions panel shows it as a bar and Claude's own footer shows a one-line summary. On a Pro/Max plan that summary shows 5-hour plan usage; the dollar figure Claude reports there is an API-price estimate, not a bill, so it appears only for API-key billing.
  • 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 <name>, devdesk new <name> [project], devdesk rename <old> <new>, devdesk end <name>, devdesk ls, devdesk pick, devdesk repo <name>, 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)
Ctrl+Shift+E Files pane (Esc: back, then close)

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.
  • /etc/devdesk/tmux.local.conf is optional and is sourced after the packaged tmux.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 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.

Roadmap

Tracked in Gitea issues by milestone: v0.6.0 file viewer, v0.7.0 editor + inbox, backlog.

S
Description
Persistent browser terminal for Claude Code (ttyd + tmux, LotusGuild TDS) at dev.lotusguild.org, packaged as a .deb
Readme MIT
1.9 MiB
2026-11-03 21:23:29 -05:00
Languages
JavaScript 52.9%
Shell 35%
CSS 9.2%
HTML 2.4%
Makefile 0.5%