Skip to content
dannyfufPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Fleet

Fleet is the native successor to swarm. It pairs a long-running daemon, fleetd, with the native GPUI app and CLI, fleet, to manage copy-on-write worktrees, GitHub pull requests, and terminal sessions that replace tmux. Jobs and terminals belong to the daemon, so background work survives closing a dialog, workspace, or the entire UI.

Requirements

  • macOS.
  • rustup with Rust 1.97.1. rust-toolchain.toml selects the pinned compiler, rustfmt, and Clippy.
  • Xcode and its Metal toolchain. If Metal is missing, install it with xcodebuild -downloadComponent MetalToolchain.
  • Zig 0.15.2. On Apple Silicon, scripts/bootstrap-zig.sh installs and verifies the pinned release and exposes it at ~/.cargo/bin/zig.
  • git and the GitHub CLI, gh, authenticated for the repositories Fleet will manage.

Build and run

make bootstrap
make run

make run builds the workspace, restarts fleetd from the same debug build, and opens the app. Use make run-release for an optimized build, or make release to only build one into target/release. Development explains the two profiles, how to measure a build, how to share compiled dependencies between worktrees, and the cleanup targets. Running fleet without a subcommand opens the app. The client auto-spawns fleetd when its Unix socket is unavailable. Fleet stores config, state, repositories, worktrees, caches, logs, trash, and daemon files under FLEET_HOME, which defaults to ~/.fleet:

FLEET_HOME=/path/to/fleet-home make run

To start copying compatible swarm v1 config and state without modifying ~/.swarm:

./target/release/fleet import --from-swarm

Configuration

Fleet reads $FLEET_HOME/config.json (normally ~/.fleet/config.json). To place worktrees on a Tailscale peer, configure the peer under hosts and optionally make it the default placement:

{
  "hosts": {
    "dev-box": {
      "provider": "tailscale",
      "node": "dev-box",
      "user": "df",
      "sshOptions": [],
      "fleetd": "fleetd",
      "fleetHome": "~/.fleet"
    }
  },
  "defaultHost": "dev-box"
}

node is a Tailscale hostname or MagicDNS name. user is optional, sshOptions defaults to an empty list, fleetd defaults to fleetd, and fleetHome defaults to ~/.fleet. Fleet resolves the node with the local Tailscale CLI and uses non-interactive OpenSSH to run a compatible remote fleetd; it does not expose a daemon port on the tailnet. Set defaultHost to local to keep implicit creation local, or pass --host for one creation. A non-local defaultHost must name a configured host.

The advanced/testing transport is a tagged command entry such as {"provider":"command","run":["sh","-c","exec \"$@\"","--"],"fleetd":"/tmp/fleetd","fleetHome":"/tmp/remote","display":"loopback"}. It executes run followed by each remote command. Existing entries shaped as {"ssh":"arch-dev","swarmCommand":"swarm"} still load, but are legacy probe-only: they report reachability and cannot host Fleet worktrees until migrated to a federated provider.

CLI reference

Run fleet --help or fleet <command> --help for generated help.

Command Description JSON success fields
fleet create <REPO> <SLUG> [--branch <BRANCH>] [--base <BASE>] [--host <HOST>] [--url <URL>] [--default-branch <BRANCH>] [--hooks <JSON>] [--json] Create or find a worktree; --url is required for an unregistered repository. protocol, created, worktree
fleet open <TARGET> Ensure a worktree session exists; accepts a worktree id, stored session name, or repo/slug alias. —
fleet list [--json] List registered repositories and worktrees. protocol, version, repos, worktrees
fleet inspect [IDS]... [--fetch] [--repo <REPO>] [--json] Inspect all or selected worktrees, optionally fetching or restricting by repository. protocol, worktrees
fleet delete <IDS>... [--json] Unconditionally delete one or more exact worktree ids. protocol, ok, results
fleet prune [--dry-run] [--no-fetch] [--kill-sessions] [--repo <REPO>] [--json] Safely prune merged worktrees; fetching is on unless --no-fetch is used. protocol, dryRun, deleted, skipped
fleet kill <ID> [--json] Hard-kill the session for an exact worktree id. protocol, ok
fleet status [--json] Refresh local worktree runtime status. protocol, statuses
fleet path <ID> [--json] Print a local absolute path or <host>:<path> for a remote worktree. protocol, path, host
fleet sleep [SESSION] [--json] Apply sleep policy to a session or worktree; a sole running session is inferred. protocol, kept, closed, sessionKilled
fleet watch list [--session <id>] [--json] List cooperative and daemon-discovered watches (defaults to FLEET_SESSION); human rows contain id, source, label, status, start time, and terminal id. protocol, watches
fleet watch tail <id> [--follow] Print retained text on its original stdout/stderr channel; --follow polls every 250 ms until exit. Raw retained stdout/stderr text
fleet exec [--watch] [--label TEXT] -- CMD [ARGS...] Run a child with byte-exact passthrough; optionally publish a read-only subagent watch using FLEET_SESSION and numeric FLEET_TERMINAL_ID (FLEET_TERMINAL remains the human name). Raw child stdout/stderr; child exit status
fleet agent list List native-agent threads: id, provider, host, session, attention, worktree, title. —
fleet agent new <WORKTREE> --provider <claude|codex> [--model <MODEL>] [--mode <ask|accept-edits|plan|full-access>] Start a native-agent thread in a published worktree and print its id. Reports the typed Unsupported error, naming the terminal fallback, when the provider executable is missing or too old. —
fleet agent send <THREAD> <TEXT> Send or steer a message on a thread. —
fleet agent respond <THREAD> <GATE> <ANSWER> Answer an open permission, question, or plan gate with provider-neutral words. —
fleet agent interrupt <THREAD> Interrupt the active turn; the provider's terminal event stays authoritative. —
fleet agent stop <THREAD> Stop the provider and retain the transcript. —
fleet agent tail <THREAD> [--replay] [--no-follow] [--last <N>] Print one JSON SeqEvent per line until the provider exits; --replay starts from sequence 1. --no-follow prints the retained history and exits instead of following, and --last <N> keeps only its newest N events; both imply --replay, and --last trims the replay only. One SeqEvent object per line
fleet agent terminal [claude|codex] Ensure a repository-level PTY agent session exists (the terminal fallback); defaults to config.agent. —
fleet subagent run --provider <claude|codex> [--brief-file <FILE>] --expect <TEXT> [--worktree <ID>] [--mode <MODE>] [--model <MODEL>] [--effort <EFFORT>] [--title <TITLE>] [--eager] [--caller <THREAD>] [--env <KEY=VALUE>]... [--json] Start a delegated child thread, reading the brief from the file or stdin; the caller is --caller or FLEET_SESSION. --effort is provider-native free text and may be passed without --model, in which case the child keeps that provider's configured default model. --env is repeatable, is merged under Fleet's own FLEET_* variables, is kept across a resume, and refuses a value with no =, an empty or repeated key, a FLEET_-prefixed key, and PATH. The JSON envelope cuts a brief longer than 200 characters and then sets briefElided: true; a shorter brief is carried whole and the key is absent. protocol, delegation, optional warning, briefElided when the brief was cut
fleet subagent complete [<ID>] [--result-file <FILE>] [--blocked] [--json-result] [--json] Report the current child's result, read from the file or stdin; the id falls back to FLEET_DELEGATION, and FLEET_SESSION plus FLEET_DELEGATION_TOKEN are required. protocol, delegation
fleet subagent wait <ID> [--timeout <SECONDS>] [--caller <THREAD>] [--json] Wait for a delegation, returning the child's report on success. --timeout defaults to 540 seconds and has no upper bound; a timeout exits 2 with a still-running line, a terminal record exits 0. The caller is --caller or FLEET_SESSION and is optional; when it matches the delegation's own caller the result is marked consumed and is not delivered again as a user message. The JSON envelope cuts a brief longer than 200 characters and then sets briefElided: true; a shorter brief is carried whole and the key is absent. protocol, delegation, briefElided when the brief was cut
fleet subagent status <ID> [--json] Show one delegation: the fixed-field line, the brief, the child's token usage and cost, and the child's report for a terminal delegation, rendered exactly as wait renders it. The JSON envelope keeps the brief whole. protocol, delegation
fleet subagent list [--caller <THREAD>] [--json] List delegations, optionally for one calling thread, one eight-field tab-separated line each: id, status, provider, child, duration, total tokens, cost, delivery. Token and cost show - when unknown; both cover the child's own thread only. The JSON envelope cuts every brief longer than 200 characters and then sets briefElided: true; a page whose briefs are all shorter is carried whole and the key is absent. protocol, delegations, briefElided when a brief was cut
fleet subagent cancel <ID> [--json] Cancel one live delegation. protocol, delegation
fleet agent-status <working|finished|permission|question|plan> [--session <SESSION>] [--terminal-id <ID>] [--json] Report agent lifecycle or attention; target flags default to FLEET_SESSION and FLEET_TERMINAL_ID. Silent on non-JSON success. protocol, ok, session, terminalId, activity, optional attention
fleet host list [--json] List configured hosts with provider, reachability, daemon link state, version, address, and known agent binaries. protocol, hosts
fleet host doctor <ID> Diagnose resolution, SSH/authentication, remote fleetd, protocol compatibility, and agent binaries for one host. —
fleet host bootstrap <ID> [--ref <GIT_REF>] Build and install a matching fleetd on the host, restart it, and wait for the federated link. —
fleet doctor Run environment diagnostics; exits unsuccessfully when any check fails. —
fleet import --from-swarm Start an import of compatible ~/.swarm/config.json and state.json. —
fleet update Run self-update, wait for completion, then exit with restart code 75. —
fleet version, fleet -v, or fleet --version Print the package version and build Git revision. —

Commands that accept --json emit one compact line using swarm-compatible protocol 1 envelopes. Their errors use {"protocol":1,"error":{"kind":"<kind>","message":"<message>"}}; other commands use human-readable output. This public envelope is separate from daemon IPC version 8; the CLI envelope remains version 1.

Board

Each context has one board, created on first use, and a published worktree can have its own optional board. Select one with --board <id>, --worktree=<owner/name#slug>, or bare --worktree inside a Fleet worktree terminal. The = keeps a following subcommand unambiguous. Otherwise --context <id> or the active context is used. fleet board show displays its columns and cards under a header naming its scope and backend, its project, the age of the last sync, and the dirty and conflict counts; fleet board list lists board summaries with their context or worktree scope. Use fleet board create with --name, --prefix, or --backend local, and fleet board set to change its name, prefix, default repository (--default-repo owner/name, --clear-default-repo), worktree-start setting, conflict policy, branch template (--branch-template "{key}-{slug}"), or whether new cards are pushed to the backend (--push-new-cards, off by default: a card created locally stays local). Board labels are created and removed there too, with --add-label NAME and --remove-label <id|name>: --label on card new and card edit only selects labels the board already carries, so add one to the board before a card can wear it. fleet board sync --wait waits for a remote backend's sync job and prints its counts and errors; --full ignores the incremental cursor and pulls the backend's complete set. Local boards do not support remote sync.

fleet board backends lists the registered backend kinds with their capabilities and the setting keys each accepts; fleet board describe prints what the board's backend reports about itself: its statuses and their categories, labels, properties, people, and the fields it cannot write back. Editing a read-only field fails with the daemon's message.

Point a board at a backend with fleet board set --backend jira --setting project=SP --setting jql='sprint in openSprints()'. --setting is repeatable and takes key=value; values parse as JSON when they are valid JSON (8, true, ["To Do","Done"]) and stay strings otherwise, and key=null removes a key. --backend starts the settings from empty, so a kind change must supply everything the new kind needs in the same command; --setting without --backend keeps the kind and merges into the stored settings. Changing the kind of a board whose cards are already linked is refused; changing the settings of the same kind is not, and costs only the incremental cursor. fleet board create takes the same --setting flag alongside --backend.

The jira backend mirrors one Jira project through the Atlassian CLI, so it needs acli on PATH and an authenticated session (acli jira auth login); Fleet never holds an API token. Priority, estimate, due date, and parent are read-only on a Jira board because acli cannot write them back.

Card commands are fleet board card new <title>, show <key|id>, edit <key|id>, move <key|id> <status> [--index N], comment <key|id> <body>, delete <key|id>, worktree <key|id> [--repo owner/name] [--base REF] [--host H], and resolve <key|id> keep-local|take-remote. New/edit accept description, status, priority, labels, assignee, estimate, due date, and repository flags. edit alone also takes --archive and the --clear-labels, --clear-assignee, --clear-estimate, --clear-due and --clear-repo flags, which are the only way to unset a field; new refuses them, since a card is born with nothing to clear.

Selector precedence is --board, then --worktree, then --context, then the active context. Card selectors accept a display key, a card ID, or — for a card with no remote issue — its local key. Every board command accepts --json for protocol 1 envelopes. In the app, g b opens the board, c creates a card, enter opens its detail, x opens the focused card's remote issue in the browser, and F runs a full sync.

Agent hooks

Fleet detects Claude Code, OpenCode, and Codex working/idle status from terminal output by default. Only explicit agent hooks create attention notifications. Fleet injects FLEET_SESSION and FLEET_TERMINAL_ID into every managed PTY, so the recommended Claude Code configuration is:

{ "hooks": {
    "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "fleet agent-status working" }] }],
    "Stop":             [{ "hooks": [{ "type": "command", "command": "fleet agent-status finished" }] }],
    "Notification": [
      { "matcher": "permission_prompt", "hooks": [{ "type": "command", "command": "fleet agent-status permission" }] },
      { "matcher": "idle_prompt",       "hooks": [{ "type": "command", "command": "fleet agent-status question" }] }
    ],
    "PermissionRequest": [{ "hooks": [{ "type": "command", "command": "fleet agent-status permission" }] }]
} }

PermissionRequest is a current Claude Code hook event and runs when a permission dialog is about to be shown. Without hooks, Fleet still shows heuristic working/idle glyphs, but it does not show agent attention toasts or play an agent attention sound.

Explicit permission, question, plan, and finished notifications use an in-app toast and the macOS Glass sound by default. Either channel can be disabled independently in ~/.fleet/config.json (or $FLEET_HOME/config.json):

{
  "ui": {
    "notifications": {
      "toast": true,
      "sound": true
    }
  }
}

Keyboard basics

Fleet is modal. The status bar always shows the current mode; overlays shadow the Hub or Workspace until closed.

Mode Purpose Leave with
Normal Navigate Hub repositories, worktrees, and pull requests. Open a session
Terminal Send keys to the active PTY. ctrl-s enters Prefix
Agent Type into a native agent thread's composer and answer its decision cards. Select another tab, ctrl-s x
Prefix One-shot Workspace or Agent popup command after ctrl-s. Next key or Esc
Scroll Navigate and select terminal scrollback. Esc, q, or i
Filter Filter the current list. Enter or Esc
Palette Search navigation and actions. Enter or Esc
Dialog Edit or confirm an action. Enter or Esc
Jobs Inspect, cancel, or retry daemon jobs. J, q, or Esc
Daemon Report startup, disconnect, or doctor state. Reconnect, Esc, or ctrl-q
FirstRun Guide initial creation or import. Complete an offered action

The 16 keys and key groups to learn first are:

Key Action
j / k, ↓ / ↑ Move the cursor.
h / l, ← / →, S-Tab / Tab Focus the previous or next Hub pane.
gg / G Jump to the first or last row.
Enter, o Select a repository or open a worktree/session.
1–9, gt / gT Switch to a numbered, next, or previous context.
p Toggle Worktrees and Pull requests.
n Clone a repository or create a worktree in the focused pane.
d Delete the selected repository or worktree, with confirmation.
/ Filter the current list.
: Open the command palette.
i Toggle the detail panel.
a / A In the Hub, open the floating Claude / Codex agent popup; in a Workspace, ctrl-s a / ctrl-s A start a native agent thread instead.
r Refresh status, pull requests, and discovery as a job.
J Open the Jobs panel.
? Open help.
Esc / q, ctrl-q, ctrl-shift-q Close the top layer; quit the app; or quit and stop the daemon.

Inside a terminal, every bare key goes to the PTY. Dragging selects text and copies it immediately; cmd-c copies the current selection and cmd-v pastes through the terminal's bracketed-paste path. Soft-wrapped visual rows copy as one logical line. ctrl-c and ctrl-v remain terminal keys. ctrl-s is the only Workspace prefix: ctrl-s s returns to Hub, ctrl-s S sleeps then returns, ctrl-s 1–9 switches tabs, ctrl-s h/l changes tabs, ctrl-s w opens the last session, ctrl-s c/x creates/closes a tab, ctrl-s a/A starts a native Claude/Codex agent thread, ctrl-s F opens the floating agent popup (the terminal fallback), ctrl-s [ enters Scroll, ctrl-s ] pastes, and ctrl-s J/? opens Jobs/help. Inside the popup, ctrl-q hides it without stopping the agent session. Use ctrl-s ctrl-s to send a literal ctrl-s. See docs/KEYMAP.md for the complete, authoritative map.

A native agent thread is a tab drawn by Fleet, not a PTY: the status bar reads AGENT, Enter sends the composer and Shift-Enter inserts a newline, Esc interrupts a running turn, and a permission, question or plan appears as a card in the thread that answers to bare keys (y/a/n/e/Esc, 1-4, y/n). The tab carries an amber dot when the agent is waiting on you. The daemon owns the thread, so it survives closing the app, and its transcript survives a daemon restart. fleet agent drives the same threads from the terminal, and the popup on ctrl-s F stays available whenever a provider is missing or too old. See docs/NATIVE-AGENTS.md.

Architecture

fleet-app     binary `fleet`: GPUI state mirror, screens, dialogs, and terminal rendering
fleet-daemon  binary `fleetd`: stores, adapters, services, jobs, PTYs, and Unix socket server
fleet-core    domain types, schemas, validation, defaults, and pure helpers; no I/O
fleet-proto   length-prefixed JSON Request/Response/Event types and terminal frame updates
fleet-term    portable PTYs, VT engine abstraction, Ghostty VT, terminal host, and key encoding
fleet-client  async daemon connect/spawn, requests, events, and terminal attachment
fleet-ui-kit  domain-independent GPUI theme, assets, Lucide icons, and reusable components
fleet-git     typed, byte-preserving Git backend that shells out to `git` with explicit argv
fleet-lazygit native lazygit clone: panels, keymap, diff view, and overlays
fleet-cli     Clap parser, protocol-1 JSON envelopes, and human-readable output
runtime       `fleet` -> `fleet-client` -> `$FLEET_HOME/fleetd.sock` -> `fleetd`
ownership     daemon owns jobs, sessions, PTYs, state, and filesystem work; clients mirror it

The dependency direction is core <- proto <- {term, client, cli} <- {daemon, app} and git <- lazygit <- app; the UI kit depends only on GPUI. Read architecture, the UX specification, and the design system for the full contracts, and the documentation index for everything else.

Development

The checked-in Makefile provides all workspace targets:

make help
make check
make build        # dev profile; make release for the optimized one
make run
make daemon
make test
make fmt
make clippy
make build-info   # toolchain, compiler cache, target/ size
make prune        # also clean-incremental, clean-release, clean, fresh

make test runs the workspace tests. Targeted Cargo tests work normally, for example cargo test -p fleet-cli. Keep parallel worktrees on separate target directories if overriding CARGO_TARGET_DIR; shared external artifacts can be stale. Share compiled dependencies between worktrees with sccache instead (Development).

Run the complete UI-kit gallery or one of its focused galleries:

cargo run -p fleet-ui-kit --example kit_gallery
cargo run -p fleet-ui-kit --example gallery_data
cargo run -p fleet-ui-kit --example gallery_input
cargo run -p fleet-ui-kit --example gallery_structure
cargo run -p fleet-ui-kit --example gallery_terminal
cargo run -p fleet-ui-kit --example gallery_agent

Testing

make test is the workspace suite. The GUI itself is tested end to end by fleet-harness, which boots a private fleetd and a harness-mode fleet against a temporary FLEET_HOME, drives them over a Unix socket, and leaves a run directory of screenshots, UiSnapshot dumps, logs and a report:

make harness            # the whole scenario corpus, on an isolated compositor output
make harness-headless   # the scenarios that need no pixels
make harness-one SCENARIO=scenarios/pointer-basics.scenario
make harness-prune      # delete run directories older than SWEEP_DAYS

make harness-headless selects the scenarios that name no shot and no clipboard, because neither works without a compositor; the same slice rides make test. Everything else — the keyboard, the pointer, fixtures and daemon faults — works in that lane, so a scenario written to run in both drives the app, asserts on the snapshot and takes no shot.

A scenario is a line-oriented text file — key, type, click, await, assert, shot, dump, quit — and the structured dump, not the screenshot, is what says whether Fleet behaved. docs/TESTING-HARNESS.md is the authority for the protocol, the snapshot, the grammar and the fixtures, and docs/DEVELOPMENT.md is how you run one.

See docs/DEVELOPMENT.md for Zig details and logs.

Status

Fleet supports worktrees, terminal sessions, lifecycle operations, and native Claude threads on configured Tailscale hosts through daemon federation. The app and CLI connect only to the local fleetd; it routes work to the owning host, keeps cached remote inventory visible while a host is offline, and resumes routing after the link recovers. Terminal sessions survive closing the app because their owning fleetd retains them, and they survive that daemon restarting: every PTY lives in a detached holder process the next daemon reattaches to (docs/ARCHITECTURE.md, "Detached PTY holders"). Native agent threads persist too: transcripts live under the owning daemon's $FLEET_HOME/agents/, and a thread with a provider resume cursor is resumed the next time it is opened.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages