Skip to content

feat: mirrorstack module init (closes #9)#11

Merged
I-am-nothing merged 5 commits into
mainfrom
feat/9-app-module-init
May 4, 2026
Merged

feat: mirrorstack module init (closes #9)#11
I-am-nothing merged 5 commits into
mainfrom
feat/9-app-module-init

Conversation

@I-am-nothing

@I-am-nothing I-am-nothing commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

New top-level module init subcommand that registers a developer-owned module on the platform via POST /v1/modules.

$ mirrorstack module init --help
Register a new module on the platform. Interactive by default; pass --yes for non-interactive use.

Options:
  --name <NAME>   Module name (human-readable)
  --slug <SLUG>   URL slug (derived from --name when omitted)
  -y, --yes       Skip prompts; requires --name
  --used          Treat 409 slug_taken as success (idempotent re-runs)

Behaviour notes

  • Pre-flight refuses to POST when the user hasn't claimed a username — modules are namespaced @<username>/<slug>, so we point them at account.mirrorstack.ai/me first.
  • Slug regex (3-40, must start with a letter, end with letter/digit, lowercase + hyphen only) mirrors the platform service and MODULE_SLUG_PATTERN in web-applications. Inlined as a manual char walk — avoids pulling in regex for one pattern.
  • --used makes 409 slug_taken behave as success, so re-running init in CI doesn't need conditional logic.
  • Out of scope (per product direction): filesystem scaffolding, SDK pull, mirrorstack dev WSS tunnel.

Naming

Originally landed as mirrorstack app module *; flattened to mirrorstack module * since the app wrapper had no other children — pure nesting noise. Matches how the web surface refers to modules (no parent "app" namespace).

Test plan

  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings
  • cargo test — 20/20 (8 new: 2 mockito for create_module, 6 for slug helpers)
  • cargo build --release
  • ./target/release/mirrorstack module init --help renders correctly

Depends on

  • api-platform#111 (POST /v1/modules) — merged

Closes #9

I-am-nothing and others added 2 commits May 4, 2026 08:08
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>
@I-am-nothing I-am-nothing changed the title feat: mirrorstack app module init (closes #9) feat: mirrorstack module init (closes #9) May 4, 2026
I-am-nothing and others added 2 commits May 4, 2026 13:47
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>
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>
@I-am-nothing I-am-nothing merged commit 27e10ee into main May 4, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: port app module init scaffold to Rust

1 participant