Commit Graph
9 Commits
Author SHA1 Message Date
Miguel PalhasandClaude Opus 5 ef3d314296 refactor(maestro): finish moving the e2e harness off raw reqwest
ci / rust (pull_request) Successful in 3m11s
ci / rust (push) Successful in 1m27s
Restores the harness's deliberate 5s per-request budget, which the shared
client's 30s default had silently replaced, and converts the last two
hand-rolled daemon calls in e2e.rs (recent-dirs, gitroot). Drops reqwest
from maestro-e2e entirely.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 12:22:35 +01:00
Miguel PalhasandClaude Opus 5 02356f84ab refactor(maestro): route every client through the shared DaemonClient
ci / rust (pull_request) Successful in 3m0s
The CLI, TUI and end-to-end harness each carried their own client: three
reqwest builders with three timeout policies, three copies of the
success-or-body check, and route strings at every call site. Two of them
discarded the daemon's structured error and reported only a status code.

Deletes `maestro-cli/src/http.rs`, both `ws_base` copies in the TUI, the
CLI's partial `DaemonInfo` (it dropped `version` and `presets`), and the
e2e harness's `check_ok`. `maestro-web` gains a protocol dependency and
stops building the error envelope with a format string and reading it back
as untyped JSON; the daemon and the proxy now register routes from the same
constants.

`maestro_web::router` returns `Result` — an unusable daemon URL fails at
startup instead of at first connect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 12:11:43 +01:00
Miguel Palhas 1bb464be4b refactor(maestro): flatten web UI to a single host
Session tree nests by working directory only; the daemon chips in the
header become a session tally, and the spawn form loses its host picker.
API calls move from /api/daemons/:id/* to /api/dirs, /api/recent-dirs,
/api/gitroot, and /api/info.
2026-08-22 10:14:10 +01:00
Miguel Palhas d006f4a7bd refactor(maestro): drop registry, p2p, and auth from daemon core
Maestro is now single-host: one daemon owns the machine's sessions and
clients talk to it directly. Removes the registry crate, the daemon's
registry client/broker/catalog mirror, JWT attach auth, cookie auth, and
the perimeter (T2) transport config.

The daemon API is now unauthenticated on its bind address, same trust
model as tmux. Session and CreateSessionRequest lose daemon_id;
RegistryEvent becomes CatalogEvent with no daemon variants; attach moves
to WS /api/attach/:id with no token.
2026-08-22 10:00:24 +01:00
naps62-yoloandClaude Opus 4.8 70c592e884 fix(maestro): surface cross-host attach failures instead of silent close
ci / rust (push) Failing after 2m11s
Cross-host (T2-brokered) attach fails when no shared perimeter is
configured: the daemon rejects the upgrade with a structured 502
(no_perimeter_candidate / no_local_perimeter / perimeter_mismatch /
peer_unreachable), but maestro-web's WS proxy had already upgraded the
browser socket and merely dropped it on the upstream failure — the
attach pane flashed and vanished with no reason shown.

- maestro-web proxy: on a failed upstream handshake, relay the daemon's
  error message to the browser as an application close frame (4502 +
  reason) instead of a bare drop; parse the structured JSON error body.
- web attach.ts: on a 4000+ close with a reason, show it in the pane
  header and terminal body and keep the pane up, rather than auto-closing.
- daemon: WARN at startup when no perimeter_id/private_addr is configured,
  since cross-host attach is then unavailable.
- docs/INSTALL.md: document the required matching perimeter_id + per-host
  private_addr for cross-host attach.

Closes #39

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:08:03 +00:00
naps62-yoloandClaude Opus 4.8 b0774934e8 fix(maestro): bound web proxy request bodies + stream responses (NAP-257)
ci / rust (push) Successful in 6m56s
`proxy_http` buffered request bodies with `to_bytes(.., usize::MAX)` and
materialized full upstream responses with `.bytes()`. Anyone reachable on the
bind address could POST a multi-GB body (the cookie middleware admits any
signed cookie) and OOM the process; a large upstream reply did the same.

Cap buffered request bodies at 8 MiB and return 413 on overflow; stream the
upstream response straight through via `bytes_stream()` → `Body::from_stream`
so it's never fully held in RAM.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 21:12:17 +00:00
naps62-yoloandClaude Opus 4.7 bc3237287f feat(maestro): maestro-web standalone binary + reverse-proxy (NAP-241)
New crate crates/maestro-web/: axum server that

* embeds the web/dist SPA bundle via include_dir! (graceful placeholder when
  pnpm build hasn't run yet — keeps fresh checkouts compiling)
* reverse-proxies /api/* HTTP to a co-located daemon (default
  http://127.0.0.1:7070) via reqwest, stripping hop-by-hop headers
* reverse-proxies /api/attach/:id and /api/events WebSockets via
  tokio_tungstenite::connect_async + a tokio task pumping both directions
* reads bind_addr / daemon_url / dev_proxy_target from the new [web]
  config section; env-var overrides MAESTRO_WEB_BIND_ADDR / _DAEMON_URL /
  _DEV_PROXY_TARGET

Integration tests spin up a mock daemon + maestro-web pair on random ports
and verify HTTP forward, SPA fallback, WS echo, and 502 on unreachable
daemon.

Adds a top-level Justfile (`just web` = pnpm install + pnpm build + cargo
build), crates/maestro-web/README.md (config + trust model + dev workflow),
and a docs/INSTALL.md section on optional web UI install.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:23:14 +00:00
naps62-yolo 7409cd6ad8 Revert "fix(maestro): maestro-web is now a proxy, not a local registry (NAP-231)"
This reverts commit 3b6dd50c1d.
2026-05-26 09:05:38 +00:00
naps62-yolo 3b6dd50c1d fix(maestro): maestro-web is now a proxy, not a local registry (NAP-231)
ci / rust (push) Successful in 4m36s
Earlier today I picked option 1 from NAP-231's open question (run
`maestro-registry` locally on a different port). That was wrong: the
user-facing framing is "local web UI", and option 2 — a thin front-end
that serves the web bundle and reverse-proxies `/api/*` to whatever
registry the daemon is already registered with — is what actually
delivers that.

What the new crate does:

- `crates/maestro-web` ships a single binary `maestro-web`.
- It embeds the web bundle the same way `maestro-registry` does
  (rust-embed against `web/dist/`).
- Mounts the bundle at `/` and a reverse proxy at `/api/*` (catch-all).
- HTTP proxy via `reqwest` — forwards method, headers (minus
  hop-by-hop), body, and passes upstream response headers (including
  `Set-Cookie`) back to the browser.
- WS proxy upgrades both sides and shuttles frames with
  `tokio::select!`. Cookies on the initial `WebSocket` upgrade headers
  are forwarded so the registry sees the same browser session.
- Reads `MAESTRO_REGISTRY` (upstream URL) and `MAESTRO_WEB_BIND_ADDR`
  (default `127.0.0.1:8888`) from env.
- An integration test (`tests/proxy.rs`) stands up a mock upstream,
  proxies an HTTP request with cookies through, and round-trips a text
  WebSocket frame to verify the WS path.

Installer changes:

- Wizard now `cargo install`s `maestro-web` (not `maestro-registry`)
  when the user opts into the web service.
- Unit template switched: `ExecStart=%h/.cargo/bin/maestro-web` plus
  `Environment=MAESTRO_REGISTRY=<upstream>` +
  `Environment=MAESTRO_WEB_BIND_ADDR=<bind>`.
- New `--web-registry <URL>` flag. Default in interactive mode: read
  `registry_url` straight out of `~/.config/maestrod/config.toml`, so
  the proxy lines up with what the daemon is using without further
  prompting. Non-interactive runs without either of those error out
  rather than guess.

Docs:

- `docs/INSTALL.md` updated unit body + flag table.
- `CLAUDE.md` repo-layout block now lists `maestro-web`.

148 tests pass workspace-wide (9 new in maestro-web, including the
proxy integration test; 15 in installer).
2026-05-26 08:59:27 +00:00