There were three answers to "how do I test against a daemon", and the CLI
— the thinnest client — had none, so its formatting and error handling
were exercised by compiling and forking the real binary plus tmux.
Adds crates/maestro-testing with a MockDaemon that serves the real /api/*
surface from maestro-protocol types, backed by a Vec<Session>. It replaces
two ad-hoc stubs: maestro-web's, which returned [{"id":"session-1"}] —
not a valid Session — and maestro-tui's scripted socket with a hardcoded
timeline.
maestro-cli gets its first tests against a daemon: six cases covering the
catalog, that DaemonInfo carries the version and presets its old private
copy dropped, that a structured failure surfaces the daemon's own message,
and that connection-refused stays distinguishable from an HTTP rejection.
maestro-web's proxy suite gains an end-to-end check that an upstream
rejection reaches the browser as a 4502 close carrying the daemon's
message — upstream_close_reason had only unit tests.
e2e keeps the tmux-shaped tests. cli_list_reads_from_daemon still runs the
shipped binary as a subprocess but now against MockDaemon, and
tui_integration.rs is deleted: the real daemon's /api/events contract is
covered by maestro-daemon/tests/local_api.rs and the client reducer by
maestro-tui/tests/catalog_ws.rs. The e2e suite drops from ~32s to ~20s.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
maestro-web
Standalone HTTP server that hosts the Maestro browser UI and reverse-proxies
to a co-located maestrod.
browser ──HTTP──▶ maestro-web (:8080) ──HTTP/WS──▶ maestrod (:7070)
│
└── / serves the embedded SPA (web/dist)
/api/... proxied unmodified
/api/attach/:id, /api/events proxied as WebSocket
Trust model
maestro-web does not authenticate anyone. Whoever can reach the bind address
can use it. Pick the bind accordingly:
| Bind | Reach | Use when |
|---|---|---|
127.0.0.1:8080 |
Local users on this host | Default. Same as tmux. |
<wg-ip>:8080 |
Devices on your WireGuard mesh | Phone/tablet over VPN. |
<lan-ip>:8080 |
Devices on your trusted LAN | Home / lab networks. |
| Public IP | Don't | — add real auth first. |
Config
The binary reads ~/.config/maestro/config.toml. The [web] section:
[web]
bind_addr = "127.0.0.1:8080" # default
daemon_url = "http://127.0.0.1:7070" # default
Env-var overrides (all optional):
MAESTRO_WEB_BIND_ADDRMAESTRO_WEB_DAEMON_URLMAESTRO_WEB_DEV_PROXY_TARGETMAESTRO_CONFIG— override the unified config-file path.
Build
The SPA bundle lives in ../../web and is embedded into the binary at compile
time via include_dir!. Build the frontend first:
pnpm -C web install
pnpm -C web build
cargo build -p maestro-web --release
Or, from the repo root:
just web
A fresh checkout compiles even without pnpm build (an empty web/dist
exists in-tree); the binary just serves a placeholder until the bundle is
built.
Run
target/release/maestro-web
Then point a browser at http://127.0.0.1:8080/.
Development workflow for the frontend
Use Vite directly so you get HMR. From web/:
pnpm dev
Vite serves on :5173 and proxies /api/* straight to maestrod at
127.0.0.1:7070 (override with MAESTRO_DAEMON_URL=...). You don't need to
rebuild the Rust binary while iterating on the frontend.