Webapp for spawning and managing dockerized Claude Code agents.
  • TypeScript 68.4%
  • Svelte 26%
  • Shell 2.7%
  • CSS 1.6%
  • Dockerfile 0.8%
  • Other 0.4%
Find a file
Luca Zanussi 3b086dd91d
All checks were successful
ci / nexus (push) Successful in 3m49s
ci / images (./nexus, agent-nexus) (push) Successful in 9m19s
ci / images (./worker, agent-nexus-worker, base) (push) Successful in 1m31s
ci / images (PLAYWRIGHT_CLI_VERSION=0.1.13, ./worker, agent-nexus-worker, -playwright0.1.13, playwright) (push) Successful in 1m27s
fix(terminal): stop Shift+Enter from auto-submitting after the soft newline
The Shift+Enter handler sent \x1b\r (soft newline) and returned false from
xterm's customKeyEventHandler, but never called preventDefault(). The handler
only acts on keydown, so the browser still fired the follow-up keypress for
Enter, which xterm's _keyPress emitted as a bare \r — submitting the prompt a
beat after the newline landed. preventDefault() on keydown cancels that
keypress, so only the \x1b\r we send reaches Claude.

Claude-Session: https://claude.ai/code/session_01JW434vyWeTEbzbHwuBaZvB
2026-06-27 10:44:31 +00:00
.claude/worktrees/issue-10-nexus-worktrees Fix README.md logo pointing at the wrong folder after Svelte migration 2026-05-15 20:08:47 +02:00
.forgejo/workflows feat(worker): Playwright image via @playwright/cli + per-image plugin skill 2026-05-30 19:10:54 +00:00
docs/superpowers Merge pull request 'fix(mobile): off-canvas drawers for the Files & Artifacts views' (#42) from fix/mobile-ui-files into main 2026-06-23 16:14:15 +02:00
nexus fix(terminal): stop Shift+Enter from auto-submitting after the soft newline 2026-06-27 10:44:31 +00:00
worker docs: crash-recovery — persisted secrets, info/exclude, convergeSessions, degraded boot (#16) 2026-06-22 21:01:53 +00:00
.gitattributes feat(agent-instructions): add bundled default CLAUDE.md + .gitattributes LF pin 2026-05-25 23:44:24 +02:00
.gitignore Merge remote-tracking branch 'origin/main' into feat/proper-db 2026-05-30 21:09:50 +00:00
AGENTS.md refactor(settings): remove the global worker_image setting and WORKER_IMAGE env 2026-06-24 19:32:13 +00:00
CLAUDE.md Initial commit: Agent Nexus — self-hosted Claude Code agent manager 2026-05-11 20:46:39 +02:00
DEPLOYMENT.md refactor(settings): remove the global worker_image setting and WORKER_IMAGE env 2026-06-24 19:32:13 +00:00
docker-compose.override.yml feat(docker): Docker Desktop override + WORKER_CALLBACK_URL 2026-05-28 22:56:01 +02:00
docker-compose.yml refactor(settings): remove the global worker_image setting and WORKER_IMAGE env 2026-06-24 19:32:13 +00:00
README.md refactor(worker): rename image nexus-worker → agent-nexus-worker, node 22 base 2026-05-30 15:39:01 +00:00

Agent Nexus

Agent Nexus

Self-hosted webapp for spawning and managing dockerized Claude Code agents.

Connects to one or more git providers (Forgejo today, GitHub planned), lists your repositories, and spawns Nexus Worker containers — each running Claude Code under tmux with cloud Remote Control so you can attach from the Claude mobile/web app or docker exec from the host.

Quick start

# 1. Build images
docker build -t agent-nexus-worker:latest ./worker
docker build -t agent-nexus:latest ./nexus

# 2. One-time: bootstrap the shared Claude session.
#    Remote Control refuses inference-only setup-tokens, so workers share the
#    full-scope OAuth state produced by `claude auth login`. The bootstrap
#    script runs it interactively and saves the result to a docker volume.
docker run --rm -it -v claude-session:/session agent-nexus-worker:latest \
  /bootstrap-claude-auth.sh

# 3. Start Nexus + the docker-socket proxy
docker compose up -d

Then open http://localhost:3001/ and:

  1. Set a master passphrase (first run only — it encrypts provider tokens at rest)
  2. Unlock with that passphrase
  3. If the bootstrap step ran, the UI advances past the Bootstrap Claude session screen automatically. If not, you'll see instructions to run step 2 above.
  4. Add a Forgejo connection (base URL + PAT with write:repository write:issue read:user read:organization)
  5. Create a workspace (Provider + Repo), then add one or more sessions from the workspace card
  6. Each session shows up at claude.ai/code and in the Claude Android/iOS app a few seconds later

Architecture

 ┌────────────────────────────────┐
 │ browser / Claude mobile app    │
 └──────────────┬─────────────────┘
                │ HTTPS (nginx, optional)
 ┌──────────────▼─────────────────┐         ┌─────────────────────────────┐
 │ agent-nexus  (Fastify + SQLite)│ via TCP │ docker-socket-proxy          │
 │  - master-passphrase vault     │────────►│  endpoint allowlist:         │
 │  - provider abstraction        │ to:2375 │  containers, exec, volumes,  │
 │  - spawns workers              │         │  images. Everything else 403.│
 └──────────────┬─────────────────┘         └────────────┬─────────────────┘
                │ creates / inspects                     │ mounts /var/run/docker.sock:ro
                │                                        ▼
                │                          ┌─────────────────────────────┐
                │                          │ host Docker engine          │
                ▼                          └────────────┬─────────────────┘
 ┌───────────────────────────────────┐                  │ spawns
 │ agent-nexus-worker (one/session)  │◄─────────────────┘
 │  - tmux PID 1, session "nexus"    │
 │  - claude --remote-control NAME   │       ┌─────────────────────────┐
 │    --worktree NAME                │──────►│ Anthropic cloud relay   │
 │  - /session ← claude-session vol  │       └────────────┬────────────┘
 │  - /workspace ← per-worker volume │                    │
 └───────────────────────────────────┘                    │
                                                          ▼
                                          your Claude mobile / claude.ai/code
  • One worker container = one Claude session = one git worktree. Spawn multiple workers on the same repo for collaborating agents; they share the same /workspace clone but each runs --worktree <name> so changes land on isolated branches.
  • Shared OAuth state: the claude-session docker volume holds credentials.json + account.json produced by claude auth login. Workers mount it read-write so refresh-token rotations persist across the fleet.
  • Docker socket is fronted by a proxy (tecnativa/docker-socket-proxy) on a default-deny internal network. Even if Nexus is compromised, the attacker cannot reach docker run --privileged or any non-allowlisted endpoint.
  • Per-spawn secrets (Forgejo token, authed clone URL) are injected via tmpfs after the worker starts. They do NOT appear in docker inspect or /proc/1/environ.
  • Worker discovery: containers carry nexus-managed=true. The UI never lists unrelated host containers.

Repo layout

agent-nexus/
├── nexus/                  # the webapp (Fastify + Vite + Kysely)
├── worker/                 # the Claude runtime image
├── docker-compose.yml      # nexus + docker-socket-proxy
├── DEPLOYMENT.md           # threat model, nginx, backups
└── .forgejo/workflows/ci.yml

See DEPLOYMENT.md for production deployment, nginx reverse-proxy config, the threat model after the socket-proxy hardening, and backup snippets.