Files
devdesk/README.md
T
jaredandClaude Opus 5.5 66321f4748
build-deb / Lint, test, package (push) Successful in 1m57s
build-deb / Deploy to CT 126 (push) Successful in 3s
build-deb / Notify on failure (push) Skipped
feat: v0.8.0 edit and save files with conflict detection
- devdesk attach --fs-write SESSION PATH BYTES BASESHA|new: streamed like
  uploads, temp file in the same folder, sha re-check (conflict reply instead
  of overwriting), mode/owner kept, atomic mv; new files with folders, never
  outside the session folder or through a symlink out of it.
- Editor: editable CodeMirror for UTF-8 text, dirty tracking, Ctrl+S,
  Save/Revert, conflict dialog with a disk-vs-yours diff, changed-on-disk
  banner, drafts in localStorage (flushed on pagehide), close/leave guards,
  New file.
- Upload streaming factored into streamIn(), shared by uploads and saves.

Closes #20

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:10:45 -04:00

9.1 KiB
Raw Permalink Blame History

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.
  • 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.
    • 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.
  • 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.
  • Context: a Claude statusLine command (devdesk-statusline) snapshots each session's model, context-window use and cost to /var/lib/devdesk/context/<session>.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 <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.