`maestro-cli` carried a hand-rolled `urlencoding()` for the attach-token query
param while `maestro-daemon` already used the `urlencoding` crate for the same
wire field. Two encoders for one field is exactly the drift that only surfaces
in production — collapse to the crate (`urlencoding = "2"`), deleting the local
copy. The unreserved set is identical, so the encoded output is unchanged
(existing `attach_url` tests pass).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New `crates/maestro/` umbrella binary hosts the CLI, daemon, and registry
behind Cargo features `cli`, `daemon`, `registry` (default = all three).
Library crates keep their structure; per-binary `main.rs` files are removed
and their dispatch logic is exposed as public functions:
- `maestro_cli::dispatch(command, config, registry_url)`
- `maestro_daemon::cli::dispatch(config, cmd)` (with public `DaemonCmd`/`ConfigureArgs`)
- `maestro_registry::run()`
Subcommand surface:
- `maestro list / spawn / attach / kill / set-registry / policy` (unchanged)
- `maestro daemon [run|configure]` (was `maestrod`)
- `maestro registry` (was `maestro-registry`)
Hard cutover — old binary names are not preserved.
Verified: default-feature build, plus `--no-default-features --features {cli|daemon|registry}`
each build clean with `-D warnings`.
CLI now reads `[shared].registry_url` and `[cli].cookie` from the unified
file via maestro-config instead of `~/.config/maestro/cli.toml`. The
two persistence paths are now distinct:
- `maestro set-registry <url>` → writes `[shared].registry_url`
- captured Set-Cookie on first contact → writes `[cli].cookie`
Both go through read-modify-write so they preserve any `[daemon]` /
`[registry]` content already in the file.
Env-var renames on the CLI flag layer:
- `--registry-url` reads `MAESTRO_SHARED_REGISTRY_URL` (was `MAESTRO_REGISTRY_URL`)
- `--config` reads `MAESTRO_CONFIG` (was `MAESTRO_CLI_CONFIG`)
Default config path is now `~/.config/maestro/config.toml`.
Closes NAP-216. Thin client for the registry HTTP API + per-session
daemon WebSocket. Becomes the headless test harness for NAP-217 and
unblocks the demo-by-script path before the web client lands (NAP-218).
Crate layout (`crates/maestro-cli/`):
- `cli.rs` — clap-derive parser. Commands: `list`, `spawn`, `attach`,
`kill`, `set-registry`. Global `--registry-url` (env
`MAESTRO_REGISTRY_URL`), `--config`, repeatable `-v`.
- `config.rs` — `CliConfig` persisted at `~/.config/maestro/cli.toml`,
storing the default registry URL + the session cookie issued by the
registry's cookie middleware.
- `http.rs` — `RegistryClient` wrapping `reqwest`. Attaches the
persisted cookie, captures `Set-Cookie` responses (first-contact
bootstrap and rotation), atomically writes the new value back to
the config file.
- `sessions.rs` — `list/spawn/kill` + connect-info fetch. `list` prints
a per-daemon grouped table; `--json` for scripting.
- `attach.rs` — raw-TTY WS proxy. Termios cfmakeraw with an RAII guard
for restore-on-drop, SIGWINCH → `ClientControl::Resize` text frame,
configurable detach key (default Ctrl-q; accepts `^X` form), echoes
`Ping`/`Pong` for keepalive symmetry with the daemon side.
Workspace deps added: clap (derive+env), reqwest (rustls+cookies),
nix (term+signal).
Smoke-tested against the live registry at maestro-registry.n62.casa:
`maestro list` issued + persisted the cookie and printed the empty
catalog cleanly. 19 new unit tests (parser, config round-trip,
table-printing, cookie extraction, attach URL/detach-key parsing).
Full workspace `cargo test`: 97 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>