- devdesk attach --fs-list/--fs-read/--git-status/--git-show/--asset (libexec/devdesk-files.sh), confined to the session folder. - The state hook records files Claude edits (Edit/Write/MultiEdit/ NotebookEdit) for the Changes tab's "C" badge; rename/end carry it. - Files pane: folders, CodeMirror read-only view, sanitized markdown preview in a sandboxed iframe with inlined relative images, images, Changes tab with line counts and diff against HEAD, "→ Claude" @path, resizable split. - The editor bundle ships as /usr/share/devdesk/assets/editor.js, is fetched over ttyd on first use, verified against the page's SHA-256 and cached in IndexedDB. - Fix: the state hook's field split collapsed empty fields. Closes #19 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
8.5 KiB
Markdown
97 lines
8.5 KiB
Markdown
# 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=<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): browse the session's folder beside the terminal, read files with syntax highlighting, preview markdown (sanitized, with its relative images) and images, and review uncommitted changes: git status with line counts, a **C** badge on files Claude edited, and a diff against HEAD. **→ Claude** types `@path` into the prompt. Read-only for now. Paths can't leave the session folder. 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](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.
|