feat: mirrorstack module init (closes #9)#11
Merged
Conversation
Adds a new top-level `app module init` subcommand that registers a
developer-owned module on the platform via POST /v1/modules.
Interactive flow:
$ mirrorstack app module init
Module name: Analytics
Slug [analytics]:
Module: Analytics
Slug: @owen/analytics
Create this module? [Y/n] y
✓ created @owen/analytics
id: 7c4b...
Non-interactive flow (for CI / automation):
mirrorstack app module init --name Analytics --yes [--slug analytics] [--used]
Pre-flight refuses to POST when the user hasn't claimed a username
(modules are namespaced `@<username>/<slug>`), pointing the user at
account.mirrorstack.ai/me. Slug format check mirrors the platform regex
(3-40, must start with a letter, end with letter/digit) — manual char
walk to avoid pulling in `regex` for one pattern.
`--used` makes 409 slug_taken behave as success — re-running init in CI
no longer needs conditional logic.
Filesystem scaffolding (templates, SDK pull) is intentionally out of
scope per the current product direction; modules are developed locally
with whatever tooling the developer prefers, and `mirrorstack dev`
(future) opens a WSS tunnel into the platform.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `app` wrapper had no other subcommands beyond `module`, so it was pure nesting noise. `mirrorstack module init` reads cleaner and matches how the web surface refers to modules (no parent "app" namespace). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three issues from local dev testing: 1. **404 on POST /v1/modules**: the CLI was talking to the account service (8081), but modules live on the applications service (8082). Split out `MIRRORSTACK_APPS_API_URL` (default `https://apps-api.mirrorstack.ai`, local `http://localhost:8082`) and route `create_module` / `get_module` through it. The `me` lookup still goes to the account service. 2. **No availability check before POST**: easy to forget you already created `media` last week and end up with a confusing 409. New `GET /v1/modules/{slug}` pre-flight catches the common already-owned-by-caller case with a clear error before we POST. Reserved/format checks still happen server-side (no equivalent endpoint, and not worth adding for one CLI consumer). 3. **Plain-text prompts**: replaced ad-hoc `prompt`/`confirm` helpers with `dialoguer` (themed Input + Confirm), `console` (styled output: bold cyan slugs, dim labels, green ✓), and `indicatif` (steady-tick spinner during network calls). `--yes` mode unchanged — no prompts, no spinner-suppression needed since indicatif already detects non-tty stderr. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- TTY check on `with_spinner`: short-circuit before spinner setup when
stderr isn't a terminal. The doc comment promised this but the code
wasn't actually implementing it; CI logs would have stuttered with
the spinner's terminal escapes.
- `print_already_exists` takes `Option<&str>` for id instead of the
`"(unknown id)"` sentinel string.
- `session_expired()` helper dedupes the same `anyhow!` message at
three call sites in `init`.
- Inline the `slug_taken` hint in the pre-flight error path —
re-looking it up via `slug_error_hint("slug_taken")` was awkward
when the literal string was already known at the call site.
- `resolve_base(env, default)` helper in `commands/mod.rs` replaces
the six `std::env::var(ENV_X).unwrap_or_else(|_| DEFAULT_X.into())`
repetitions across `module.rs`, `whoami.rs`, and `login.rs`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 4, 2026
Both account and applications services are exposed under the same `api.mirrorstack.ai` hostname in prod (path-routed at the ingress); the separate `apps-api.mirrorstack.ai` was a guess that didn't match how the platform is actually deployed. Local dev keeps the 8082 split via `.env`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New top-level
module initsubcommand that registers a developer-owned module on the platform viaPOST /v1/modules.Behaviour notes
@<username>/<slug>, so we point them ataccount.mirrorstack.ai/mefirst.MODULE_SLUG_PATTERNin web-applications. Inlined as a manual char walk — avoids pulling inregexfor one pattern.--usedmakes 409slug_takenbehave as success, so re-running init in CI doesn't need conditional logic.mirrorstack devWSS tunnel.Naming
Originally landed as
mirrorstack app module *; flattened tomirrorstack module *since theappwrapper had no other children — pure nesting noise. Matches how the web surface refers to modules (no parent "app" namespace).Test plan
cargo fmt --checkcargo clippy --all-targets -- -D warningscargo test— 20/20 (8 new: 2 mockito forcreate_module, 6 for slug helpers)cargo build --release./target/release/mirrorstack module init --helprenders correctlyDepends on
Closes #9