The TUI read its leader from `[tui].leader`; the web used `DEFAULT_LEADER`, a module constant. Rebinding one left the other on `ctrl-a`, so the two escape hatches hanging off the leader (`leader ctrl-k`, `leader leader`) moved on one client and not the other. `GET /api/info` now carries `leader`. The web binds that when it arrives and rebuilds the leader-owning `CommandSet`, so the `?` overlay and the command bar render the configured chord; an absent or empty field — an older daemon, or an unreachable `/api/info` — leaves it on `ctrl-a`. Chose the existing `[tui].leader` served as-is over a new shared key: one knob, no precedence rules between two spellings, and no migration for operators who already set it. The name is a wart — it is the leader for every client now, not the TUI's — and `docs/COMMAND-BAR.md` and `docs/INSTALL.md` say so.
11 KiB
Installing Maestro
The repo is private, so grab install.sh while signed in to gitea, then run it:
- Open https://git.naps.pt/naps62/maestro/src/branch/main/install.sh, click
Raw, save as
install.sh. Or from any existing clone:git show origin/main:install.sh > install.sh - Run it:
chmod +x install.sh && ./install.sh
The bootstrap script builds maestro-installer into a temp directory, runs the
wizard, and cleans up — only the unified maestro binary ends up persisted in
~/.cargo/bin.
Updating is the same command. Re-running ./install.sh detects an existing
systemd unit, refreshes it, and runs systemctl --user restart maestro instead
of start. No --update flag needed. The restart keeps your sessions: the unit sets
KillMode=process, so systemd signals only the daemon — the tmux server (and
every session) survives, and the new daemon re-adopts them on start. A unit
written before this default lacks the line; re-run ./install.sh to refresh it.
Inside the script, cargo install --git https://git.naps.pt/naps62/maestro …
uses your already-configured git credentials for this host (HTTPS helper or
SSH agent). If only SSH is set up:
MAESTRO_REPO_URL=ssh://git@git.naps.pt/naps62/maestro ./install.sh
Pass flags through to the wizard after ./install.sh:
./install.sh --yes --no-systemd
The wizard:
- Runs
cargo installfor the unifiedmaestrocrate (pinned to the committedCargo.lockvia--locked). The same binary hosts the CLI, the per-host daemon (maestro daemon), and the TUI, gated by Cargo features. - On systemd hosts, optionally writes a user unit at
~/.config/systemd/user/maestro.service, reloads systemd, and offers to enable + start it.
The binary lands in ~/.cargo/bin/maestro — make sure that's on your $PATH.
Nix / NixOS
The repo is a flake. It exposes the maestro package, the SPA bundle it
embeds, and a Home Manager module:
nix run git+https://git.naps.pt/naps62/maestro -- list # the maestro binary
packages.maestro is the unified CLI + daemon + TUI binary, with the browser
SPA embedded, wrapped so tmux and git are on its PATH. The Nix build
points MAESTRO_WEB_DIST at packages.webFrontend, so build.rs skips pnpm
entirely and no network fetch happens mid-compile. Set that variable yourself
to reuse a bundle built elsewhere; leave it unset and the build falls back to
running pnpm install && pnpm build in web/.
Home Manager, with the daemon running as a user unit (same trust boundary as the tmux server it wraps):
{
inputs.maestro.url = "git+https://git.naps.pt/naps62/maestro";
# in your Home Manager configuration:
imports = [ inputs.maestro.homeManagerModules.default ];
services.maestro = {
enable = true; # installs the binary, runs `maestro daemon`
settings = {
daemon.bind_addr = "127.0.0.1:7070";
};
};
}
settings is written verbatim to ~/.config/maestro/config.toml in the schema
below; every field has a default, so {} writes no file at all. The daemon
serves the browser UI off that same address, and there is no auth layer — bind
it to a LAN or VPN address only on a trusted segment, never a public IP.
services.maestro.daemon.enable = false installs the CLI and TUI without
running a local daemon, for a host that only talks to another machine's.
The daemon unit carries the same KillMode=process as the wizard-written one,
so systemctl --user restart maestro-daemon leaves the tmux server and every
session alive.
Thin builds for single-role hosts
The default install pulls in everything (cli + daemon + tui + web).
Single-role hosts can opt out of unused dependencies:
# Daemon-only host — no CLI or TUI:
cargo install --git https://git.naps.pt/naps62/maestro \
--no-default-features --features daemon --locked maestro
# CLI-only client (no local daemon, no TUI):
cargo install --git https://git.naps.pt/naps62/maestro \
--no-default-features --features cli --locked maestro
Neither of those builds the browser UI: web is left out, so pnpm and node
aren't needed. Add --features daemon,web to put it back.
The wizard always installs the default-feature build.
The terminal UI: maestro tui
maestro tui is the keyboard-and-mouse driven terminal UI client. It ships
in the default feature set, so a vanilla cargo install maestro builds it.
A bare maestro opens the TUI too — the missing subcommand defaults to tui
(#119), so maestro --daemon-url http://host:7070 works as well. Builds
without the tui feature have no UI to open and print help instead (exit 0).
Drop the tui feature to skip it on hosts that don't need the UI surface
(--no-default-features --features cli,daemon).
It talks to the local maestro daemon over http://127.0.0.1:7070 by default.
Override the daemon URL with --daemon-url or $MAESTRO_DAEMON_URL.
The [tui] section in ~/.config/maestro/config.toml (or whatever path you
pass via the global --config flag) controls the keymap. Shipped defaults
cover every v1 action — only override what you want to change:
[tui]
theme = "dark" # see the theme list below
leader = "ctrl-a" # one byte that escapes Insert mode; the web reads
# the same setting off /api/info
chord_timeout_ms = 1000 # how long to hold a partial chord like `ctrl-w`
[tui.keymap.normal]
"i" = "EnterInsert"
"n" = "OpenSpawnModal"
"N" = "FastForwardSpawn"
"x" = "KillHighlighted"
"r" = "RenameHighlighted"
"?" = "ShowHelp"
"ctrl-w h" = "FocusSidebar"
"enter" = "AttachHighlighted"
"pageup" = "ScrollPanePageUp"
"pagedown" = "ScrollPanePageDown"
[tui.keymap.insert]
# (leader is added automatically as LeaveInsert; only override here when you
# want extra Insert-mode bindings)
theme selects the complete TUI palette. dark is the default: Maestro's
graphite palette, near-neutral chrome where the activity colours are the only
hue on screen; light is its light peer; ansi uses the terminal's named
ANSI colours for terminals without truecolour. Five house presets recolour
the graphite base with an accent, and the remaining presets adapt well-known
editor themes — in both groups individual colours are lifted or darkened
where they would fall short of the WCAG AA contrast floors the palette tests
assert:
| name | based on |
|---|---|
ice-cyan |
graphite base, signal-cyan accent |
periwinkle |
indigo base, blue-violet accent |
amber-phosphor |
warm graphite, CRT-amber accent |
ember |
warm graphite, orange-red accent |
orchid |
violet base, muted violet accent |
solarized-dark |
Solarized Dark |
solarized-light |
Solarized Light |
catppuccin |
Catppuccin Mocha |
github-dark |
GitHub Dark |
github-light |
GitHub Light |
tokyo-night |
Tokyo Night |
gruvbox |
Gruvbox Dark |
nord |
Nord |
dracula |
Dracula |
An unknown name is rejected as an [tui].theme configuration error.
The full action vocabulary is the Action enum in
crates/maestro-tui/src/action.rs.
The browser UI
The daemon serves the SPA off its own listener. Open
http://127.0.0.1:7070/ — or whatever [daemon].bind_addr is — and the
browser talks to the same process the CLI and TUI do. One binary, one port,
nothing extra to install or start.
Build prerequisite. The web cargo feature is on by default and embeds the
bundle at compile time: build.rs runs pnpm install --frozen-lockfile && pnpm build in web/, so pnpm and node must be on PATH. Two ways around
that — point MAESTRO_WEB_DIST at a bundle built elsewhere, or build with
--no-default-features and pick the features you want (see Thin builds
above).
Trust model. There is no authentication layer: anyone who can reach
[daemon].bind_addr can load the UI and drive every session on the host.
Pick 127.0.0.1 for local-only, a WireGuard/LAN address for one-segment
access, and never a public IP — the network perimeter is the only boundary
Maestro has.
Upgrading from the two-binary install. Re-running ./install.sh removes
the old maestro-web.service unit for you; nothing to undo by hand. A leftover
[web] block in ~/.config/maestro/config.toml is ignored rather than
rejected, but it no longer does anything — move the address you care about to
[daemon].bind_addr.
Flags
| Flag | Purpose |
|---|---|
--git <url> |
Install from a different git repo (default: this one). |
--branch <name> |
Pin a branch with --git. |
--path <dir> |
Install from a local workspace checkout (--path + -p maestro). |
--no-locked |
Drop --locked from cargo install (resolves a fresh dep tree). |
--exec-start <path> |
Override the systemd unit's ExecStart (default ~/.cargo/bin/maestro daemon). |
--no-systemd |
Skip the systemd step even if detected. |
-y, --yes |
Non-interactive: install the binary + drop systemd unit + reload + enable, but don't auto-start the unit. |
Where tmux sockets land
maestro daemon defaults TMUX_TMPDIR to $XDG_RUNTIME_DIR (= /run/user/$UID)
so a plain tmux ls in your shell — which typically uses the same env var
— sees maestro-managed sessions. Override in
~/.config/maestro/config.toml:
[daemon]
tmux_tmpdir = "/tmp"
Pick /tmp if the daemon must outlive your login session and you can't run
loginctl enable-linger (the runtime dir is wiped on logout, taking the
tmux sockets with it). The env var MAESTRO_DAEMON_TMUX_TMPDIR takes
precedence over the file.
What the systemd unit looks like
The wizard writes a user unit (systemctl --user), since maestro daemon
runs per-user and uses tmux sockets under $XDG_RUNTIME_DIR. Body:
[Unit]
Description=Maestro per-host daemon
Documentation=https://git.naps.pt/naps62/maestro
After=network-online.target
[Service]
Type=simple
ExecStart=%h/.cargo/bin/maestro daemon
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
Check status / logs:
systemctl --user status maestro
journalctl --user -u maestro -f
Running the wizard without the bootstrap script
If you'd rather skip the script:
cargo install --git https://git.naps.pt/naps62/maestro maestro-installer
maestro-install
This leaves maestro-installer permanently in ~/.cargo/bin — the bootstrap
script avoids that by installing to a scratch dir.
Fully manual install (no wizard)
cargo install --git https://git.naps.pt/naps62/maestro --locked maestro
maestro daemon # foreground; or wire your own service manager
Every setting has a default, so ~/.config/maestro/config.toml is optional —
create it only to change the bind address, spawn presets, or TUI
keymap. (Worktrees live inside their base repos at <repo>/worktrees/<branch>;
set [daemon].worktree_root only to override that.)
Non-Linux hosts
The wizard works on macOS for the cargo install part. The systemd step is
automatically skipped (probe of
/run/systemd/system). launchd integration is not yet supported — start
maestro daemon from your shell or your own service manager.