A read-only terminal dashboard for firstmate fleets. It shows the fleet information that firstmate keeps as plain files in each home, for homes on this machine and on other machines over SSH.
The demo fleet is made-up data: a main home, a local second mate (web-mate) and a remote home on devbox.
See Demo to record it again.
For each home:
- Homes: the configured homes and the second mates that their
data/secondmates.mdlists, local or remote. Each home shows its session lock (held, stale or free, fromstate/.lockand apscheck of the pid), its watcher beat, and away mode. - Work: the task records in
state/<id>.meta, the current state of each task from its status logstate/<id>.status, the open decisions and blockers, the captain holds, and the backlog indata/backlog.md(In flight, Queued, Done). - PRs: the PR of each task (the
pr=key in its meta, else the first PR link in its status log) and of each in-flight backlog row. Presspto read the state, checks, mergeability, review decision and unresolved review threads of each PR withgh. - Context: the documents that are loaded into a firstmate session at start (
data/projects.md,data/secondmates.md,data/captain.md,data/captain-shared.md,data/learnings.mdand, in a second mate,data/charter.md), with sizes and the token estimate againstconfig/startup-memory-budget. It also shows the files inconfig/, the projects, the second-mate routes and the otherdata/*.mddocuments. - Activity: the queue counts (wake queue, operational inbox, task inboxes, pending replies), the most recent status events across tasks, and the tail of
state/fleet-ledger.jsonl.
Press Enter on any row to open a detail view: a task with all its meta keys and status events, a decision, a backlog row with its body, a PR, a file, or a ledger record.
Run this command on macOS or Linux:
curl -fsSL https://raw.githubusercontent.com/jayjongcheolpark/fleetdeck/main/install.sh | shThe script downloads the release archive for your OS and CPU, checks it against the release's SHA256SUMS, and installs fleetdeck to ~/.local/bin.
If that directory is not on your PATH, the script tells you the line to add to your shell profile.
- To install to a different directory, set
FLEETDECK_INSTALL_DIR:curl -fsSL … | FLEETDECK_INSTALL_DIR=/usr/local/bin sh. - To install a specific version, set
FLEETDECK_VERSION, for exampleFLEETDECK_VERSION=0.2.0.
To replace the installed binary with the latest release, run:
fleetdeck updatefleetdeck update reads the latest GitHub Release, downloads the archive for the target that the running binary was built for, and checks it against SHA256SUMS.
It runs the new binary once with --version, then renames it over the running binary.
If any step fails, the old binary stays in place.
It needs no GitHub token.
To only check whether a newer release exists, run fleetdeck update --check.
fleetdeck update works for a binary from the install script or from a release archive.
If you installed with cargo, update with cargo.
Each GitHub Release has a fleetdeck-<version>-<target>.tar.gz archive for these targets:
| Target | Platform |
|---|---|
aarch64-apple-darwin |
macOS on Apple silicon |
x86_64-apple-darwin |
macOS on Intel |
x86_64-unknown-linux-gnu |
Linux x86_64 |
aarch64-unknown-linux-gnu |
Linux arm64 |
Each archive holds the fleetdeck binary, this README and the license.
-
Set the version and the target:
VERSION=0.2.0 TARGET=aarch64-apple-darwin
-
Download the archive and the
SHA256SUMSfile:BASE=https://github.com/jayjongcheolpark/fleetdeck/releases/download/v$VERSION curl -fLO "$BASE/fleetdeck-$VERSION-$TARGET.tar.gz" curl -fLO "$BASE/SHA256SUMS"
-
Check the archive against
SHA256SUMS. On macOS, useshasum -a 256in place ofsha256sum.sha256sum --check --ignore-missing SHA256SUMS
-
Extract the archive and put the binary on your
PATH:tar -xzf "fleetdeck-$VERSION-$TARGET.tar.gz" install -m 755 "fleetdeck-$VERSION-$TARGET/fleetdeck" ~/.local/bin/fleetdeck
If you download the archive with a browser, macOS can block the binary because it is not signed.
To allow it, run xattr -d com.apple.quarantine ~/.local/bin/fleetdeck.
You need a Rust toolchain (1.88 or later) to build from source.
cargo install --git https://github.com/jayjongcheolpark/fleetdeck --tag v0.2.0 fleetdeckOmit --tag to build the latest main.
From a clone of this repository, use cargo install --path crates/fleetdeck.
- Create
~/.config/fleetdeck/config.toml(or$XDG_CONFIG_HOME/fleetdeck/config.toml). - Add one
[[home]]entry for each main home. For a remote home, sethostto an SSH alias from your~/.ssh/config.
refresh_secs = 30 # read every home again after this many seconds
timeout_secs = 15 # give up on a machine after this many seconds
[[home]]
name = "main"
path = "/Users/me/Developer/firstmate"
[[home]]
name = "devbox"
host = "devbox" # ssh alias; omit for a home on this machine
path = "~/firstmate" # ~ expands on the remote host
# discover = false # do not add second mates from data/secondmates.mdYou do not need to list second mates.
fleetdeck reads data/secondmates.md in each configured home and adds every route it finds: a local route on the same machine as its parent, and a host: route over SSH.
Without a config file, fleetdeck shows $FM_HOME, or the current directory when it is a firstmate home.
You can also pass homes on the command line:
fleetdeck --home ~/Developer/firstmate --ssh devbox:~/firstmate| Key | Action |
|---|---|
↑/↓, j/k |
Move in the focused pane |
Tab, ←/→ |
Move between the home list and the content |
1 2 3 4, [ ] |
Work, PRs, Context, Activity tabs |
Enter |
Open the selected row in the detail view |
Esc |
Close the detail view |
r |
Read every home again now |
p |
Read PR status with gh |
? |
Help |
q |
Quit |
Other modes:
fleetdeck --jsonprints the collected snapshots as JSON.fleetdeck --frame 160x45 --tab work --select 0prints one rendered frame as text.
fleetdeck reads every home again after refresh_secs.
When the terminal reports that it lost focus, fleetdeck stops the timed reads and shows paused in the status bar.
When the terminal gets focus again, fleetdeck reads every home immediately and starts the timer again.
r still reads every home while fleetdeck is paused.
A terminal that does not report focus changes never pauses fleetdeck.
The Work tab shows the tasks, the open decisions and blockers, the captain holds and the backlog of the selected home:
Enter opens the selected row in a detail view:
p reads each PR with gh:
fleetdeck never writes, moves or deletes anything in a home, and it never changes fleet state.
- One read-only script does all home access.
crates/fleetdeck-core/src/gather.shis the complete set of commands that fleetdeck runs on a machine that holds a home. It only useshead,tail,ls,stat,wc,awk,tr,ps,date,hostnameanduname, and its only redirections go to/dev/null. fleetdeck runs it withsh -sfor a local home and withssh <alias> sh -sfor a remote home, so both paths are the same code. - No firstmate scripts.
fleetdeck parses the files directly.
It does not run
fm-fleet-snapshot.shorfm-bearings-snapshot.sh, because they refresh a cache understate/. It does not runfm-crew-state.sheither, because that script reads terminal panes and calls the forge. - No acknowledgements.
Queues are counted, never drained: the wake queue, the operational inbox and the task inboxes stay exactly as they are.
fleetdeck sends no keys to terminal panes and sends no signals to processes.
Liveness comes from
ps -p <pid>only. - Secrets are not read.
Files in
config/whose names containpassword,secret,token,credential,keyor.bak, or end in.env, are shown by name and size only. - GitHub is read-only.
The PR tab uses
gh pr view --json …and onegh api graphqlquery for review threads. It never posts, edits, approves or merges. - Remote reads go straight to the files.
SSH runs the gather script under the login shell of the remote account.
fleetdeck does not use firstmate's
fm-on.shremote job channel, so it does not interrupt the parent's reply mirror.
The integration test tests/fake_home.rs builds a fake main home and second mate, collects them, and checks that every path, byte and modification time is unchanged.
crates/fleetdeck-core: the data collection library, with no terminal code.collect::collect_fleetreturns onemodel::HomeSnapshotper home. Another front end can reuse it.crates/fleetdeck: the ratatui terminal UI.
The parsers follow the producers in firstmate: bin/fm-classify-lib.sh for status lines and open decisions, the tasks-axi markdown grammar for the backlog, bin/fm-secondmate-registry-lib.sh for second-mate routes, bin/fm-project-mode.sh for projects, and docs/fleet-ledger.md for the ledger.
- The task state comes from the status log, the same way
fm-crew-state.shfalls back to it. fleetdeck does not look at terminal panes, so it cannot tell a working pane from an idle one beyond the busy-state file. - A status log larger than 1 MiB is read from its end only, so a decision that was opened before that point and never closed does not show in the Work tab.
The home summary line still shows firstmate's own count from
state/home-summary.json. - A remote route listed by a remote home (a second mate on a third machine) is skipped.
- PR status supports github.com only.
The GIF and the screenshots come from demo/demo.tape, a vhs script.
To record them again, install Docker and run:
demo/record.shThe script builds fleetdeck and runs vhs in a container.
demo/setup.sh builds the demo fleet from demo/homes.
In the container, small stand-ins in demo/bin replace ssh, gh and hostname.
The "remote" home is read through fleetdeck's real ssh transport, but from a local directory, and the PR data is made up, so the recording makes no network calls.



