<p align="center">
  <img src="brand/arboretum-logo-on-dark.png" alt="Arboretum" width="300">
</p>

<p align="center">
  A self-hosted web dashboard for your git worktrees and the Claude Code sessions running on them — from any device.
</p>

<p align="center">
  <strong>English</strong> · <a href="README.fr.md">Français</a>
</p>

**Status: MVP.** The worktree-first dashboard, session discovery & resume, multi-repo worktree lifecycle, live session states, the web terminal, and mobile supervision (installable PWA, Web Push when a session needs you, approve/deny without opening a terminal), and work groups (run several related repos at once) are implemented and tested.

---

## The problem

Working with AI coding agents changed how we use git: one feature = one worktree = one Claude Code session, several of them in parallel. But the tooling didn't follow:

- `git worktree list` across multiple repos is a chore, worktrees pile up, each needs its `node_modules` and `.env`.
- Claude Code sessions are scattered: some running in terminals, some resumable from history, with no consolidated view of *which one is waiting for your input*.
- When you step away from your desk, a session blocked on a permission prompt stays blocked.

## What Arboretum does

A single Node.js daemon you run on your dev machine (`npx @johanleroy/git-arboretum`), serving a web UI usable from your desktop, phone or tablet:

- **Worktree-first, multi-repo dashboard** — every worktree of every registered repo, with its git state (branch, ahead/behind, dirty files) *and* the state of its Claude Code session (busy / waiting for input / idle / resumable).
- **Full worktree lifecycle** — create (with per-repo post-create hooks: `npm ci`, copy `.env`…), adopt worktrees created by hand, delete with guardrails, prune orphans.
- **Session discovery & resume** — sessions you launched in your own terminal show up automatically; resume dead ones, observe or fork live ones. Never corrupts a live session.
- **Web terminal** — full xterm.js terminal to every managed session, surviving browser disconnects.
- **Supervision from your phone** — installable PWA with push notifications when a session needs you; approve/deny a prompt without opening a terminal.
- **Work groups** — bundle related repos (e.g. an API, its web frontend and a shared library) into a named group to work on them at once: a unified view of all their worktrees and sessions, a side-by-side multi-terminal grid, and a one-click "cross-repo feature" that creates the same branch worktree (and optionally a Claude session) across every repo in the group.

---

## Requirements

- **Node.js ≥ 22.16** — required, not just recommended. Arboretum persists state with `node:sqlite` (`DatabaseSync`), which is native and stable only from this version. (`.nvmrc` pins `22`.)
- **The `claude` CLI** on your `PATH` if you want Arboretum to launch and manage Claude Code sessions. Arboretum wraps the interactive CLI you already use — install and authenticate it as usual.
- A **git** repository (or several) you want to manage.

## Quick start

Two paths, depending on what you want:

- **Just use it (most people).** Arboretum is a published npm package — you **don't need to clone this repo**. Point npm at the registry and run it (below). Do this on the machine where your Claude Code sessions run.
- **Run from source.** Clone the repo only to hack on Arboretum or run an unreleased build.

### Run it (recommended)

Arboretum is published to a self-hosted Gitea npm registry. Point the `@johanleroy` scope at it once per machine — add to `~/.npmrc`:

```
@johanleroy:registry=https://git.lidge.fr/api/packages/johanleroy/npm/
```

No token needed — the package is publicly readable. Then run it from anywhere:

```bash
npx @johanleroy/git-arboretum
```

On first start, Arboretum prints a one-time **access token** and the URL to open:

```
┌──────────────────────────────────────────────────────────────────┐
│  First start — your access token (shown once, store it safely):    │
└──────────────────────────────────────────────────────────────────┘

  <your-token-here>

  Login at: http://127.0.0.1:7317/
```

Open the URL, paste the token to log in, and you're in. The token is stored **hashed** — it is shown only once, so save it somewhere safe (a password manager). You can manage tokens later from **Settings**.

`npx` fetches and runs the latest published version each time. To install it once — and get the `arboretum` command on your `PATH`, which the [background service](#running-it-as-a-background-service) relies on — install it globally instead:

```bash
npm i -g @johanleroy/git-arboretum
arboretum          # identical to the npx command, from the installed binary
```

### Run from source

Only needed to **develop** Arboretum or run an unreleased build — not required just to use it. Clone the repo, install dependencies, build, then start the daemon:

```bash
git clone https://git.lidge.fr/johanleroy/arboretum.git
cd arboretum
nvm use            # or ensure Node ≥ 22.16
npm install
npm run build      # builds shared → server → web (order matters)
node packages/server/dist/index.js
```

## Using Arboretum

1. **Add a repository.** From the dashboard, register a local git repo by its path. Optionally configure **post-create hooks** (e.g. `npm ci`, `cp ../.env .env`) that run automatically every time you create a new worktree for that repo.
2. **Create or adopt worktrees.** Spin up a new worktree + branch in one click (hooks run for you), or adopt a worktree you created by hand. Each worktree shows its branch, ahead/behind, and dirty-file count.
3. **Start or resume a session.** Launch a Claude Code session on a worktree, or resume one that was started in your terminal — Arboretum discovers existing sessions automatically and always resumes them in their original working directory.
4. **Watch the live states.** Each session reports whether it's *busy*, *waiting for your input*, or *idle*. Open the **web terminal** to interact directly; it survives browser disconnects (closing the tab does not kill the session).
5. **Supervise from your phone.** Install the PWA, and when a session flips to *waiting* you get a push notification. Approve or deny the prompt straight from the notification UI — no terminal required.

## Remote access from your phone

Arboretum binds to `127.0.0.1` by default and **refuses** to bind to a non-loopback address without an explicit override. The recommended (and safe) way to reach it from other devices is **[Tailscale Serve](https://tailscale.com/kb/1242/tailscale-serve)** — valid HTTPS, tailnet identity, no open ports:

```bash
# Expose the local daemon over HTTPS inside your tailnet
tailscale serve --bg 7317
```

Then start Arboretum allowing your tailnet origin (the strict Origin check needs to know about it):

```bash
npx @johanleroy/git-arboretum --allow-origin https://<machine>.<tailnet>.ts.net
```

Open `https://<machine>.<tailnet>.ts.net` from any device on your tailnet. **Web Push requires HTTPS**, so Tailscale Serve (or another HTTPS front) is also what enables mobile notifications. On **iOS**, install the app to your home screen first, then allow notifications.

> ⚠️ A web terminal is remote code execution **by design**. Never expose Arboretum directly to the public internet.

## Running it as a background service

The quickest way to run Arboretum as a service that survives logout and restarts on boot is the built-in installer. Install a pinned version globally, then run `install` — it detects your OS, writes the service file, starts it, and prints the one-time token:

```bash
npm i -g @johanleroy/git-arboretum
arboretum install --allow-origin https://MACHINE.TAILNET.ts.net
```

This sets up a **systemd user service** on Linux (`~/.config/systemd/user/arboretum.service`) or a **launchd LaunchAgent** on macOS (`~/Library/LaunchAgents/fr.lidge.arboretum.plist`). Every daemon flag (`--port`, `--allow-origin`, `--db`, …) is propagated to the service. Manage it with:

```bash
arboretum status      # service status (+ where to read logs)
arboretum uninstall   # stop and remove the service
```

Logs live in `journalctl --user -u arboretum -f` (Linux) or `~/Library/Logs/arboretum/` (macOS). Run `arboretum install --dry-run …` first to print the unit/plist and the exact commands without touching anything.

<details>
<summary>Prefer to set up systemd by hand? (Linux)</summary>

Create `~/.config/systemd/user/arboretum.service`:

```ini
[Unit]
Description=Arboretum — git worktree & Claude Code dashboard
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=%h/.local/bin/arboretum --port 7317 --allow-origin https://MACHINE.TAILNET.ts.net
Restart=on-failure
RestartSec=5
KillSignal=SIGTERM
TimeoutStopSec=10
Environment=NODE_ENV=production

[Install]
WantedBy=default.target
```

```bash
which arboretum                    # adjust ExecStart to the real path if needed
systemctl --user daemon-reload
systemctl --user enable --now arboretum
loginctl enable-linger "$USER"     # start the service at boot, without an open session
journalctl --user -u arboretum -f  # logs
```
</details>

> The one-time **access token is printed by `arboretum install`** (and on the very first manual run with an empty database). The token is hashed and never shown again — store it safely.

## Configuration

Commands: `arboretum` starts the daemon (the default), `arboretum serve` is an explicit alias, `arboretum install` / `uninstall` / `status` manage the background service, and `arboretum help` prints usage.

Daemon options are CLI flags:

| Flag | Default | Description |
|---|---|---|
| `--port <n>` | `7317` | Port to listen on. |
| `--bind <addr>` | `127.0.0.1` | Bind address. Non-loopback is refused unless `--i-know-this-exposes-a-terminal` is set. |
| `--allow-origin <url>` | — | Additional allowed `Origin` (repeatable). Needed for Tailscale/HTTPS access. |
| `--db <path>` | `<data>/arboretum.db` | SQLite database path. |
| `--vapid-contact <mailto/url>` | `mailto:arboretum@localhost` | VAPID contact subject for Web Push. |
| `--print-token` | `false` | Hint about token re-printing (tokens are hashed and cannot be re-shown). |
| `--i-know-this-exposes-a-terminal` | `false` | Acknowledge binding to a non-loopback address. **Avoid** — prefer Tailscale Serve. |

`arboretum install` accepts every daemon flag above (propagated verbatim to the service) plus:

| Flag | Description |
|---|---|
| `--bin-path <path>` | Use this binary in the service instead of `node` + the bundled script. |
| `--label <id>` | launchd label (macOS only, default `fr.lidge.arboretum`). |
| `--dry-run` | Print the unit/plist and commands without applying anything. |
| `--no-enable` | Write the service file but do not enable/start it. |

State (the SQLite database) lives in `$XDG_DATA_HOME/arboretum` (default `~/.local/share/arboretum`).

## Security model

A web terminal is remote code execution *by design*. Arboretum's guardrails are structural:

- Binds to `127.0.0.1` by default; refuses non-loopback binds without an explicit flag.
- Authenticates **every** `/api/**` request **and** every `/ws` upgrade with revocable tokens, and applies a **strict `Origin` check** (the `SameSite=Strict` cookie does not cover WebSocket upgrades — this is the anti cross-site hijacking guard).
- Tokens are stored **hashed** (sha256) and compared in constant time; the bootstrap token is shown only once. The session cookie is an HMAC-signed payload, `HttpOnly` and `SameSite=Strict`, and it automatically gains the `Secure` flag when the request arrives over HTTPS (e.g. behind Tailscale Serve). Login is rate-limited with exponential backoff.
- Sends hardened HTTP headers (CSP, `X-Frame-Options`, `nosniff`, `Referrer-Policy`, conditional HSTS, `no-store` on the API), restricts the data directory to `0o700` and the database to `0o600`, and **encrypts sensitive secrets at rest** (AES-256-GCM).
- Keeps an **audit log** of sensitive operations and offers **GDPR** data export/erasure (Settings → Security & compliance).

Tailscale Serve is **the** way to reach Arboretum from other devices — not just a recommendation: valid HTTPS, tailnet identity, no open ports. The `--i-know-this-exposes-a-terminal` flag is an escape hatch, not a deployment mode; never expose Arboretum directly to the internet.

See [`SECURITY.md`](SECURITY.md) for the full threat model and [`docs/ENTERPRISE_DEPLOYMENT.md`](docs/ENTERPRISE_DEPLOYMENT.md) for hardening in regulated environments.

## What makes it different

| | Arboretum | GitKraken Agent Mode / Conductor / Nimbalyst | Happy / CloudCLI | Anthropic Remote Control |
|---|---|---|---|---|
| Web UI, any device | ✅ | ❌ desktop apps | ✅ | ✅ |
| Visual worktree management (multi-repo) | ✅ | ✅ (single repo, desktop) | ❌ | ❌ |
| Discovers & resumes *existing* terminal sessions | ✅ | ❌ | partial | ❌ |
| 100% self-hosted — zero traffic through third-party servers | ✅ | ✅ | relay server | ❌ relayed through Anthropic |
| Linux-first | ✅ | varies | ✅ | Desktop app has no Linux build |
| Open source | MIT | ❌ / partial | MIT / AGPL | ❌ |

Anthropic's Remote Control is great at piloting *one* session from your phone. Arboretum is the layer it doesn't provide: the consolidated, self-hosted board of all your worktrees and sessions across all your repos.

## A note on Claude usage

Arboretum wraps the **interactive** Claude Code CLI in a PTY — the same thing you run in your terminal, displayed in your browser. It does not use the Agent SDK or headless mode. Anthropic's usage policies around programmatic use may evolve; Arboretum will track CLI releases and document any impact transparently.

## Development

Arboretum is an npm-workspaces monorepo: `@arboretum/shared` (WS/REST protocol, source of truth), `@johanleroy/git-arboretum` (the Fastify daemon, the published package), and `@arboretum/web` (the Vue 3 SPA).

```bash
npm run build        # build shared → server → web (order matters)
npm run typecheck    # tsc -b shared + server
npm test             # vitest across packages
npm run dev:server   # daemon in watch mode
npm run dev:web      # Vite dev server (proxies /api and /ws to the daemon on :7317)
```

End-to-end acceptance scripts (run `npm run build` first):

```bash
node packages/server/scripts/acceptance-p1.mjs   # core: daemon + real WS client
node packages/server/scripts/acceptance-p2.mjs   # session discovery & resume
node packages/server/scripts/acceptance-p3.mjs   # worktrees & session correlation
node packages/server/scripts/acceptance-p4.mjs   # Web Push + WS `answer` command
node packages/server/scripts/acceptance-p5.mjs   # work groups: CRUD + WS broadcast + CASCADE
```

## Support

Arboretum is a free, self-funded side project. If it saves you time, you can support its development:

[![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-johanleroy-FFDD00?logo=buymeacoffee&logoColor=black)](https://buymeacoffee.com/johanleroy)

## License

MIT — see [LICENSE](LICENSE).
