Control coding agents (Claude Code and others) running on remote machines — projects, sessions, and live agent chat across all your hosts in one UI.
Two parts:
fleet-hub/— a static React SPA with no backend. The browser talks directly to the server API on each host.fleet-server/— the per-host server: a single-binary fork of the CloudCLI UI server (no Node.js required on hosts). Stock CloudCLI also works.
Design goal: installation of both parts must be as simple as possible —
one command per host for the server, one command for the hub. See
docs/installation-simplicity.md for the
current state and roadmap, and "Toward one-click" below
for what's left.
Two things get installed: fleet-server on each machine you want to control (including your own laptop), and the hub on the machine you drive from.
fleet-server drives whatever agent CLIs are already on the host, so install and log into the ones you want first (skip any you won't use):
# Claude Code — https://docs.claude.com/en/docs/claude-code
claude # then sign in once (interactive)
# Codex — https://developers.openai.com/codex/cli
codex login # keychain login is detected automaticallyThen install fleet-server (single binary, no Node.js/npm) — one command
that installs and starts a persistent service on :3011:
# without Homebrew — installs the binary + a launchd/systemd service
# IPv6-first bind by default, with IPv4 fallback when needed
curl -fsSL https://raw.githubusercontent.com/pfedotovsky/agents-fleet-hub/main/fleet-server/scripts/install.sh | sh -s -- --service
# or with Homebrew (macOS/Linux)
brew install pfedotovsky/tap/fleet-server
fleet-server auth setup
brew services start fleet-serverinstall.sh --service records the claude path visible in the install shell,
so service managers with a minimal PATH can still launch Claude Code. Verify
it's up: curl http://localhost:3011/health. Opening the host URL in a browser
only shows a small status page. For remote access, create the host
username/password locally:
fleet-server auth setupThe installer does not prompt for credentials, and fleet-server does not expose
a browser/API account-setup flow. Drop
--service (or run fleet-server yourself instead of brew services) if you
just want the binary without a background service. See
fleet-server/README.md for env vars, --port/
--host flags, and migrating an existing CloudCLI host. (Hosts still running
stock CloudCLI on :3001 keep working too.)
Remote hosts: make the port reachable — fleet-server binds all interfaces
with IPv6-first defaults (HOST=::, falling back to 0.0.0.0 only when IPv6
is unavailable), then open the firewall or reach it over a VPN/SSH tunnel.
Anyone who can reach the port + sign in can run code as that user, so don't
expose it on the open internet without TLS in front.
Desktop app (recommended):
brew install --cask pfedotovsky/tap/agents-hub # macOSLinux builds and the source/dev-server path are in
fleet-hub/README.md (npm install && npm run dev →
http://localhost:5173).
In the hub: Settings (gear) → add a host with its base URL
(http://localhost:3011, or http://my-vm.example.net:3011) → sign in. A
remote fleet-server host must already have a local account from
fleet-server auth setup; use that username/password here. Only the JWT is
stored in the hub, never the password. Projects and sessions from that host
appear immediately.
Check what a host is running (the version field of the public health
endpoint; instanceId/hostname/dataDir also identify which server is
answering — useful when a port is forwarded):
curl http://localhost:3011/healthThen upgrade, matching how you installed it. On both paths the service must be restarted — replacing the binary does not restart a running service, so the old version keeps serving until you do:
# Homebrew
brew update && brew upgrade fleet-server
brew services restart fleet-server # required — old binary runs until restart
# install.sh — just re-run the installer; --service reinstalls + restarts the unit
curl -fsSL https://raw.githubusercontent.com/pfedotovsky/agents-fleet-hub/main/fleet-server/scripts/install.sh | sh -s -- --serviceConfirm it took by re-checking /health (version should be the new one, and
instanceId will have changed because the process restarted). Data in
~/.fleet-server is untouched by an upgrade. Maintainers cutting a new server
release follow docs/releasing.md.
Run the complete source stack from the repository root (Node.js, npm, and Bun are required):
npm run devThe command installs missing project dependencies, starts the source server
first, waits for its health check, and then starts Vite. Ctrl+C stops both.
The installed Homebrew release can stay running:
| Purpose | Address | Data |
|---|---|---|
| Released/Homebrew UI | http://localhost:3011/fleet-hub/ |
~/.fleet-server |
| Released server/API | http://localhost:3011 |
~/.fleet-server |
| Vite development UI | http://localhost:5173 |
Browser-local |
| Source development server | http://localhost:3012 |
~/.fleet-server-dev |
Vite discovers 3012 as localhost (dev); packaged builds discover 3011. To
run either part separately:
cd fleet-hub && npm run dev # UI only, :5173
cd fleet-server && bun run dev # server only, :3012To develop the desktop (Tauri) shell instead of the browser build, use
npm run tauri dev in fleet-hub/ (needs Rust). More detail in
fleet-hub/README.md and
fleet-server/README.md; the release process is in
docs/releasing.md.
Already done: install.sh --service installs and starts a persistent
launchd/systemd unit in one command, and the hub auto-discovers a localhost
fleet-server/CloudCLI and offers a one-click "Add". Remaining friction,
roughly highest-leverage first (tracked privately as GitHub issues):
- Signed & notarized desktop app. If the cask ships an unsigned build, macOS Gatekeeper adds a scary right-click-open step. Notarization makes the hub install truly one-click. (Windows/Linux signing likewise.)
- Agent-CLI bootstrap + clear auth state. The host still needs
claude/codexinstalled and logged in. Login is interactive per provider, but the installer could detect what's missing and print exact next steps, and the hub already surfaces "not signed in" (issues #13/#15) — make that a first-class, actionable banner rather than a silent empty chat. - Keep hosts current. Self-update was stripped from the fork; today it's
a manual
brew upgrade fleet-server. A tiny "update available" check (compare/healthversion to the latest release) or an opt-in periodic upgrade would keep a fleet from drifting. - Remote reachability helper. The hardest part for remote VMs is the network (bind address, firewall, TLS). A documented one-liner tunnel, or a hub-side "paste this on the VM" snippet, would remove the last manual step.
The current north star: one command per host (install + start + a nudge to log the agent CLIs in) and one install for the hub (signed app). The localhost path is already effectively one-click; remote hosts still need the network sorted out by hand.
Hub-created Claude sessions don't show in claude --resume. fleet-server
runs Claude through the Agent SDK, so its sessions are tagged
CLAUDE_CODE_ENTRYPOINT=sdk-ts, and the interactive claude --resume / /resume
picker deliberately hides SDK-tagged sessions. The transcripts are still written
normally to ~/.claude/projects/ and are fully resumable — either from the hub,
or in the terminal by explicit id: claude --resume <session-id> (the id is the
.jsonl filename). To make new hub sessions appear in the picker, export
CLAUDE_CODE_ENTRYPOINT=cli for the fleet-server process (e.g. in
~/.fleet-server/.env), at the cost of reporting SDK usage as interactive. Full
detail in docs/architecture.md.
A host's JWT allows running code as your user on that machine. Tokens live in your browser's localStorage only — don't host this page anywhere public.
This repository contains two differently-licensed parts:
fleet-server/is a fork of the CloudCLI UI server and is licensed AGPL-3.0-or-later with upstream's Section 7 additional terms — seefleet-server/LICENSE,fleet-server/NOTICE, andfleet-server/UPSTREAM.md.fleet-hub/is separate, independently developed work (no CloudCLI code) and is licensed MIT — seefleet-hub/LICENSE. It talks tofleet-serveronly over the network API, so it is not a derivative work of the AGPL server.- Everything else in the repository is also separate from
fleet-serverand not covered by its license.
Do not move code across this boundary.