diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 3b98b209..1a3704e5 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -96,7 +96,7 @@ jobs: node-version: ${{ env.NODE_VERSION }} cache: "npm" # Need uv too — pages.ts imports ./modules.generated, which is - # produced by `sm gen-pages` from the installed Python modules. + # produced by `smpy gen-pages` from the installed Python modules. - uses: astral-sh/setup-uv@v8.0.0 with: enable-cache: true diff --git a/CHANGELOG.md b/CHANGELOG.md index f045b529..6b7e8185 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ Initial public release. All 12 Python packages publish to PyPI and all 3 JS pack ### Added -- `sm new ` CLI generator (shipped via the `simple_module_cli` PyPI distribution) scaffolding a working app with `users + dashboard + permissions` pre-wired. +- `smpy new ` CLI generator (shipped via the `simple_module_cli` PyPI distribution) scaffolding a working app with `users + dashboard + permissions` pre-wired. - PyPI Trusted Publishing workflow (`.github/workflows/release.yml`) for zero-secret releases. - npm Trusted Publishing for all three JS packages. diff --git a/Makefile b/Makefile index 14b8b1c0..cc298e14 100644 --- a/Makefile +++ b/Makefile @@ -25,12 +25,12 @@ dev-ui: # Regenerate host/client_app/modules.{manifest.json,generated.ts,generated.css} from installed modules. gen-pages: - uv run --project host sm host gen-pages --host-dir=host/client_app + uv run --project host smpy host gen-pages --host-dir=host/client_app # Install JS deps declared by installed modules into host/client_app/node_modules. # Wheel-installed modules need this; in-repo workspace modules do not. sync-module-deps: - uv run --project host sm host sync-js-deps --host-client-app=host/client_app + uv run --project host smpy host sync-js-deps --host-client-app=host/client_app # Build build: diff --git a/README.md b/README.md index 7a6dab28..c6cedb18 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ A modular-monolith framework for Python. Each feature lives in its own self-cont If you want to **build an app on simple_module**, not hack on the framework itself: ```bash -uvx --from simple_module_cli sm new my-app +uvx --from simple_module_cli smpy new my-app cd my-app make dev ``` @@ -45,7 +45,7 @@ make migrate make dev ``` -Hit `http://localhost:8000` — you land on the public page. `/users/login` is the email+password login, `/dashboard/` is the authenticated home, and `/dashboard/doctor` is the admin-only "sm doctor" panel (static checks, migrations, dev server, modules). +Hit `http://localhost:8000` — you land on the public page. `/users/login` is the email+password login, `/dashboard/` is the authenticated home, and `/dashboard/doctor` is the admin-only "smpy doctor" panel (static checks, migrations, dev server, modules). ## Create a new module @@ -121,7 +121,7 @@ Power users can still override the following bootstrap knobs via env if needed: All module-level settings — users, SMTP, Celery broker, file storage backend, etc. — live in the admin UI. After upgrading an existing deployment, run once: ```bash -uv run sm settings import-from-env +uv run smpy settings import-from-env ``` to seed DB overrides from the current `SM_*` environment. @@ -163,7 +163,7 @@ The 300-line file cap (enforced by CI) usually pushes you to factor row-level co Either use the CLI: ```bash -uv run sm users create-admin --email admin@example.com --password changeme +uv run smpy users create-admin --email admin@example.com --password changeme ``` Or let the app bootstrap it automatically on first boot by setting env vars **before** running `make migrate && make dev`: diff --git a/docs/database/migrations.md b/docs/database/migrations.md index 53ec60b2..8a56fb7c 100644 --- a/docs/database/migrations.md +++ b/docs/database/migrations.md @@ -46,7 +46,7 @@ uv run alembic downgrade orders@base # back to the state before the orders m ## First migration of a new module -When you scaffold a module with `sm create-module`, the *first* autogenerate revision produces a file that needs this marker added by hand: +When you scaffold a module with `smpy create-module`, the *first* autogenerate revision produces a file that needs this marker added by hand: ```python # migrations/versions/XXXX_add_orders_tables.py @@ -108,7 +108,7 @@ If `invoices` has an FK to `orders.order.id`: - Alembic will emit `ADD CONSTRAINT` in the invoices table's migration. - The migration that creates `invoices_invoice` must come **after** the one that creates `orders_order` in linear history. -- `sm create-module` + `uv run alembic revision --autogenerate` handle this naturally as long as `depends_on` is correct in `ModuleMeta`. +- `smpy create-module` + `uv run alembic revision --autogenerate` handle this naturally as long as `depends_on` is correct in `ModuleMeta`. On Postgres, cross-schema FKs work natively (`orders.order.id ← invoices.invoice.order_id`). diff --git a/docs/e2e-testing.md b/docs/e2e-testing.md index 3195840d..2c13d4c4 100644 --- a/docs/e2e-testing.md +++ b/docs/e2e-testing.md @@ -29,7 +29,7 @@ make dev # FastAPI on :8000 + Vite on :5050 Create the first admin user (needed for e2e auth): ```bash -uv run sm-users create-admin --email admin@example.com --password admin +uv run smpy users create-admin --email admin@example.com --password admin ``` Or set `SM_USERS_BOOTSTRAP_EMAIL` / `SM_USERS_BOOTSTRAP_PASSWORD` in `.env` @@ -48,7 +48,7 @@ When you write e2e tests, read these environment variables (all optional): | Variable | Default | Notes | | -------------- | ------------------------- | ------------------------------------------------------------ | | `E2E_BASE_URL` | `http://localhost:8000` | Where the FastAPI host is listening. | -| `E2E_USERNAME` | `admin@example.com` | Email of the admin user created via `sm-users create-admin`. | +| `E2E_USERNAME` | `admin@example.com` | Email of the admin user created via `smpy users create-admin`. | | `E2E_PASSWORD` | `admin` | Password of the above admin user. | | `SM_USERS_VERIFICATION_TOKEN_SECRET` | `dev-verify-token-secret-change-me` | Must match the running server's value so locally-minted invite tokens are accepted. | diff --git a/docs/framework-conventions.md b/docs/framework-conventions.md index 662e4f7b..fbdbe3f7 100644 --- a/docs/framework-conventions.md +++ b/docs/framework-conventions.md @@ -20,7 +20,7 @@ modules// └── pages/ # *.tsx — auto-discovered by Vite ``` -Scaffold a fresh module with `sm create-module --dest modules/` — it generates all of the above. Then run `uv add ./modules/` to register it on your app. +Scaffold a fresh module with `smpy create-module --dest modules/` — it generates all of the above. Then run `uv add ./modules/` to register it on your app. ## ModuleMeta @@ -296,7 +296,7 @@ class OrdersModule(ModuleBase): return {"orders": Path(str(importlib.resources.files(__package__) / "locales"))} ``` -`sm create-module` scaffolds this method and a matching `locales/en.json` automatically. +`smpy create-module` scaffolds this method and a matching `locales/en.json` automatically. ### Key naming diff --git a/docs/framework/i18n.md b/docs/framework/i18n.md index aa994868..b0bf6818 100644 --- a/docs/framework/i18n.md +++ b/docs/framework/i18n.md @@ -26,7 +26,7 @@ class OrdersModule(ModuleBase): } ``` -The key (`"orders"`) is the **namespace** — it prefixes every key in the files. `sm create-module` scaffolds this method and a starter `en.json` automatically. +The key (`"orders"`) is the **namespace** — it prefixes every key in the files. `smpy create-module` scaffolds this method and a starter `en.json` automatically. ## Key naming diff --git a/docs/framework/settings.md b/docs/framework/settings.md index fcb63331..1d4fdd5a 100644 --- a/docs/framework/settings.md +++ b/docs/framework/settings.md @@ -133,7 +133,7 @@ Values are keyed by `.`. Conventional namespace is the module na Existing deployments that used env vars for module settings should run once: ```bash -uv run sm-settings import-from-env +uv run smpy settings import-from-env ``` This reads the current environment, looks up keys the settings service knows about, and writes overrides into the DB. Idempotent — skips keys that already have a DB override. After running, you can remove those env vars from deployment config. diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 1a646518..fb7c9025 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -75,7 +75,7 @@ Prefix `SM_BG_TASKS_*`. The defaults in `docker-compose.yml` already set these s After upgrading from an older deployment, import existing `SM_*` values into the DB settings store once: ```bash -uv run sm-settings import-from-env +uv run smpy settings import-from-env ``` This is idempotent — it only seeds keys that don't have a DB override yet. diff --git a/docs/guide/first-module.md b/docs/guide/first-module.md index 4fa675f5..61c02216 100644 --- a/docs/guide/first-module.md +++ b/docs/guide/first-module.md @@ -1,13 +1,13 @@ # Your first module -A stage-by-stage walk-through: from `sm create-module` to a working Orders module with custom fields, validation, a menu entry, and a test. +A stage-by-stage walk-through: from `smpy create-module` to a working Orders module with custom fields, validation, a menu entry, and a test. -Assumes you've completed the [Quickstart](/guide/quickstart) and have an app on disk created by `sm new`. +Assumes you've completed the [Quickstart](/guide/quickstart) and have an app on disk created by `smpy new`. ## 1. Scaffold ```bash -sm create-module orders --dest modules/orders +smpy create-module orders --dest modules/orders uv add ./modules/orders ``` diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 3275822c..afc6b6ee 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -1,6 +1,6 @@ # Installation -You install simple_module_python by installing its **CLI** — `sm` — and using it to scaffold a new app. There's no repo to clone; the framework ships as a set of Python packages on PyPI and the CLI assembles them into a working project for you. +You install simple_module_python by installing its **CLI** — `smpy` — and using it to scaffold a new app. There's no repo to clone; the framework ships as a set of Python packages on PyPI and the CLI assembles them into a working project for you. ## Prerequisites @@ -26,28 +26,28 @@ If you prefer pipx: pipx install simple_module_cli ``` -That puts `sm` on your PATH globally. Confirm with: +That puts `smpy` on your PATH globally. Confirm with: ```bash -sm --help +smpy --help ``` ## Scaffold a new app ```bash -sm new myapp +smpy new myapp ``` Interactive — you pick the database (SQLite / Postgres), whether to enable multi-tenancy, and which bundled modules to include. Skip the prompts and accept the defaults with: ```bash -sm new myapp --yes +smpy new myapp --yes ``` Or pick a preset and add modules non-interactively: ```bash -sm new myapp --preset standard --with background_tasks,file_storage +smpy new myapp --preset standard --with background_tasks,file_storage ``` | Preset | Modules | @@ -56,7 +56,7 @@ sm new myapp --preset standard --with background_tasks,file_storage | `standard` (default) | `minimal` + `dashboard`, `settings`, `feature_flags` | | `full` | `standard` + `background_tasks`, `file_storage` | -After scaffolding, `sm new` runs `uv sync`, `npm install`, and `alembic upgrade head` for you (skip with `--no-install` if you'd rather drive that yourself). +After scaffolding, `smpy new` runs `uv sync`, `npm install`, and `alembic upgrade head` for you (skip with `--no-install` if you'd rather drive that yourself). ## Boot it @@ -74,7 +74,7 @@ Hit `http://localhost:8000`. You should see the landing page. ## Database choice -`sm new` writes a `.env.example`. The default is SQLite (zero setup): +`smpy new` writes a `.env.example`. The default is SQLite (zero setup): ```bash SM_DATABASE_URL=sqlite+aiosqlite:///./app.db @@ -100,7 +100,7 @@ See [Configuration](/guide/configuration) for the full list of env vars. If you included the `users` module: ```bash -uv run sm-users create-admin --email admin@example.com --password changeme +uv run smpy users create-admin --email admin@example.com --password changeme ``` Or set bootstrap env vars so the admin is auto-created on first boot: @@ -116,7 +116,7 @@ Then `make migrate && make dev`. ## Add a module to your app ```bash -sm create-module orders --dest modules/orders +smpy create-module orders --dest modules/orders ``` That generates `modules/orders/` with the full layout (model, contracts, service, endpoints, pages, tests, locales, `pyproject.toml` entry point). Add the package to your app's dependencies and re-sync: @@ -133,7 +133,7 @@ The full walkthrough is in [Your first module](/guide/first-module). When new releases of `simple_module_*` ship to PyPI, bump every dep in lockstep: ```bash -sm package-update +smpy package-update ``` Pass `--dry-run` first to preview the diff. @@ -155,6 +155,6 @@ Re-run `uv sync` — entry points are registered at install time, not at import ## Next steps - [Quickstart](/guide/quickstart) — bootstrap and tour the running app in five minutes. -- [Project structure](/guide/project-structure) — what `sm new` lays down. +- [Project structure](/guide/project-structure) — what `smpy new` lays down. - [Your first module](/guide/first-module) — extend the app with your own domain logic. - [Bundled modules](/modules/) — what each pre-installed module ships. diff --git a/docs/guide/project-structure.md b/docs/guide/project-structure.md index d53b9220..79878c1f 100644 --- a/docs/guide/project-structure.md +++ b/docs/guide/project-structure.md @@ -1,6 +1,6 @@ # Project structure -`sm new myapp` lays down a focused tree. The host owns the runnable app; modules live as packages under `modules/`; everything else is configuration. +`smpy new myapp` lays down a focused tree. The host owns the runnable app; modules live as packages under `modules/`; everything else is configuration. ```text myapp/ @@ -25,19 +25,19 @@ myapp/ │ ├── env.py # calls build_module_metadata() to union every module's MetaData │ └── versions/ │ -├── modules/ # your custom modules go here (sm create-module --dest modules/) -│ └── orders/ # example — generated by sm create-module orders +├── modules/ # your custom modules go here (smpy create-module --dest modules/) +│ └── orders/ # example — generated by smpy create-module orders │ └── … (see "Anatomy of a module" below) │ └── tests/ # your app-level tests └── test_smoke.py ``` -`sm new --flat` skips the `modules/` directory entirely, for the case where the app only consumes published modules and never authors its own. `sm new --preset minimal` ships fewer pre-installed modules. +`smpy new --flat` skips the `modules/` directory entirely, for the case where the app only consumes published modules and never authors its own. `smpy new --preset minimal` ships fewer pre-installed modules. ## Bundled modules -The framework's first-party modules live on PyPI as `simple_module_` and are added to `pyproject.toml` by `sm new` based on the preset / `--with` you pick: +The framework's first-party modules live on PyPI as `simple_module_` and are added to `pyproject.toml` by `smpy new` based on the preset / `--with` you pick: | Module | What it provides | |---|---| @@ -93,7 +93,7 @@ See the [module authoring guide](/module-authoring) for the full contract. | `client_app/modules.manifest.json` | `make gen-pages` | Same | | `client_app/modules.generated.css` | `make gen-pages` | Same | | `migrations/versions/XXXX_*.py` | `uv run alembic revision --autogenerate -m "…"` | When you add/change SQLModel tables | -| `modules//**` | `sm create-module --dest modules/` | Scaffolding a new module | +| `modules//**` | `smpy create-module --dest modules/` | Scaffolding a new module | Never hand-edit the `.generated.*` files — they are overwritten on the next `make gen-pages` run. diff --git a/docs/guide/quickstart.md b/docs/guide/quickstart.md index 8d512d56..f34321a1 100644 --- a/docs/guide/quickstart.md +++ b/docs/guide/quickstart.md @@ -1,6 +1,6 @@ # Quickstart -Five minutes from `sm new` to a running app with a freshly scaffolded module. +Five minutes from `smpy new` to a running app with a freshly scaffolded module. ## 1. Install the CLI @@ -8,18 +8,18 @@ Five minutes from `sm new` to a running app with a freshly scaffolded module. uv tool install simple_module_cli ``` -(Or `pipx install simple_module_cli`.) That puts `sm` on your PATH globally. +(Or `pipx install simple_module_cli`.) That puts `smpy` on your PATH globally. ## 2. Scaffold an app ```bash -sm new myapp --yes +smpy new myapp --yes cd myapp ``` `--yes` accepts the defaults (SQLite, no multi-tenancy, the `standard` preset: `auth`, `users`, `permissions`, `dashboard`, `settings`, `feature_flags`). The CLI runs `uv sync`, `npm install`, and `alembic upgrade head` for you. -For an interactive run with prompts, drop the `--yes`. For a preset + extras: `sm new myapp --preset standard --with background_tasks,file_storage --yes`. +For an interactive run with prompts, drop the `--yes`. For a preset + extras: `smpy new myapp --preset standard --with background_tasks,file_storage --yes`. ## 3. Boot it @@ -39,7 +39,7 @@ The API and Vite dev servers start side by side. Visit: In another terminal, from inside `myapp`: ```bash -uv run sm-users create-admin --email admin@example.com --password changeme +uv run smpy users create-admin --email admin@example.com --password changeme ``` Sign in at `/users/login` and you land on the dashboard. @@ -47,7 +47,7 @@ Sign in at `/users/login` and you land on the dashboard. ## 5. Scaffold a new module ```bash -sm create-module orders --dest modules/orders +smpy create-module orders --dest modules/orders uv add ./modules/orders ``` diff --git a/docs/index.md b/docs/index.md index e6ed8ed5..54cfcd51 100644 --- a/docs/index.md +++ b/docs/index.md @@ -63,7 +63,7 @@ features: ```bash uv tool install simple_module_cli -sm new myapp --yes +smpy new myapp --yes cd myapp make dev # API on :8000, Vite on :5050 ``` @@ -71,7 +71,7 @@ make dev # API on :8000, Vite on :5050 Then in another terminal, inside `myapp`: ```bash -sm create-module orders --dest modules/orders +smpy create-module orders --dest modules/orders uv add ./modules/orders ``` diff --git a/docs/module-authoring.md b/docs/module-authoring.md index 873b9680..ec771c33 100644 --- a/docs/module-authoring.md +++ b/docs/module-authoring.md @@ -1,7 +1,7 @@ # Module authoring guide This is the reference for authoring a module that is installable from PyPI -and assembled into a host by the `sm create-host` scaffold. It describes the +and assembled into a host by the `smpy create-host` scaffold. It describes the contract a module must follow, the env-var conventions, the migration workflow the host developer uses, and the API-version / semver rules. @@ -256,7 +256,7 @@ templates by copying + editing. ## Testing during development -Install `simple_module_test` as a dev dependency (the `sm create-module` +Install `simple_module_test` as a dev dependency (the `smpy create-module` scaffold does this automatically): ```toml diff --git a/docs/modules/dashboard.md b/docs/modules/dashboard.md index bf2d7923..4467da05 100644 --- a/docs/modules/dashboard.md +++ b/docs/modules/dashboard.md @@ -69,4 +69,4 @@ Top-level keys in `dashboard/locales/en.json`: ## Replacing it -If you want a different post-login landing page, set `users.login_redirect_url` in the [admin settings UI](/modules/settings) (or via `sm-settings import-from-env` from `SM_USERS_LOGIN_REDIRECT_URL`) to your route. You can keep the dashboard module installed for the menu entry, or set `SM_MODULES_ENABLED` without `dashboard` to drop it entirely. The `users` module auto-detects whether `dashboard` is installed and falls back to `/` if not. +If you want a different post-login landing page, set `users.login_redirect_url` in the [admin settings UI](/modules/settings) (or via `smpy settings import-from-env` from `SM_USERS_LOGIN_REDIRECT_URL`) to your route. You can keep the dashboard module installed for the menu entry, or set `SM_MODULES_ENABLED` without `dashboard` to drop it entirely. The `users` module auto-detects whether `dashboard` is installed and falls back to `/` if not. diff --git a/docs/modules/index.md b/docs/modules/index.md index f85b7e03..d2c3e2c5 100644 --- a/docs/modules/index.md +++ b/docs/modules/index.md @@ -5,9 +5,9 @@ simple_module_python ships with eight first-party modules. Each is a regular Pyt | Module | Depends on | What it provides | |---|---|---| | [`auth`](/modules/auth) | — | Tiny public surface (`UserContext`, `get_current_user`, `require_permission`) other modules import. The actual login/session logic lives in `users`. | -| [`users`](/modules/users) | `auth` | Email + password auth, sessions, roles, invites, password reset, email verification, admin UI, mailer backends, `sm-users create-admin`. | +| [`users`](/modules/users) | `auth` | Email + password auth, sessions, roles, invites, password reset, email verification, admin UI, mailer backends, `smpy users create-admin`. | | [`permissions`](/modules/permissions) | `auth`, `users` | Role / direct-grant assignment store + admin UI; `RequiresPermission` dependency that honours both forms. | -| [`settings`](/modules/settings) | — | DB-backed key/value store with system / tenant / user precedence; per-module pydantic settings registration; hot reload; `sm-settings` CLI. | +| [`settings`](/modules/settings) | — | DB-backed key/value store with system / tenant / user precedence; per-module pydantic settings registration; hot reload; `smpy settings` CLI. | | [`feature_flags`](/modules/feature_flags) | — | Runtime feature toggles with system + tenant overrides. | | [`file_storage`](/modules/file_storage) | `settings` | Pluggable file storage (filesystem, S3-compatible) with upload validation, presigned URLs, browse/download/delete UI. | | [`background_tasks`](/modules/background_tasks) | `users` | Celery + Redis workers, persistent task history, retry, stuck-task sweep, live worker dashboard. | diff --git a/docs/modules/settings.md b/docs/modules/settings.md index a8a08f4a..c6297aaa 100644 --- a/docs/modules/settings.md +++ b/docs/modules/settings.md @@ -23,7 +23,7 @@ Two distinct surfaces: The pattern (pydantic `BaseSettings` subclass + `register_module_settings` in `register_settings`) is documented in [Per-module settings convention](/guide/configuration#per-module-settings-convention). What the settings module adds on top: - **DB hydration**: `app.state..settings` is replaced with a fresh instance built from DB overrides + pydantic defaults during the host's hydrate phase, before `on_startup` runs. -- **Env-var migration**: `sm-settings import-from-env` scans every registered class's `env_prefix` and seeds matching `SM_*` env vars as SYSTEM-scoped rows. Idempotent. +- **Env-var migration**: `smpy settings import-from-env` scans every registered class's `env_prefix` and seeds matching `SM_*` env vars as SYSTEM-scoped rows. Idempotent. - **Admin editing**: registered fields appear at `/settings/modules/` with type-aware inputs. - **Hot reload**: saving via the admin UI calls `apply_changes_and_reload`, which validates the diff against the pydantic class, persists deltas, swaps the live `app.state..settings`, and publishes [`SettingsReloaded`](#events) so dependents (SMTP clients, Celery configs, …) can rebuild. @@ -169,7 +169,7 @@ Unique constraint on `(scope, scope_id, key)`. ## CLI -- `sm-settings import-from-env` — one-shot migration. Walks every module that has registered settings; for each `SM__` env var present, writes a SYSTEM-scope row so the value persists without needing the env var any longer. Idempotent — only seeds keys that don't already have a DB override. +- `smpy settings import-from-env` — one-shot migration. Walks every module that has registered settings; for each `SM__` env var present, writes a SYSTEM-scope row so the value persists without needing the env var any longer. Idempotent — only seeds keys that don't already have a DB override. ## Inertia pages diff --git a/docs/modules/users.md b/docs/modules/users.md index 10047c40..d538aeef 100644 --- a/docs/modules/users.md +++ b/docs/modules/users.md @@ -183,10 +183,10 @@ Everything else is DB-backed (initial values are pydantic defaults; edit at `/se ## CLI -- `sm-users create-admin --email --password

[--full-name ] [--force]` — creates (or, with `--force`, updates) an admin user. Idempotent: re-running with the same email is a no-op without `--force`. +- `smpy users create-admin --email --password

[--full-name ] [--force]` — creates (or, with `--force`, updates) an admin user. Idempotent: re-running with the same email is a no-op without `--force`. ```bash -uv run sm-users create-admin --email admin@example.com --password changeme +uv run smpy users create-admin --email admin@example.com --password changeme ``` Programmatically: @@ -202,7 +202,7 @@ result = await create_admin(db, email="admin@example.com", password="...") Two paths to seed the first admin: -1. **CLI** — `sm-users create-admin ...`. +1. **CLI** — `smpy users create-admin ...`. 2. **Env vars** — set `SM_USERS_BOOTSTRAP_EMAIL` + `SM_USERS_BOOTSTRAP_PASSWORD` before first `make dev`. `bootstrap_admin_from_env(app)` runs at startup and creates the admin if the `users_user` table is empty. Optionally seed a non-admin too via `SM_USERS_BOOTSTRAP_USER_EMAIL` + `SM_USERS_BOOTSTRAP_USER_PASSWORD`. ## Mailer backends diff --git a/docs/reference/deployment.md b/docs/reference/deployment.md index 210466a6..15b54844 100644 --- a/docs/reference/deployment.md +++ b/docs/reference/deployment.md @@ -18,7 +18,7 @@ Before serving traffic: ## Build -Typical Docker build for an app scaffolded by `sm new`: +Typical Docker build for an app scaffolded by `smpy new`: ```dockerfile FROM python:3.12-slim AS builder diff --git a/docs/reference/diagnostic-codes.md b/docs/reference/diagnostic-codes.md index 18328c1f..9317d5b6 100644 --- a/docs/reference/diagnostic-codes.md +++ b/docs/reference/diagnostic-codes.md @@ -25,7 +25,7 @@ The framework runs a set of static checks over installed modules at app boot. Er | `SM014` | WARNING | Non-default locale is missing keys present in the default (untranslated). | Translate the missing keys. | | `SM015` | WARNING | Non-default locale has keys not in the default (stale / orphan translation). | Remove the stale keys, or add them to the default file if they really belong. | | `SM016` | ERROR | Locale JSON is invalid or contains non-string leaves. | Fix the JSON; keys must flatten to strings only. | -| `SM017` | WARNING | Module ships `.tsx` pages but has no `package.json` / `tsconfig.json`. Vite can't resolve type imports. | Run `sm create-module` on a dummy name and copy the generated config, or scaffold by hand. | +| `SM017` | WARNING | Module ships `.tsx` pages but has no `package.json` / `tsconfig.json`. Vite can't resolve type imports. | Run `smpy create-module` on a dummy name and copy the generated config, or scaffold by hand. | | `SM018` | WARNING | An Inertia `router.post/patch/put/delete()` call in a page targets a JSON `/api/*` endpoint, which would return raw JSON and be rejected by Inertia. | Point the call at a view endpoint that returns `inertia.render(...)` or a redirect; or use plain `fetch()` if you really want a JSON response. | | `SM019` | WARNING | Module declares a non-empty `view_prefix` and overrides `register_routes` but registers neither menu items nor permissions — admins can't reach the pages from the sidebar or grant access from the role editor. | Add `register_menu_items` for a sidebar entry, or `register_permissions` to surface the module in the role editor (sub-pages of another module typically just register permissions). | diff --git a/docs/reference/make-commands.md b/docs/reference/make-commands.md index 1272a268..fec4f6a0 100644 --- a/docs/reference/make-commands.md +++ b/docs/reference/make-commands.md @@ -2,31 +2,31 @@ Two surfaces drive day-to-day work: -1. **The `sm` CLI** — installed globally with `uv tool install simple_module_cli`. Used to scaffold apps and modules and to bump dependency versions. +1. **The `smpy` CLI** — installed globally with `uv tool install simple_module_cli`. Used to scaffold apps and modules and to bump dependency versions. 2. **The Makefile in your scaffolded app** — a thin wrapper around `uv` and `npm` for the inner dev loop (`dev`, `migrate`, `build`, `gen-pages`). Past those, there's no hidden tooling — read the `Makefile` and pyproject.toml in your app directly when something surprises you. -## `sm` CLI +## `smpy` CLI ```bash -sm --help +smpy --help ``` ### Scaffolders | Command | What | |---|---| -| `sm new ` | Scaffold a new app at `./`. Interactive by default; pass `--yes` for defaults, `--preset minimal\|standard\|full`, `--with mod1,mod2` for extras, `--db sqlite\|postgres`, `--tenancy`, `--flat` (no `modules/` dir). | -| `sm create-module ` | Scaffold a publishable module package at `./simple_module_` (or `--dest `). | -| `sm create-host ` | Scaffold just a host project (no sample module). Useful when you want a minimal shell that consumes published modules. | +| `smpy new ` | Scaffold a new app at `./`. Interactive by default; pass `--yes` for defaults, `--preset minimal\|standard\|full`, `--with mod1,mod2` for extras, `--db sqlite\|postgres`, `--tenancy`, `--flat` (no `modules/` dir). | +| `smpy create-module ` | Scaffold a publishable module package at `./simple_module_` (or `--dest `). | +| `smpy create-host ` | Scaffold just a host project (no sample module). Useful when you want a minimal shell that consumes published modules. | ### Maintenance | Command | What | |---|---| -| `sm package-update` | Bump every `simple_module_*` dependency in `pyproject.toml` to the latest PyPI version. `--dry-run` previews the diff. | -| `sm skills add\|list\|update` | Install or update the bundled agent skills under `.claude/skills/` for use with Claude Code. | +| `smpy package-update` | Bump every `simple_module_*` dependency in `pyproject.toml` to the latest PyPI version. `--dry-run` previews the diff. | +| `smpy skills add\|list\|update` | Install or update the bundled agent skills under `.claude/skills/` for use with Claude Code. | ### Module-contributed plugins @@ -34,19 +34,19 @@ When a module declares a `simple_module_cli.cli_plugins` entry point, the CLI mo | Command | From | What | |---|---|---| -| `sm host gen-pages` | `simple_module_hosting` | Regenerate `client_app/modules.{manifest.json,generated.ts,generated.css}` from the installed modules. Run when you add or remove pages. | -| `sm host sync-js-deps` | `simple_module_hosting` | Install JS deps declared by wheel-installed modules into `client_app/node_modules`. In-repo workspace modules don't need this. | -| `sm settings import-from-env` | `simple_module_settings` | One-shot migration: read every `SM__*` env var and seed it as a SYSTEM-scope row in the DB-backed settings store. Idempotent. | +| `smpy host gen-pages` | `simple_module_hosting` | Regenerate `client_app/modules.{manifest.json,generated.ts,generated.css}` from the installed modules. Run when you add or remove pages. | +| `smpy host sync-js-deps` | `simple_module_hosting` | Install JS deps declared by wheel-installed modules into `client_app/node_modules`. In-repo workspace modules don't need this. | +| `smpy settings import-from-env` | `simple_module_settings` | One-shot migration: read every `SM__*` env var and seed it as a SYSTEM-scope row in the DB-backed settings store. Idempotent. | -`sm-users create-admin` is also installed via the `users` module, but as a separate top-level entry point (not under `sm`): +`smpy users create-admin` is contributed by the `users` module as a `smpy users` subcommand: ```bash -uv run sm-users create-admin --email admin@example.com --password changeme +uv run smpy users create-admin --email admin@example.com --password changeme ``` ## Scaffolded-app Makefile -`sm new` writes a slim Makefile to your app. The intent is *zero magic* — every target is one or two lines you could run by hand. +`smpy new` writes a slim Makefile to your app. The intent is *zero magic* — every target is one or two lines you could run by hand. | Command | What | |---|---| @@ -54,10 +54,10 @@ uv run sm-users create-admin --email admin@example.com --password changeme | `make dev` | `make gen-pages`, then `uvicorn main:app --reload` on `:8000` and `vite` on `:5050` in parallel. | | `make migrate` | `uv run alembic upgrade head`. Idempotent. | | `make build` | `cd client_app && npm run build`. Produces the production frontend bundle. | -| `make gen-pages` | Regenerate the page manifest. Auto-runs before `make dev`. Same effect as `sm host gen-pages`. | -| `make sync-js-deps` | Install JS deps from wheel-installed modules. Same effect as `sm host sync-js-deps`. | +| `make gen-pages` | Regenerate the page manifest. Auto-runs before `make dev`. Same effect as `smpy host gen-pages`. | +| `make sync-js-deps` | Install JS deps from wheel-installed modules. Same effect as `smpy host sync-js-deps`. | -If you included `background_tasks` in `sm new`, the scaffold appends `worker`, `beat`, and `worker-docker` targets that wrap the Celery commands — read your `Makefile` to see the exact invocations. +If you included `background_tasks` in `smpy new`, the scaffold appends `worker`, `beat`, and `worker-docker` targets that wrap the Celery commands — read your `Makefile` to see the exact invocations. ## Routine ops without a target diff --git a/docs/release-notes/2026-04-21-db-backed-settings.md b/docs/release-notes/2026-04-21-db-backed-settings.md index 2ead4b90..ad760d98 100644 --- a/docs/release-notes/2026-04-21-db-backed-settings.md +++ b/docs/release-notes/2026-04-21-db-backed-settings.md @@ -7,9 +7,9 @@ Every `SM__*` env var has moved to the admin UI at `/settings/modules`. After deploying this release: -1. Run `uv run sm-settings import-from-env` once to seed the DB with your current environment values. +1. Run `uv run smpy settings import-from-env` once to seed the DB with your current environment values. 2. Remove the `SM__*` entries from your `.env` / deployment config (they're no longer read). ## Breaking changes -Setting `SM_USERS_ALLOW_SIGNUP=true` (or any other `SM__*`) in the environment no longer has any effect. Use the admin UI or the `sm-settings` CLI. +Setting `SM_USERS_ALLOW_SIGNUP=true` (or any other `SM__*`) in the environment no longer has any effect. Use the admin UI or the `smpy settings` CLI. diff --git a/docs/superpowers/plans/2026-04-21-db-backed-module-settings.md b/docs/superpowers/plans/2026-04-21-db-backed-module-settings.md index 6b595e25..891500ac 100644 --- a/docs/superpowers/plans/2026-04-21-db-backed-module-settings.md +++ b/docs/superpowers/plans/2026-04-21-db-backed-module-settings.md @@ -2522,7 +2522,7 @@ git commit -m "test(e2e): verify settings-UI hot-reload for users.allow_signup" ## Phase 6: Import-from-env CLI -### Task 6.1: `sm-settings import-from-env` command +### Task 6.1: `smpy settings import-from-env` command **Files:** - Modify: `modules/settings/settings/cli.py` (create if missing; check `pyproject.toml` for existing entry point) @@ -2584,7 +2584,7 @@ Expected: FAIL — ImportError. # modules/settings/settings/cli.py """CLI entry points for the Settings module. -``sm-settings import-from-env`` — reads every ``SM__*`` from the +``smpy settings import-from-env`` — reads every ``SM__*`` from the current process environment and writes each matching field as an override to the ``Setting`` table. Idempotent. """ @@ -2660,7 +2660,7 @@ Add under `[project.scripts]`: ```toml [project.scripts] -sm-settings = "settings.cli:main" +smpy settings = "settings.cli:main" ``` (If `[project.scripts]` already exists, append the entry.) @@ -2678,7 +2678,7 @@ Expected: 2 passed. ```bash git add modules/settings/settings/cli.py modules/settings/pyproject.toml modules/settings/tests/test_cli_import.py -git commit -m "feat(settings): add sm-settings import-from-env CLI" +git commit -m "feat(settings): add smpy settings import-from-env CLI" ``` --- @@ -2732,7 +2732,7 @@ Inspect `docker-compose.yml:36-40` and `docker-compose.yml:61-65` and remove the Actually: the compose defaults must match the service hostnames (`redis`), which differ from local dev (`localhost`). Two options: - **Option A** — keep env override in compose. Reasonable compromise; compose is "deployment", not module config. -- **Option B** — change Celery defaults to `redis://redis:6379/*` and tell local-dev users to override via `sm-settings` CLI after first boot. +- **Option B** — change Celery defaults to `redis://redis:6379/*` and tell local-dev users to override via `smpy settings` CLI after first boot. Pick **A** — compose env stays. The spec's promise ("only DB URL in env") refers to the user's local `.env.example`, not CI/compose overrides which are container-specific plumbing. @@ -2772,7 +2772,7 @@ Power users can still override the following bootstrap knobs via env if needed: All module-level settings — users, SMTP, Celery broker, file storage backend, etc. — live in the admin UI. After upgrading an existing deployment, run once: ```bash -uv run sm-settings import-from-env +uv run smpy settings import-from-env ``` to seed DB overrides from the current `SM_*` environment. @@ -2828,12 +2828,12 @@ Every `SM__*` env var has moved to the admin UI at `/settings/modules`. After deploying this release: -1. Run `uv run sm-settings import-from-env` once to seed the DB with your current environment values. +1. Run `uv run smpy settings import-from-env` once to seed the DB with your current environment values. 2. Remove the `SM__*` entries from your `.env` / deployment config (they're no longer read). ## Breaking changes -Setting `SM_USERS_ALLOW_SIGNUP=true` (or any other `SM__*`) in the environment no longer has any effect. Use the admin UI or the `sm-settings` CLI. +Setting `SM_USERS_ALLOW_SIGNUP=true` (or any other `SM__*`) in the environment no longer has any effect. Use the admin UI or the `smpy settings` CLI. ``` - [ ] **Step 2: Commit** @@ -2871,4 +2871,4 @@ These were marked "open decisions to resolve during plan writing" in the spec. R 2. **Event bus sync/async**: `EventBus.publish` is async (uses `asyncio.gather`). `apply_changes_and_reload` `await`s the publish so handlers run before the API responds. -3. **`list_packages` source**: uses the in-memory registry (`ModuleSettingsRegistry.all_packages()`) as the source of truth during the app's lifetime. A separate `sm-settings prune-orphans` command (future follow-up, not in this plan) would scan the DB for `Setting` rows whose package is no longer registered. +3. **`list_packages` source**: uses the in-memory registry (`ModuleSettingsRegistry.all_packages()`) as the source of truth during the app's lifetime. A separate `smpy settings prune-orphans` command (future follow-up, not in this plan) would scan the DB for `Setting` rows whose package is no longer registered. diff --git a/docs/superpowers/plans/2026-04-21-public-release.md b/docs/superpowers/plans/2026-04-21-public-release.md index c4e46317..fbf6cd29 100644 --- a/docs/superpowers/plans/2026-04-21-public-release.md +++ b/docs/superpowers/plans/2026-04-21-public-release.md @@ -2,9 +2,9 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Publish all 14 Python packages to PyPI and 3 JS packages to npm as `v0.0.1`, with a working `sm new` / `simple-module new` CLI generator that scaffolds a new app consuming these packages. +**Goal:** Publish all 14 Python packages to PyPI and 3 JS packages to npm as `v0.0.1`, with a working `smpy new` / `simple-module new` CLI generator that scaffolds a new app consuming these packages. -**Architecture:** Seven phases in sequence. Phase 0 prepares the repo (LICENSE, CHANGELOG, lint scripts). Phase 1 renames 10 modules to the `simple-module-*` PyPI namespace. Phases 2–3 update Python + npm package metadata. Phase 4 writes substantive READMEs for all 17 packages. Phase 5 builds the version-bump script. Phase 6 extends the existing host-template CLI into a full `sm new` generator. Phase 7 adds the release workflow + docs. Every change must pass `make lint` and `make test`. +**Architecture:** Seven phases in sequence. Phase 0 prepares the repo (LICENSE, CHANGELOG, lint scripts). Phase 1 renames 10 modules to the `simple-module-*` PyPI namespace. Phases 2–3 update Python + npm package metadata. Phase 4 writes substantive READMEs for all 17 packages. Phase 5 builds the version-bump script. Phase 6 extends the existing host-template CLI into a full `smpy new` generator. Phase 7 adds the release workflow + docs. Every change must pass `make lint` and `make test`. **Tech Stack:** Python 3.12, `uv`, `hatchling`, Click, `tomlkit`; Node 20+, npm workspaces; GitHub Actions + PyPI/npm Trusted Publishing (OIDC); Jinja2 for template rendering. @@ -134,7 +134,7 @@ Initial public release. All 14 Python packages publish to PyPI and all 3 JS pack ### Added -- `simple-module new ` / `sm new ` CLI generator scaffolding a working app with `users + dashboard + permissions` pre-wired. +- `simple-module new ` / `smpy new ` CLI generator scaffolding a working app with `users + dashboard + permissions` pre-wired. - PyPI Trusted Publishing workflow (`.github/workflows/release.yml`) for zero-secret releases. - npm Trusted Publishing for all three JS packages. @@ -1152,7 +1152,7 @@ Run: `uv sync --all-packages && uv run which simple-module` Expected: prints a path to a `simple-module` executable inside `.venv/bin/`. Run: `uv run simple-module --help` -Expected: prints the Click help menu (same as `sm --help`). +Expected: prints the Click help menu (same as `smpy --help`). - [ ] **Step 3: Run tests** @@ -1671,7 +1671,7 @@ git commit -m "docs(db): add package README for PyPI" ```markdown # simple-module-hosting -FastAPI + Inertia.js host runtime for the [simple_module](https://github.com/antosubash/simple_module_python) framework — builds the app, wires the middleware pipeline, exposes the `sm` / `simple-module` CLI, and ships the project scaffolder. +FastAPI + Inertia.js host runtime for the [simple_module](https://github.com/antosubash/simple_module_python) framework — builds the app, wires the middleware pipeline, exposes the `smpy` / `simple-module` CLI, and ships the project scaffolder. ## Install @@ -1690,8 +1690,8 @@ uvx simple-module new my-app - `create_app(settings)` — returns a fully-wired `FastAPI` instance with all discovered modules registered. - Middleware pipeline (execution order): CorrelationId → RequestLogging → SecurityHeaders → Session → `` → Tenant (opt-in) → Locale → InertiaLayoutData → app. - Inertia wiring — shared props (`auth`, `menus`, `i18n`), `InertiaDep`, page-route lookup. -- CLI entry points: both `sm` and `simple-module` are installed and alias the same Click tree. -- Scaffolders — `sm create-host`, `sm create-module`, `sm new` (greenfield app with users + dashboard + permissions pre-wired), `sm gen-pages`. +- CLI entry points: both `smpy` and `simple-module` are installed and alias the same Click tree. +- Scaffolders — `smpy create-host`, `smpy create-module`, `smpy new` (greenfield app with users + dashboard + permissions pre-wired), `smpy gen-pages`. ## Usage @@ -1717,7 +1717,7 @@ simple-module doctor # diagnostic codes (SM001-SM017) simple-module gen-pages # regenerate client_app/modules.generated.ts ``` -`sm` works identically to `simple-module`. +`smpy` works identically to `simple-module`. ## Depends on @@ -1881,7 +1881,7 @@ Pre-wired into any app scaffolded with `simple-module new`. - Admin invite flow — admin enters an email, recipient clicks a link, sets a password, is logged in. - Public signup toggle (`SM_USERS_ALLOW_SIGNUP`, default `false`). - Bootstrap admin via env vars (`SM_USERS_BOOTSTRAP_EMAIL` + `SM_USERS_BOOTSTRAP_PASSWORD`) — idempotent, only creates if the users table is empty. -- `sm-users create-admin` CLI for ad-hoc admin creation. +- `smpy users create-admin` CLI for ad-hoc admin creation. - Inertia pages for login/register/invite-accept/admin-invite. - Console mailer (logs to stdout) or SMTP mailer (`SM_USERS_MAILER=smtp`). @@ -1890,7 +1890,7 @@ Pre-wired into any app scaffolded with `simple-module new`. CLI: ```bash -uv run sm-users create-admin --email admin@example.com --password 'change-me' +uv run smpy users create-admin --email admin@example.com --password 'change-me' ``` Bootstrap-on-boot (`.env`): @@ -3013,11 +3013,11 @@ git commit -m "feat(scripts): add lockstep bump_version.py for 17-package releas --- -## Phase 7 — `sm new` CLI + template +## Phase 7 — `smpy new` CLI + template -This phase extends the existing `simple_module_hosting.cli` with a new `new` subcommand that scaffolds a full app with `users + dashboard + permissions` pre-wired. The existing `sm create-host` stays (it's the lower-level scaffolder); `sm new` is the opinionated wrapper. +This phase extends the existing `simple_module_hosting.cli` with a new `new` subcommand that scaffolds a full app with `users + dashboard + permissions` pre-wired. The existing `smpy create-host` stays (it's the lower-level scaffolder); `smpy new` is the opinionated wrapper. -### Task 7.1: Write failing test for `sm new` +### Task 7.1: Write failing test for `smpy new` **Files:** - Create: `framework/hosting/tests/test_cli_new.py` @@ -3025,7 +3025,7 @@ This phase extends the existing `simple_module_hosting.cli` with a new `new` sub - [ ] **Step 1: Write the test file** ```python -"""Tests for the `sm new` / `simple-module new` CLI subcommand.""" +"""Tests for the `smpy new` / `simple-module new` CLI subcommand.""" from __future__ import annotations import json @@ -3131,7 +3131,7 @@ The existing `_create_host(target, name, modules)` already copies `templates/hos ```python # --------------------------------------------------------------- -# create_app_project — used by `sm new` / `simple-module new` +# create_app_project — used by `smpy new` / `simple-module new` # --------------------------------------------------------------- import json as _json @@ -3242,7 +3242,7 @@ def _inject_py_deps(text: str, deps: list[str], dev_deps: list[str]) -> str: ```bash git add framework/hosting/simple_module_hosting/scaffolding.py -git commit -m "feat(scaffolding): add create_app_project helper for sm new" +git commit -m "feat(scaffolding): add create_app_project helper for smpy new" ``` ### Task 7.3: Add the `new` Click subcommand @@ -3354,7 +3354,7 @@ Expected: `/tmp/smoke/` contains `pyproject.toml`, `main.py`, `package.json`, `. ```bash git add framework/hosting/simple_module_hosting/cli.py framework/hosting/tests/test_cli_new.py -git commit -m "feat(cli): add 'sm new' / 'simple-module new' greenfield generator +git commit -m "feat(cli): add 'smpy new' / 'simple-module new' greenfield generator Scaffolds a fresh app pre-wired with users, dashboard, and permissions. Generates a random SM_SECRET_KEY, sets the DB URL @@ -3408,7 +3408,7 @@ Expected: still pass. ```bash git add framework/hosting/simple_module_hosting/templates/host/ -git commit -m "chore(template): ensure host template is compatible with sm new flow" || echo "nothing to commit" +git commit -m "chore(template): ensure host template is compatible with smpy new flow" || echo "nothing to commit" ``` --- diff --git a/docs/superpowers/plans/2026-04-26-cli-modules-and-bg-jobs.md b/docs/superpowers/plans/2026-04-26-cli-modules-and-bg-jobs.md index ea0bbc4d..eb90cb54 100644 --- a/docs/superpowers/plans/2026-04-26-cli-modules-and-bg-jobs.md +++ b/docs/superpowers/plans/2026-04-26-cli-modules-and-bg-jobs.md @@ -2,7 +2,7 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Upgrade `sm new` so it scaffolds a SimpleModule project with any chosen subset of modules (presets or custom) and lands a runnable Celery worker + beat + Redis stack when `background_tasks` is selected — no manual editing required. +**Goal:** Upgrade `smpy new` so it scaffolds a SimpleModule project with any chosen subset of modules (presets or custom) and lands a runnable Celery worker + beat + Redis stack when `background_tasks` is selected — no manual editing required. **Architecture:** Replace the single-file `framework/hosting/simple_module_hosting/cli.py` with a `cli/` package containing a hardcoded module **catalog** (with transitive-dep resolution), an interactive **wizard**, and per-module **recipes** that perform post-scaffold actions. The `background_tasks` recipe writes `scripts/run_worker.py`, appends Make targets, and emits a `docker-compose.yml` + `worker.Dockerfile`. @@ -62,9 +62,9 @@ git mv framework/hosting/simple_module_hosting/cli.py framework/hosting/simple_m Run: `uv run pytest framework/hosting/tests/test_cli_new.py -v` Expected: PASS (3 tests). Python imports `cli/__init__.py` for `simple_module_hosting.cli` exactly the same as `cli.py`, so `main` is still findable. -- [ ] **Step 4: Verify `sm` console script still resolves** +- [ ] **Step 4: Verify `smpy` console script still resolves** -Run: `uv run sm --help` +Run: `uv run smpy --help` Expected: Lists `new`, `create-host`, `create-module`, `gen-pages`, `sync-js-deps` — same as before. - [ ] **Step 5: Commit** @@ -290,7 +290,7 @@ git add framework/hosting/simple_module_hosting/cli/catalog.py framework/hosting git commit -m "feat(cli): module catalog with transitive dep expansion Adds CATALOG, PRESETS, and expand_deps() — pure data + one pure -function. Will be wired into 'sm new' in a follow-up." +function. Will be wired into 'smpy new' in a follow-up." ``` --- @@ -307,7 +307,7 @@ Wizard owns the prompt sequence: db → tenancy → preset (or custom checkbox l ```python # framework/hosting/tests/test_cli_wizard.py -"""Tests for the `sm new` interactive wizard.""" +"""Tests for the `smpy new` interactive wizard.""" from __future__ import annotations @@ -402,7 +402,7 @@ Expected: All 6 tests fail with `ModuleNotFoundError: No module named 'simple_mo ```python # framework/hosting/simple_module_hosting/cli/wizard.py -"""Interactive prompt sequence for `sm new`. +"""Interactive prompt sequence for `smpy new`. Returns the user's choices as ``(db, tenancy, selected)`` where ``selected`` is the topologically resolved module list (already includes transitive @@ -469,7 +469,7 @@ Expected: All 6 tests pass. ```bash git add framework/hosting/simple_module_hosting/cli/wizard.py framework/hosting/tests/test_cli_wizard.py -git commit -m "feat(cli): interactive wizard for sm new +git commit -m "feat(cli): interactive wizard for smpy new db -> tenancy -> preset (or custom checkbox loop) -> confirm. Auto-adds required deps with a printed note. No new TUI dependency." @@ -787,7 +787,7 @@ Expected: All tests fail with `ModuleNotFoundError: No module named 'simple_modu # framework/hosting/simple_module_hosting/cli/recipes.py """Per-module post-scaffold recipes. -A recipe is invoked by ``sm new`` after the base host scaffold lands. It +A recipe is invoked by ``smpy new`` after the base host scaffold lands. It performs module-specific actions (write helper scripts, append Make targets, drop a docker-compose stack). The framework layer is kept free of devex concerns — recipes know about Makefiles and compose, framework @@ -853,7 +853,7 @@ class BackgroundTasksRecipe: if path.exists(): raise FileExistsError( f"{path} already exists — refusing to clobber. " - "Remove the file or run `sm new` against an empty directory." + "Remove the file or run `smpy new` against an empty directory." ) run_worker_dest.parent.mkdir(parents=True, exist_ok=True) @@ -1057,7 +1057,7 @@ drive both Python deps (from catalog) and post-scaffold recipes." --- -## Task 6: Wire `sm new` to use catalog + wizard + recipes +## Task 6: Wire `smpy new` to use catalog + wizard + recipes **Files:** - Create: `framework/hosting/simple_module_hosting/cli/new.py` @@ -1155,7 +1155,7 @@ Expected: New tests fail (current `new` command doesn't accept `--preset` / `--w ```python # framework/hosting/simple_module_hosting/cli/new.py -"""The upgraded `sm new` command. +"""The upgraded `smpy new` command. Combines flag-driven non-interactive use (`--preset` / `--with`) with the interactive wizard. All paths converge on @@ -1314,7 +1314,7 @@ Expected: All tests pass — both the existing (`--yes --db sqlite`) tests and t Run: ```bash -TMP=$(mktemp -d) && uv run sm new demo --yes --preset full --no-install --dest "$TMP/demo" +TMP=$(mktemp -d) && uv run smpy new demo --yes --preset full --no-install --dest "$TMP/demo" ls "$TMP/demo/scripts/run_worker.py" "$TMP/demo/docker-compose.yml" "$TMP/demo/docker/worker.Dockerfile" grep -E '^(worker|beat|worker-docker):' "$TMP/demo/Makefile" grep SM_BG_TASKS_BROKER_URL "$TMP/demo/.env.example" @@ -1327,7 +1327,7 @@ Expected: every file/grep matches; no errors. git add framework/hosting/simple_module_hosting/cli/new.py \ framework/hosting/simple_module_hosting/cli/__init__.py \ framework/hosting/tests/test_cli_new.py -git commit -m "feat(cli): sm new with --preset, --with, and wizard +git commit -m "feat(cli): smpy new with --preset, --with, and wizard Scaffolds a project with any chosen subset of modules. Selecting background_tasks lands a runnable Celery worker + beat + Redis stack @@ -1355,8 +1355,8 @@ Expected: All tests pass — including the existing scaffolding/host tests, whic Run: ```bash -TMP=$(mktemp -d) && uv run sm new demo --yes --preset full --no-install --dest "$TMP/demo" -cd "$TMP/demo" && uv sync && uv run sm doctor 2>&1 || true +TMP=$(mktemp -d) && uv run smpy new demo --yes --preset full --no-install --dest "$TMP/demo" +cd "$TMP/demo" && uv sync && uv run smpy doctor 2>&1 || true cd - ``` Expected: Exits clean (no SM001/SM008/SM009 errors). SM010 may surface because the freshly-scaffolded project has no migration history yet — that's existing behavior. @@ -1378,7 +1378,7 @@ Otherwise skip. **Spec coverage:** Every section of the spec is mapped to a task — - Catalog → Task 2 - Wizard → Task 3 -- `sm new` flags → Task 6 +- `smpy new` flags → Task 6 - Recipes + templates → Task 4 - `create_app_project` refactor → Task 5 - File-layout reorg into `cli/` package → Task 1 diff --git a/docs/superpowers/plans/2026-04-26-standalone-cli-package.md b/docs/superpowers/plans/2026-04-26-standalone-cli-package.md index dfd8ac20..15bae97b 100644 --- a/docs/superpowers/plans/2026-04-26-standalone-cli-package.md +++ b/docs/superpowers/plans/2026-04-26-standalone-cli-package.md @@ -2,7 +2,7 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Carve the scaffolder out of `simple_module_hosting` into a new PyPI distribution `simple-module` whose only deps are `typer` + `tomlkit`. Single `sm` console script; plugin subcommands (`sm host gen-pages`, `sm users create-admin`, …) discovered via Python entry points. All `sm-*` sibling scripts go away. +**Goal:** Carve the scaffolder out of `simple_module_hosting` into a new PyPI distribution `simple-module` whose only deps are `typer` + `tomlkit`. Single `smpy` console script; plugin subcommands (`smpy host gen-pages`, `smpy users create-admin`, …) discovered via Python entry points. All `sm-*` sibling scripts go away. **Architecture:** New workspace member `framework/cli/` containing the importable package `simple_module`. Click → Typer 1:1 port at the decorator layer; logic, templates, and tests are copies. `simple_module_hosting` keeps only its runtime + a new `host_cli.py` Typer app registered as a plugin. Modules `users` and `settings` swap their `sm-*` console-script entries for `simple_module.cli_plugins` entry-point entries. @@ -25,7 +25,7 @@ | `framework/cli/simple_module/catalog.py` | `ModuleEntry`, `CATALOG`, `PRESETS`, `expand_deps` (moved). | | `framework/cli/simple_module/wizard.py` | `run_wizard` (moved + Typer port). | | `framework/cli/simple_module/recipes.py` | `Recipe`, `BackgroundTasksRecipe`, `RECIPES` (moved). | -| `framework/cli/simple_module/new.py` | `sm new` Typer command (moved + ported). | +| `framework/cli/simple_module/new.py` | `smpy new` Typer command (moved + ported). | | `framework/cli/simple_module/cli.py` | Root Typer app + `create-host` / `create-module` commands + plugin mount + `main`. | | `framework/cli/simple_module/plugins.py` | Entry-point discovery + mounting. | | `framework/cli/simple_module/templates/` | All template files (moved from hosting). | @@ -37,13 +37,13 @@ | `framework/hosting/simple_module_hosting/_env.py` | **Deleted**. | | `framework/hosting/simple_module_hosting/templates/` | **Deleted** (moved). | | `framework/hosting/simple_module_hosting/manifest.py` | Stays — used by `host_cli.py`. | -| `framework/hosting/pyproject.toml` | Drops `sm`/`simple-module` scripts; adds `simple_module.cli_plugins` entry. | -| `modules/users/pyproject.toml` | Drops `sm-users` script; adds `simple_module.cli_plugins` entry. | -| `modules/settings/pyproject.toml` | Drops `sm-settings` script; adds `simple_module.cli_plugins` entry. | +| `framework/hosting/pyproject.toml` | Drops `smpy`/`simple-module` scripts; adds `simple_module.cli_plugins` entry. | +| `modules/users/pyproject.toml` | Drops `smpy users` script; adds `simple_module.cli_plugins` entry. | +| `modules/settings/pyproject.toml` | Drops `smpy settings` script; adds `simple_module.cli_plugins` entry. | | `modules/settings/settings/cli.py` | Rewritten as Typer app. | -| `Makefile` | `sm gen-pages` → `sm host gen-pages`; `sm sync-js-deps` → `sm host sync-js-deps`. | +| `Makefile` | `smpy gen-pages` → `smpy host gen-pages`; `smpy sync-js-deps` → `smpy host sync-js-deps`. | | `pyproject.toml` (root) | Workspace `members = ["framework/*", …]` already covers `framework/cli`; verify ruff `extend-exclude` updated to point at the new templates path. | -| `README.md` | `sm-users` / `sm-settings` snippets updated to `sm users …` / `sm settings …`. | +| `README.md` | `smpy users` / `smpy settings` snippets updated to `smpy users …` / `smpy settings …`. | --- @@ -72,7 +72,7 @@ Create `framework/cli/pyproject.toml`: [project] name = "simple-module" version = "0.0.1" -description = "Standalone scaffolder for the SimpleModule framework — `sm new`, `sm create-module`, plugin host." +description = "Standalone scaffolder for the SimpleModule framework — `smpy new`, `smpy create-module`, plugin host." readme = "README.md" license = "MIT" license-files = ["LICENSE"] @@ -96,7 +96,7 @@ dependencies = [ ] [project.scripts] -sm = "simple_module.cli:main" +smpy = "simple_module.cli:main" simple-module = "simple_module.cli:main" [project.urls] @@ -117,7 +117,7 @@ packages = ["simple_module"] Create `framework/cli/simple_module/cli.py`: ```python -"""Root `sm` command — scaffolders + plugin mount. +"""Root `smpy` command — scaffolders + plugin mount. This file gets fleshed out in Task 5 (Typer port) and Task 6 (plugin discovery). For now it exists only so the ``sm = simple_module.cli:main`` @@ -149,19 +149,19 @@ Standalone scaffolder for the [SimpleModule framework](https://github.com/antosu ```bash pip install simple-module # or: pipx install simple-module -sm new my-app # interactive wizard -sm new my-app --yes --preset full +smpy new my-app # interactive wizard +smpy new my-app --yes --preset full ``` -Provides three built-in commands: `sm new`, `sm create-host`, `sm create-module`. +Provides three built-in commands: `smpy new`, `smpy create-host`, `smpy create-module`. When other framework packages are installed, they contribute additional subcommands via the `simple_module.cli_plugins` entry-point group: | Package | Commands | |---|---| -| `simple_module_hosting` | `sm host gen-pages`, `sm host sync-js-deps` | -| `simple_module_users` | `sm users create-admin` | -| `simple_module_settings` | `sm settings import-from-env` | +| `simple_module_hosting` | `smpy host gen-pages`, `smpy host sync-js-deps` | +| `simple_module_users` | `smpy users create-admin` | +| `simple_module_settings` | `smpy settings import-from-env` | ## License @@ -177,7 +177,7 @@ Expected: succeeds; the new `simple-module` distribution appears in `uv pip list - [ ] **Step 6: Verify the stub console script resolves** -Run: `uv run sm --help 2>&1 | head -3` +Run: `uv run smpy --help 2>&1 | head -3` Expected: prints the SystemExit message from Step 3 (proves the entry point + package import works). - [ ] **Step 7: Commit** @@ -619,7 +619,7 @@ git mv framework/hosting/tests/test_scaffolding_module.py framework/cli/tests/te - [ ] **Step 8: Update imports in the moved tests** -In both moved files, replace `from simple_module_hosting.scaffolding import ...` with `from simple_module.scaffolding import ...`. The `from simple_module_hosting.cli import main` in the `Click sm create-host/create-module` integration tests stays — those tests get rewritten in Task 5. +In both moved files, replace `from simple_module_hosting.scaffolding import ...` with `from simple_module.scaffolding import ...`. The `from simple_module_hosting.cli import main` in the `Click smpy create-host/create-module` integration tests stays — those tests get rewritten in Task 5. Concretely, in `framework/cli/tests/test_scaffolding_host.py`: - Line 14: `from simple_module_hosting.scaffolding import compute_module_pages` — leave for now (`compute_module_pages` is re-exported via the shim — works through Task 8). @@ -812,7 +812,7 @@ In `framework/cli/tests/test_cli_new.py`, change: - [ ] **Step 8: Run the test suite** Run: `uv run pytest framework/cli/ framework/hosting/ -q` -Expected: all tests pass — the existing Click `sm` is still functional through the hosting package; tests in `framework/cli/tests/` import from the moved homes and exercise the same code. +Expected: all tests pass — the existing Click `smpy` is still functional through the hosting package; tests in `framework/cli/tests/` import from the moved homes and exercise the same code. - [ ] **Step 9: Commit** @@ -846,7 +846,7 @@ Big change. Rewrites the four command files — `new.py`, `cli.py` (new), and th Replace the entire file with: ```python -"""``sm new`` Typer command — flag-driven or interactive scaffolder.""" +"""``smpy new`` Typer command — flag-driven or interactive scaffolder.""" from __future__ import annotations @@ -981,7 +981,7 @@ def new_project( Replace the file with: ```python -"""Interactive prompt sequence for ``sm new``.""" +"""Interactive prompt sequence for ``smpy new``.""" from __future__ import annotations @@ -1040,15 +1040,15 @@ def run_wizard(*, default_db: str, default_tenancy: bool) -> tuple[str, bool, li Replace the stub from Task 1 with: ```python -"""Root `sm` Typer app — scaffolders + plugin mount. +"""Root `smpy` Typer app — scaffolders + plugin mount. Built-in commands: - sm new - sm create-host - sm create-module + smpy new + smpy create-host + smpy create-module Plugins discovered via the ``simple_module.cli_plugins`` entry-point -group are mounted as named subgroups (e.g. ``sm host gen-pages``). +group are mounted as named subgroups (e.g. ``smpy host gen-pages``). """ from __future__ import annotations @@ -1139,12 +1139,12 @@ def create_module( # Plugin discovery — mount any installed `simple_module.cli_plugins` -# entry-point apps as named subgroups (e.g. `sm host gen-pages`). +# entry-point apps as named subgroups (e.g. `smpy host gen-pages`). discover_and_mount(app) def main() -> None: - """Entry point for the `sm` console script.""" + """Entry point for the `smpy` console script.""" app() @@ -1237,7 +1237,7 @@ def test_wizard_aborts_on_confirm_no() -> None: - [ ] **Step 6: Delete the hosting CLI package** -The Click `sm` entry point in `simple_module_hosting` is being replaced by `simple_module.cli:main`. Delete: +The Click `smpy` entry point in `simple_module_hosting` is being replaced by `simple_module.cli:main`. Delete: ```bash git rm -r framework/hosting/simple_module_hosting/cli/ @@ -1278,14 +1278,14 @@ Also switch `from click.testing import CliRunner` → `from typer.testing import - [ ] **Step 9: Re-install workspace and run tests** Run: `uv sync --all-packages && uv run pytest framework/cli/ framework/hosting/ -q` -Expected: all tests pass. The `sm` console script is now provided by `simple-module`; the old `simple_module_hosting.cli:main` no longer exists. +Expected: all tests pass. The `smpy` console script is now provided by `simple-module`; the old `simple_module_hosting.cli:main` no longer exists. Smoke check the binary: -Run: `uv run sm --help` +Run: `uv run smpy --help` Expected: lists `new`, `create-host`, `create-module` (no plugins yet — Task 6). -Run: `uv run sm new demo --yes --preset full --no-install --dest /tmp/sm-typer-smoke` +Run: `uv run smpy new demo --yes --preset full --no-install --dest /tmp/sm-typer-smoke` Expected: works the same as before; produces the demo project. Clean up: `rm -rf /tmp/sm-typer-smoke`. - [ ] **Step 10: Commit** @@ -1418,14 +1418,14 @@ Expected: FAIL — `_iter_plugin_entries` does not exist yet, plus the stub does Replace the stub with: ```python -"""Plugin discovery for ``sm`` via the ``simple_module.cli_plugins`` group. +"""Plugin discovery for ``smpy`` via the ``simple_module.cli_plugins`` group. Each entry-point's value (``module:attr``) must resolve to a :class:`typer.Typer` instance. The entry-point name becomes the -subcommand namespace under ``sm`` (e.g. ``sm host gen-pages``). +subcommand namespace under ``smpy`` (e.g. ``smpy host gen-pages``). Failed loads (broken import, wrong type) print one line to stderr and -are skipped — ``sm`` keeps working with whatever else loads. +are skipped — ``smpy`` keeps working with whatever else loads. """ from __future__ import annotations @@ -1482,9 +1482,9 @@ def discover_and_mount(root: typer.Typer) -> None: Run: `uv run pytest framework/cli/tests/test_plugin_discovery.py -v` Expected: 4 tests pass. -- [ ] **Step 5: Verify `sm --help` still works (no plugins installed yet)** +- [ ] **Step 5: Verify `smpy --help` still works (no plugins installed yet)** -Run: `uv run sm --help` +Run: `uv run smpy --help` Expected: same output as before, no errors. No `host`, `users`, or `settings` subgroups appear yet (those are wired in Tasks 7 & 8). - [ ] **Step 6: Commit** @@ -1508,7 +1508,7 @@ EOF **Files:** - Create: `framework/hosting/simple_module_hosting/host_cli.py` (Typer app with `gen-pages` + `sync-js-deps`). - Modify: `framework/hosting/pyproject.toml` (add `[project.entry-points."simple_module.cli_plugins"]`). -- Modify: `Makefile` (`sm gen-pages` → `sm host gen-pages`; `sm sync-js-deps` → `sm host sync-js-deps`). +- Modify: `Makefile` (`smpy gen-pages` → `smpy host gen-pages`; `smpy sync-js-deps` → `smpy host sync-js-deps`). - Create: `framework/hosting/tests/test_host_cli.py` (smoke). - [ ] **Step 1: Write the failing test** @@ -1559,7 +1559,7 @@ Expected: FAIL — `simple_module_hosting.host_cli` does not exist. Translate the existing `gen-pages` and `sync-js-deps` Click commands to Typer: ```python -"""``sm host`` plugin — project-time helpers exposed through the simple-module CLI. +"""``smpy host`` plugin — project-time helpers exposed through the simple-module CLI. Commands here need module discovery (``simple_module_core.discover_modules``) and the manifest helpers; they're not part of the standalone scaffolder. @@ -1691,10 +1691,10 @@ Expected: 3 tests pass. - [ ] **Step 6: Re-install workspace and verify the plugin mounts** -Run: `uv sync --all-packages && uv run sm --help` +Run: `uv sync --all-packages && uv run smpy --help` Expected: `host` appears in the subcommand list (from the entry point). -Run: `uv run sm host --help` +Run: `uv run smpy host --help` Expected: lists `gen-pages` and `sync-js-deps`. - [ ] **Step 7: Update Makefile** @@ -1703,20 +1703,20 @@ In `Makefile`, replace: ```makefile gen-pages: - uv run --project host sm gen-pages --host-dir=host/client_app + uv run --project host smpy gen-pages --host-dir=host/client_app sync-module-deps: - uv run --project host sm sync-js-deps --host-client-app=host/client_app + uv run --project host smpy sync-js-deps --host-client-app=host/client_app ``` with: ```makefile gen-pages: - uv run --project host sm host gen-pages --host-dir=host/client_app + uv run --project host smpy host gen-pages --host-dir=host/client_app sync-module-deps: - uv run --project host sm host sync-js-deps --host-client-app=host/client_app + uv run --project host smpy host sync-js-deps --host-client-app=host/client_app ``` - [ ] **Step 8: Verify Make targets still work** @@ -1728,12 +1728,12 @@ Expected: Wrote manifest.json, generated.ts, generated.css. (Or, if the host has ```bash git add -A -git commit -m "feat(hosting): host_cli plugin (sm host gen-pages, sm host sync-js-deps) +git commit -m "feat(hosting): host_cli plugin (smpy host gen-pages, smpy host sync-js-deps) $(cat <<'EOF' -gen-pages and sync-js-deps move out of the deleted sm console script +gen-pages and sync-js-deps move out of the deleted smpy console script and into a Typer plugin published under the simple_module.cli_plugins -entry-point group. Makefile updated for the new sm host * shape. +entry-point group. Makefile updated for the new smpy host * shape. EOF )" ``` @@ -1743,11 +1743,11 @@ EOF ## Task 8: Convert `users` and `settings` modules to plugins **Files:** -- Modify: `modules/users/pyproject.toml` (drop `sm-users`, add entry point). +- Modify: `modules/users/pyproject.toml` (drop `smpy users`, add entry point). - Modify: `modules/users/users/cli.py` (already a Typer app — verify; minimal changes). - Modify: `modules/settings/settings/cli.py` (rewrite as Typer app named `app`). -- Modify: `modules/settings/pyproject.toml` (drop `sm-settings`, add entry point). -- Modify: `README.md` (`sm-users` / `sm-settings` snippets → `sm users` / `sm settings`). +- Modify: `modules/settings/pyproject.toml` (drop `smpy settings`, add entry point). +- Modify: `README.md` (`smpy users` / `smpy settings` snippets → `smpy users` / `smpy settings`). - [ ] **Step 1: Update `modules/users/pyproject.toml`** @@ -1755,7 +1755,7 @@ Find: ```toml [project.scripts] -sm-users = "users.cli:app" +smpy users = "users.cli:app" ``` Replace with: @@ -1772,7 +1772,7 @@ users = "users.cli:app" Replace the entire file with: ```python -"""``sm settings`` plugin — currently only ``import-from-env``. +"""``smpy settings`` plugin — currently only ``import-from-env``. One-shot migration: walks every registered module's BaseSettings and writes a SYSTEM-scoped override for each ``SM__`` env @@ -1841,7 +1841,7 @@ Find: ```toml [project.scripts] -sm-settings = "settings.cli:main" +smpy settings = "settings.cli:main" ``` Replace with: @@ -1853,23 +1853,23 @@ settings = "settings.cli:app" - [ ] **Step 4: Update existing settings test if any** -Run: `grep -n "sm-settings\|settings.cli.main\|from settings.cli import main" modules/settings/tests/` +Run: `grep -n "smpy settings\|settings.cli.main\|from settings.cli import main" modules/settings/tests/` If there are matches, fix them — `main` no longer exists. The new entry is `app`. Most likely there are no test changes needed (the existing settings tests target `import_from_env_impl` directly). - [ ] **Step 5: Re-install + smoke** -Run: `uv sync --all-packages && uv run sm --help 2>&1 | head -20` +Run: `uv sync --all-packages && uv run smpy --help 2>&1 | head -20` Expected: subgroups `host`, `users`, `settings` all appear. -Run: `uv run sm users --help` and `uv run sm settings --help` +Run: `uv run smpy users --help` and `uv run smpy settings --help` Expected: each lists their subcommand(s). - [ ] **Step 6: Update README** In `README.md`, replace: -- `uv run sm-users create-admin --email …` → `uv run sm users create-admin --email …` -- `uv run sm-settings import-from-env` → `uv run sm settings import-from-env` -- Any other occurrences of `sm-users` / `sm-settings`. +- `uv run smpy users create-admin --email …` → `uv run smpy users create-admin --email …` +- `uv run smpy settings import-from-env` → `uv run smpy settings import-from-env` +- Any other occurrences of `smpy users` / `smpy settings`. - [ ] **Step 7: Run the full test suite** @@ -1883,9 +1883,9 @@ git add -A git commit -m "refactor(modules): convert users + settings to sm plugins $(cat <<'EOF' -Drops sm-users and sm-settings console scripts. Both modules now +Drops smpy users and smpy settings console scripts. Both modules now register Typer apps under the simple_module.cli_plugins entry-point -group, mounted as `sm users` and `sm settings`. settings/cli.py +group, mounted as `smpy users` and `smpy settings`. settings/cli.py rewritten as a Typer app (was hand-rolled Click-style argv parsing). README updated for the new command shape. EOF @@ -1998,11 +1998,11 @@ Expected: all tests green. The plan's planned test count is roughly: Run: ```bash -uv run sm --help -uv run sm host --help -uv run sm users --help -uv run sm settings --help -TMP=$(mktemp -d) && uv run sm new demo --yes --preset full --no-install --dest "$TMP/demo" +uv run smpy --help +uv run smpy host --help +uv run smpy users --help +uv run smpy settings --help +TMP=$(mktemp -d) && uv run smpy new demo --yes --preset full --no-install --dest "$TMP/demo" ls "$TMP/demo/scripts/run_worker.py" "$TMP/demo/docker-compose.yml" ``` Expected: every command works; the new project lands all background-task scaffolding correctly. @@ -2018,7 +2018,7 @@ $(cat <<'EOF' workspace dep on simple_module. - Add framework/cli/tests/test_no_framework_deps.py to guard against future dep drift in the standalone scaffolder. -- README + Makefile reflect the final sm host/users/settings shape. +- README + Makefile reflect the final smpy host/users/settings shape. EOF )" ``` @@ -2033,7 +2033,7 @@ EOF - Click → Typer port → Task 5. - Plugin discovery (`simple_module.cli_plugins`, `discover_and_mount`, error handling, dup detection) → Task 6. - `host_cli` plugin (gen-pages, sync-js-deps), Makefile rename → Task 7. -- `users` + `settings` plugin migration, drop sm-users/sm-settings scripts → Task 8. +- `users` + `settings` plugin migration, drop smpy users/smpy settings scripts → Task 8. - No-framework-deps guard → Task 9. - Cleanup, README updates, full verification → Tasks 8 & 9. diff --git a/docs/superpowers/specs/2026-04-21-db-backed-module-settings-design.md b/docs/superpowers/specs/2026-04-21-db-backed-module-settings-design.md index bb5320bc..6e546562 100644 --- a/docs/superpowers/specs/2026-04-21-db-backed-module-settings-design.md +++ b/docs/superpowers/specs/2026-04-21-db-backed-module-settings-design.md @@ -14,7 +14,7 @@ Collapse the `.env` surface to a four-variable bootstrap and move every other co - New admin UI at `/settings/modules` (replaces today's read-only view) — sidebar + main panel, typed inputs, per-module save, per-field reset-to-default. - Hot-reload on save: rebuild `app.state..settings` and fire a `settings.reloaded` event. - Secret fields masked in UI (write-only). -- One-shot `sm-settings import-from-env` CLI to migrate existing deployments. +- One-shot `smpy settings import-from-env` CLI to migrate existing deployments. **Out of scope.** - Tenant-scoped and user-scoped overrides in the new UI. The existing free-form `/settings` Browse/Create/Edit pages — which already support all three scopes — stay for power users. @@ -171,15 +171,15 @@ View endpoints: - **Pydantic validation fails on save.** UI shows per-field errors; no DB write; `app.state` unchanged. - **Pydantic validation fails during `on_startup` hydrate.** The field's DB row is bad (e.g. schema changed between releases). Log a warning, keep the default for that field, continue booting. The UI flags the field as "stored value invalid — using default". -- **A module disappears between releases.** Its `Setting` rows become orphaned. `make doctor` gets a new `SM018` warning listing orphan settings rows; a separate CLI `sm-settings prune-orphans` removes them. +- **A module disappears between releases.** Its `Setting` rows become orphaned. `make doctor` gets a new `SM018` warning listing orphan settings rows; a separate CLI `smpy settings prune-orphans` removes them. - **Concurrent edits.** Last write wins (there's no optimistic concurrency today on `Setting`). Acceptable — admin UI with low write rate. - **Placeholder token-secret check.** `UsersSettings.model_validator` fires during DB hydration in production. On boot: blocks startup (matches current behavior). On UI save: returns validation error. ## Migration - **No Alembic migration** — `Setting` table already exists. -- **`sm-settings import-from-env` CLI** — one-shot: reads `SM__*` from the current process environment, writes corresponding rows to `Setting` for each registered module. Idempotent. -- **Release note** — deployments must run `sm-settings import-from-env` after upgrade (or accept that all modules revert to defaults). +- **`smpy settings import-from-env` CLI** — one-shot: reads `SM__*` from the current process environment, writes corresponding rows to `Setting` for each registered module. Idempotent. +- **Release note** — deployments must run `smpy settings import-from-env` after upgrade (or accept that all modules revert to defaults). - **Per-module code changes:** - Remove `env_prefix` and `env_file` from `SettingsConfigDict`. - `register_settings` calls `register_module_settings(app, package, SettingsCls)`; the helper installs defaults on `app.state..settings`. @@ -188,7 +188,7 @@ View endpoints: - `BootstrapSettings` — the four env vars, read at boot. Lives in `framework/hosting`. - `HostSettings` — `multi_tenant`, `tenant_header`, `i18n_default_locale`, `i18n_supported_locales`, `i18n_cookie_name`, plus anything else currently on the monolithic `HostingSettings` minus the bootstrap four. DB-backed via the same `register_module_settings` helper under `package="host"`. - **`.env.example`** — rewritten to the four bootstrap vars; everything else removed. -- **`docker-compose.yml`** — removes `SM_BG_TASKS_BROKER_URL` / `SM_BG_TASKS_RESULT_BACKEND` environment entries; the Redis defaults in `BackgroundTasksSettings` are updated to `redis://redis:6379/*` (matching the compose Redis service hostname). Dev-outside-compose users run `sm-settings import-from-env` or set overrides in the UI. +- **`docker-compose.yml`** — removes `SM_BG_TASKS_BROKER_URL` / `SM_BG_TASKS_RESULT_BACKEND` environment entries; the Redis defaults in `BackgroundTasksSettings` are updated to `redis://redis:6379/*` (matching the compose Redis service hostname). Dev-outside-compose users run `smpy settings import-from-env` or set overrides in the UI. - **`README.md`** — env-var table shrinks to four rows + a pointer to `/settings/modules`. ## Tests @@ -208,7 +208,7 @@ View endpoints: ## Diagnostics - Existing `SM012` (`register_settings` overridden but nothing on `app.state.`) is still emitted — the `register_module_settings` helper satisfies it automatically. -- New `SM018` — orphan `Setting` rows for packages that no longer register a BaseSettings class. Warning level; fixable via `sm-settings prune-orphans`. +- New `SM018` — orphan `Setting` rows for packages that no longer register a BaseSettings class. Warning level; fixable via `smpy settings prune-orphans`. ## Ship criteria @@ -216,7 +216,7 @@ View endpoints: - `make lint` green, including the 300-line file cap. `_module_settings.py` is ~153 lines today; the metadata extensions should stay under the cap or split into a sibling module. - `make doctor` green. - Manual: `/settings/modules` renders, editing `users.allow_signup` takes effect without restart, editing `background_tasks.broker_url` shows the "requires restart" badge. -- `sm-settings import-from-env` run against a dev `.env` successfully populates the `Setting` table. +- `smpy settings import-from-env` run against a dev `.env` successfully populates the `Setting` table. ## Open decisions (to resolve during plan writing) diff --git a/docs/superpowers/specs/2026-04-21-public-release-design.md b/docs/superpowers/specs/2026-04-21-public-release-design.md index 285d7561..96405dba 100644 --- a/docs/superpowers/specs/2026-04-21-public-release-design.md +++ b/docs/superpowers/specs/2026-04-21-public-release-design.md @@ -147,7 +147,7 @@ Per-package specifics below. Each README is ~60–120 lines. The "Usage" snippet |---|---| | `simple-module-core` | `ModuleBase` + `ModuleMeta` example, lifecycle hook list, entry-points declaration snippet, `discover_modules()` mention. | | `simple-module-db` | `create_module_base(name)` example, standard mixins (`AuditMixin`, `SoftDeleteMixin`, `MultiTenantMixin`, `VersionedMixin`), auto-commit-on-flush rule. | -| `simple-module-hosting` | `create_app(settings)` minimal `main.py` snippet, middleware pipeline overview, `sm` / `simple-module` CLI commands (`new`, `doctor`) and note that both names work. | +| `simple-module-hosting` | `create_app(settings)` minimal `main.py` snippet, middleware pipeline overview, `smpy` / `simple-module` CLI commands (`new`, `doctor`) and note that both names work. | | `simple-module-testing` | Automatic fixture loading via `pytest11` entry point, the fixtures provided (`app`, `client`, `db_session`, `authenticated_client`, `settings`). | | `simple-module-users` | Email+password auth, admin invite flow, `SM_USERS_*` settings, `sm-users create-admin` CLI. | | `simple-module-dashboard` | Adds `/dashboard` landing page for authenticated users, menu entry registration. | @@ -212,11 +212,11 @@ Specifics per package: - `files` includes `src/` so `.ts` sources are in the tarball. - Consumers' `tsconfig.json` must have `"allowJs": false` (default) and resolve `.ts` via bundler — already the case with Vite. -### 3. `sm` / `simple-module` CLI generator (Workstream 3) +### 3. `smpy` / `simple-module` CLI generator (Workstream 3) New `new` subcommand on the existing Click CLI in `simple_module_hosting/cli.py`. -**Both `sm` and `simple-module` work as CLI entry points** — they are aliases for the same underlying command. This is declared in `framework/hosting/pyproject.toml`: +**Both `smpy` and `simple-module` work as CLI entry points** — they are aliases for the same underlying command. This is declared in `framework/hosting/pyproject.toml`: ```toml [project.scripts] @@ -224,9 +224,9 @@ sm = "simple_module_hosting.cli:main" simple-module = "simple_module_hosting.cli:main" ``` -`sm` stays as the short form for daily use; `simple-module` is the discoverable long form that matches the package namespace (what users first encounter on PyPI). Both expose the full command tree — `new`, `doctor`, future subcommands — identically. +`smpy` stays as the short form for daily use; `simple-module` is the discoverable long form that matches the package namespace (what users first encounter on PyPI). Both expose the full command tree — `new`, `doctor`, future subcommands — identically. -**Interactive:** `simple-module new my-app` *or* `sm new my-app` +**Interactive:** `simple-module new my-app` *or* `smpy new my-app` **Scripted:** `simple-module new my-app --db sqlite --no-tenancy --yes` Flags: @@ -381,7 +381,7 @@ package.json × 3 ─┘ │ pipx install simple-module-hosting==$VERSION │ ▼ - sm new /tmp/smoke → make install (uv sync + npm install) → make test ✅ + smpy new /tmp/smoke → make install (uv sync + npm install) → make test ✅ ``` ## Error handling @@ -398,7 +398,7 @@ package.json × 3 ─┘ │ 1. **Bump script unit tests** — `scripts/tests/test_bump_version.py` with fixtures covering: Python bump succeeds, Python dep pins rewrite, npm version bump succeeds, `@simple-module-py/*` deps rewrite across `dependencies` / `devDependencies` / `peerDependencies`, `--check` mode, malformed TOML / JSON aborts cleanly, unrelated third-party deps stay untouched. 2. **Local dry run** — `make release-check version=0.0.1` confirms the bump is idempotent across all 17 files. 3. **TestPyPI + local `npm pack` rehearsal** — run `release.yml` with `target=testpypi` against version `0.0.1a0` (PEP 440 pre-release). Python side publishes to TestPyPI end-to-end; npm side runs `npm pack --dry-run` on every JS package and uploads the resulting `.tgz` tarballs as workflow artifacts so they can be inspected (or `npm install`ed from a file path) without burning an npm version. This must pass before the real run. -4. **Smoke job** as part of the workflow itself (real run only) — `sm new` + `make test` + `npm install` in the generated app, verifying both PyPI and npm packages resolve. +4. **Smoke job** as part of the workflow itself (real run only) — `smpy new` + `make test` + `npm install` in the generated app, verifying both PyPI and npm packages resolve. 5. **Per-package README smoke** — `scripts/check_readmes.py` verifies every one of the 17 published packages has a `README.md` > 500 bytes and contains the required H1 + "Install" + "Usage" sections. Runs in `make lint`. 6. **Per-package metadata smoke** — `scripts/check_metadata.py` verifies every one of the 17 packages has: - A non-placeholder `description` (not "Add your description here"). @@ -415,7 +415,7 @@ This is also the phasing for the implementation plan: 1. **Metadata hygiene + READMEs** (Workstreams 1 + 2 + 2b) — every `pyproject.toml` and `package.json` gets real description, classifiers/keywords, URLs, license fields; every one of the 17 packages gets a real `README.md`; root `LICENSE` added. 2. **Bump script + tests** (Workstream 4) — unit-tested in isolation before any workflow depends on it. Covers both TOML and JSON sides. -3. **`sm new` CLI + template** (Workstream 3) — must work locally (`uv run sm new /tmp/foo`) before the smoke test can rely on it. Template references `@simple-module-py/*` npm packages by version. +3. **`smpy new` CLI + template** (Workstream 3) — must work locally (`uv run smpy new /tmp/foo`) before the smoke test can rely on it. Template references `@simple-module-py/*` npm packages by version. 4. **Local npm pack smoke + TestPyPI rehearsal** (Workstream 5, `target=testpypi`) — publish `0.0.1a0` to TestPyPI + `npm pack` all JS packages locally; inspect artifacts. 5. **Trusted Publisher setup** (Workstream 6, manual) — operator configures PyPI + TestPyPI + npm. Happens between step 4 and step 6. 6. **Real release** — run `release.yml` with `target=pypi`, version `0.0.1`. Publishes 14 Python + 3 npm packages, runs smoke. diff --git a/docs/superpowers/specs/2026-04-26-cli-modules-and-bg-jobs-design.md b/docs/superpowers/specs/2026-04-26-cli-modules-and-bg-jobs-design.md index ed75c67e..65a994ab 100644 --- a/docs/superpowers/specs/2026-04-26-cli-modules-and-bg-jobs-design.md +++ b/docs/superpowers/specs/2026-04-26-cli-modules-and-bg-jobs-design.md @@ -5,7 +5,7 @@ ## Problem -`sm new ` today only pre-wires three modules (`users`, `dashboard`, `permissions`) and ignores the cost of opting in to anything else. To stand up a project that uses `background_tasks`, the user has to: +`smpy new ` today only pre-wires three modules (`users`, `dashboard`, `permissions`) and ignores the cost of opting in to anything else. To stand up a project that uses `background_tasks`, the user has to: 1. Add `simple_module_background_tasks` to `pyproject.toml` by hand. 2. Set `SM_BG_TASKS_BROKER_URL` in `.env`. @@ -17,7 +17,7 @@ That is enough friction that "I want background jobs" turns into a half-day yak- ## Goals -- `sm new` accepts an explicit module list via flags, or runs an interactive wizard when no flags are given. +- `smpy new` accepts an explicit module list via flags, or runs an interactive wizard when no flags are given. - Selecting `background_tasks` lands a runnable Celery worker + beat + Redis stack via `docker compose up`, plus host Make targets and `scripts/run_worker.py`, with no manual editing required. - Module dependencies are resolved transitively and added silently (with a printed note). - The CLI catalog is hardcoded — adding a new module to the catalog is a CLI code change. @@ -28,7 +28,7 @@ That is enough friction that "I want background jobs" turns into a half-day yak- - Third-party module registration. The catalog is closed for this change. - A TUI library (`questionary`, `inquirer`, etc.). Wizard uses `click.prompt` and `click.confirm`. - Worker queue configuration, autoscaling, beat-only deployments. -- Replacing or deprecating `sm create-host` — it remains the lower-level "deps-only" command. +- Replacing or deprecating `smpy create-host` — it remains the lower-level "deps-only" command. ## Design @@ -39,13 +39,13 @@ Replace `framework/hosting/simple_module_hosting/cli.py` with a package: ``` framework/hosting/simple_module_hosting/cli/ ├── __init__.py # click group; re-exports existing commands -├── new.py # `sm new` — flags + wizard, calls into catalog/wizard/recipes +├── new.py # `smpy new` — flags + wizard, calls into catalog/wizard/recipes ├── catalog.py # ModuleEntry, CATALOG, PRESETS, expand_deps() ├── wizard.py # interactive prompts (db, tenancy, preset, custom-pick) └── recipes.py # Recipe protocol + per-module post-scaffold actions ``` -Each file has one responsibility and stays under the 300-line cap. Existing commands (`create-host`, `create-module`, `gen-pages`, `sync-js-deps`) move into `cli/__init__.py` (or thin per-command modules) so the `sm` console script keeps working. +Each file has one responsibility and stays under the 300-line cap. Existing commands (`create-host`, `create-module`, `gen-pages`, `sync-js-deps`) move into `cli/__init__.py` (or thin per-command modules) so the `smpy` console script keeps working. ### Catalog @@ -89,10 +89,10 @@ def expand_deps(selected: Iterable[str]) -> tuple[list[str], list[tuple[str, str `expand_deps` is the only non-trivial function: BFS over `requires`, append-only, preserves topo order so the resulting list is always loadable. Unknown name raises `KeyError("unknown module: ; available: ...")`. -### `sm new` interface +### `smpy new` interface ``` -sm new +smpy new --dest --db sqlite|postgres (existing) --tenancy/--no-tenancy (existing) @@ -190,8 +190,8 @@ The two existing kwargs — `db` and `tenancy` — keep their current behavior. ### Backward compat -- `sm create-host --with=` keeps current "deps-only" behavior unchanged. -- `sm new --yes` with no other flags → standard preset → same outcome as today. +- `smpy create-host --with=` keeps current "deps-only" behavior unchanged. +- `smpy new --yes` with no other flags → standard preset → same outcome as today. - `create_app_project` callers passing only `name`, `db`, `tenancy` keep working (default `selected=None`). ## Tests @@ -199,7 +199,7 @@ The two existing kwargs — `db` and `tenancy` — keep their current behavior. - `framework/hosting/tests/test_cli_catalog.py` — `expand_deps` returns transitive closure; auto-add list correct; unknown name raises with available-list message; idempotent on already-resolved input. - `framework/hosting/tests/test_cli_wizard.py` — `CliRunner` driving each preset path (1/2/3/4) and the custom checkbox loop; verifies dep auto-add notice prints; verifies `--yes` skips prompts. - `framework/hosting/tests/test_cli_recipes.py` — `BackgroundTasksRecipe.apply` against a fresh tempdir already populated by `create_host`. Asserts `.env.example` contains `SM_BG_TASKS_BROKER_URL`, `scripts/run_worker.py` exists with the expected import, `Makefile` contains `worker:` / `beat:` targets, `docker-compose.yml` parses to YAML with `redis`, `worker`, `beat` keys under `services`. -- `framework/hosting/tests/test_cli_new.py` — end-to-end smoke: `sm new demo --yes --preset=full --no-install --dest=` produces a project that contains the union of all modules, a runnable compose file, Make targets, and `pyproject.toml` listing every package. +- `framework/hosting/tests/test_cli_new.py` — end-to-end smoke: `smpy new demo --yes --preset=full --no-install --dest=` produces a project that contains the union of all modules, a runnable compose file, Make targets, and `pyproject.toml` listing every package. ## Failure modes @@ -211,7 +211,7 @@ The two existing kwargs — `db` and `tenancy` — keep their current behavior. This change is additive: -- `cli.py` becomes `cli/__init__.py` plus four new files. The `sm` console-script entry point in `framework/hosting/pyproject.toml` continues to point at `simple_module_hosting.cli:main` — `__init__.py` exposes `main` from the new package. +- `cli.py` becomes `cli/__init__.py` plus four new files. The `smpy` console-script entry point in `framework/hosting/pyproject.toml` continues to point at `simple_module_hosting.cli:main` — `__init__.py` exposes `main` from the new package. - No template changes that affect existing host scaffolds. The new `_optional/` tree is additive. - `create_app_project`'s positional/kwarg signature is unchanged; only the new `selected=` kwarg is added. diff --git a/docs/superpowers/specs/2026-04-26-standalone-cli-package-design.md b/docs/superpowers/specs/2026-04-26-standalone-cli-package-design.md index bf0f8c3a..feb22bfe 100644 --- a/docs/superpowers/specs/2026-04-26-standalone-cli-package-design.md +++ b/docs/superpowers/specs/2026-04-26-standalone-cli-package-design.md @@ -5,14 +5,14 @@ ## Problem -`sm new` is currently shipped inside `simple_module_hosting`, which means `pip install simple-module-hosting` (or anything on top of it like `pipx install`) drags in FastAPI, Starlette, Inertia, Uvicorn, SQLModel, and the rest of the runtime — even when the user only wants to scaffold a new project. There is also no single CLI surface: hosting registers `sm`, `simple_module_hosting`'s helpers (`gen-pages`, `sync-js-deps`) are crammed into the same binary, and individual modules register their own `sm-users`, `sm-settings`, … console scripts. A new user who runs `pip install simple-module` should get a small, runnable scaffolder; a developer working inside a project should get one consolidated `sm` whose subcommand surface grows as plugins are installed. +`smpy new` is currently shipped inside `simple_module_hosting`, which means `pip install simple-module-hosting` (or anything on top of it like `pipx install`) drags in FastAPI, Starlette, Inertia, Uvicorn, SQLModel, and the rest of the runtime — even when the user only wants to scaffold a new project. There is also no single CLI surface: hosting registers `smpy`, `simple_module_hosting`'s helpers (`gen-pages`, `sync-js-deps`) are crammed into the same binary, and individual modules register their own `sm-users`, `sm-settings`, … console scripts. A new user who runs `pip install simple-module` should get a small, runnable scaffolder; a developer working inside a project should get one consolidated `smpy` whose subcommand surface grows as plugins are installed. ## Goals - A new PyPI distribution `simple-module` whose only runtime dependencies are `typer` and `tomlkit`. No framework-runtime deps. -- A single `sm` console script. All today's `sm-*` scripts (`sm-host`, `sm-users`, `sm-settings`) go away. -- Built-in (always-available) commands: `sm new`, `sm create-host`, `sm create-module`. -- Plugin commands provided via Python entry points: `simple_module_hosting` contributes `sm host gen-pages` and `sm host sync-js-deps`; the `users` and `settings` modules contribute `sm users …` and `sm settings …`. +- A single `smpy` console script. All today's `sm-*` scripts (`sm-host`, `sm-users`, `sm-settings`) go away. +- Built-in (always-available) commands: `smpy new`, `smpy create-host`, `smpy create-module`. +- Plugin commands provided via Python entry points: `simple_module_hosting` contributes `smpy host gen-pages` and `smpy host sync-js-deps`; the `users` and `settings` modules contribute `smpy users …` and `smpy settings …`. - `pip install simple-module` works on a machine with no other framework packages installed and gives the user a working scaffolder. ## Non-goals @@ -40,7 +40,7 @@ framework/cli/ ← NEW workspace member ├── catalog.py ModuleEntry, CATALOG, PRESETS, expand_deps ├── wizard.py run_wizard ├── recipes.py Recipe protocol, BackgroundTasksRecipe, RECIPES - ├── new.py `sm new` Typer command + ├── new.py `smpy new` Typer command ├── plugins.py entry-point discovery + mounting ├── cli.py root Typer app, mounts plugins, exposes `main` └── templates/ package data @@ -55,7 +55,7 @@ framework/cli/ ← NEW workspace member [project] name = "simple-module" version = "0.0.1" -description = "Standalone scaffolder for the SimpleModule framework — `sm new`, `sm create-module`, plugin host." +description = "Standalone scaffolder for the SimpleModule framework — `smpy new`, `smpy create-module`, plugin host." readme = "README.md" license = "MIT" requires-python = ">=3.12" @@ -66,7 +66,7 @@ dependencies = [ ] [project.scripts] -sm = "simple_module.cli:main" +smpy = "simple_module.cli:main" simple-module = "simple_module.cli:main" [build-system] @@ -97,18 +97,18 @@ users = "users.cli:app" settings = "settings.cli:app" ``` -At startup, `simple_module.plugins.discover()` calls `importlib.metadata.entry_points(group="simple_module.cli_plugins")`, loads each entry, and mounts it on the root app via `root.add_typer(plugin_app, name=entry.name)`. A failed entry-point load (broken import, missing attribute) prints a single warning and is skipped — `sm` itself keeps working. Discovery is unconditional but cheap; loading is eager so `--help` shows the full surface. +At startup, `simple_module.plugins.discover()` calls `importlib.metadata.entry_points(group="simple_module.cli_plugins")`, loads each entry, and mounts it on the root app via `root.add_typer(plugin_app, name=entry.name)`. A failed entry-point load (broken import, missing attribute) prints a single warning and is skipped — `smpy` itself keeps working. Discovery is unconditional but cheap; loading is eager so `--help` shows the full surface. Resulting CLI surface: ``` -sm new … (built-in) -sm create-host … (built-in) -sm create-module … (built-in) -sm host gen-pages … (when simple_module_hosting installed) -sm host sync-js-deps … (when simple_module_hosting installed) -sm users create-admin … (when users module installed) -sm settings import-from-env … (when settings module installed) +smpy new … (built-in) +smpy create-host … (built-in) +smpy create-module … (built-in) +smpy host gen-pages … (when simple_module_hosting installed) +smpy host sync-js-deps … (when simple_module_hosting installed) +smpy users create-admin … (when users module installed) +smpy settings import-from-env … (when settings module installed) ``` ### File moves @@ -169,7 +169,7 @@ Note: the `simple-module` console-script alias is dropped from `simple_module_ho - Root `pyproject.toml`: add `framework/cli` to workspace members. - `[tool.uv.sources]`: add `simple-module = { workspace = true }` so in-tree dev resolves to the local copy. - `host/pyproject.toml`: add `simple-module==0.0.1` as a dependency. (Hosting and module packages do **not** depend on `simple-module`.) -- `Makefile`: rename `sm gen-pages` → `sm host gen-pages` and `sm sync-js-deps` → `sm host sync-js-deps` in the `gen-pages` and `sync-module-deps` targets. The `make new-module` target keeps invoking `sm create-module`. +- `Makefile`: rename `smpy gen-pages` → `smpy host gen-pages` and `smpy sync-js-deps` → `smpy host sync-js-deps` in the `gen-pages` and `sync-module-deps` targets. The `make new-module` target keeps invoking `smpy create-module`. - Existing CI (`.github/workflows/pr.yml`) needs no change — `make lint` and `make test` will pick up the new workspace member automatically. - Release matrix (`.github/workflows/release.yml`): one new entry for the `simple-module` package alongside the existing 14. @@ -183,9 +183,9 @@ Note: the `simple-module` console-script alias is dropped from `simple_module_ho ### Failure modes -- **A plugin's entry point references a missing module or non-Typer object.** Log a warning (`sm` keeps working with whatever else is installed). Test covers this. +- **A plugin's entry point references a missing module or non-Typer object.** Log a warning (`smpy` keeps working with whatever else is installed). Test covers this. - **Two plugins claim the same subgroup name** (e.g. two installs both register `host`). `add_typer` raises; we catch the second registration and warn, keeping the first. Test covers this. -- **A user has `simple-module` installed but no plugins.** Built-in commands work; `sm --help` shows only `new` / `create-host` / `create-module`. No errors. +- **A user has `simple-module` installed but no plugins.** Built-in commands work; `smpy --help` shows only `new` / `create-host` / `create-module`. No errors. - **Templates package-data shipping.** The `_optional/` directory tree is copied as `package-data` under the wheel; verified by `framework/cli/tests/test_cli_recipes.py` reading the package-data path at runtime (same pattern that works today). ### Backward compat @@ -194,7 +194,7 @@ This is a pre-`0.0.1` framework with no external consumers. There are no shims o - `simple_module_hosting.cli` package, `simple_module_hosting.scaffolding`, and `simple_module_hosting.app_project` are **deleted**, not re-exported. - All in-tree callers are updated mechanically: tests, the `Makefile`, `host/pyproject.toml`, the per-module `pyproject.toml` files. -- The `sm-users` and `sm-settings` console scripts are deleted. Anyone running `sm-users create-admin` switches to `sm users create-admin`. +- The `sm-users` and `sm-settings` console scripts are deleted. Anyone running `sm-users create-admin` switches to `smpy users create-admin`. ## Open questions diff --git a/framework/cli/README.md b/framework/cli/README.md index fdac1d5e..3147002d 100644 --- a/framework/cli/README.md +++ b/framework/cli/README.md @@ -9,7 +9,7 @@ pip install simple_module_cli # or, to keep the CLI in its own venv: pipx install simple_module_cli # or, to run it without installing: -uvx --from simple_module_cli sm new my-app +uvx --from simple_module_cli smpy new my-app ``` The package depends only on `typer` and `tomlkit` — installing it does **not** pull in FastAPI, SQLModel, or any other framework runtime. @@ -17,21 +17,21 @@ The package depends only on `typer` and `tomlkit` — installing it does **not** ## Usage ```bash -sm new my-app # interactive wizard -sm new my-app --yes --preset full # all built-in modules + background jobs -sm create-module my_feature # scaffold a publishable module package -sm create-host bare-host # scaffold a bare host (no opinionated wiring) +smpy new my-app # interactive wizard +smpy new my-app --yes --preset full # all built-in modules + background jobs +smpy create-module my_feature # scaffold a publishable module package +smpy create-host bare-host # scaffold a bare host (no opinionated wiring) ``` -Built-in commands: `sm new`, `sm create-host`, `sm create-module`. +Built-in commands: `smpy new`, `smpy create-host`, `smpy create-module`. When other framework packages are installed, they contribute additional subcommands via the `simple_module_cli.cli_plugins` entry-point group: | Package | Commands | |---|---| -| `simple_module_hosting` | `sm host gen-pages`, `sm host sync-js-deps` | -| `simple_module_users` | `sm users create-admin` | -| `simple_module_settings` | `sm settings import-from-env` | +| `simple_module_hosting` | `smpy host gen-pages`, `smpy host sync-js-deps` | +| `simple_module_users` | `smpy users create-admin` | +| `simple_module_settings` | `smpy settings import-from-env` | ## License diff --git a/framework/cli/pyproject.toml b/framework/cli/pyproject.toml index 3a75db50..2c1c5513 100644 --- a/framework/cli/pyproject.toml +++ b/framework/cli/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "simple_module_cli" version = "0.0.11" -description = "Standalone scaffolder for the SimpleModule framework — `sm new`, `sm create-module`, plugin host." +description = "Standalone scaffolder for the SimpleModule framework — `smpy new`, `smpy create-module`, plugin host." readme = "README.md" license = "MIT" license-files = ["LICENSE"] @@ -25,7 +25,7 @@ dependencies = [ ] [project.scripts] -sm = "simple_module_cli.cli:main" +smpy = "simple_module_cli.cli:main" simple-module = "simple_module_cli.cli:main" [project.urls] diff --git a/framework/cli/simple_module_cli/app_project.py b/framework/cli/simple_module_cli/app_project.py index 20815097..0ce9fe22 100644 --- a/framework/cli/simple_module_cli/app_project.py +++ b/framework/cli/simple_module_cli/app_project.py @@ -195,7 +195,7 @@ def _seed_static_dist_placeholder(static_dist: Path) -> None: def _pin_sample_module_deps(sample_dest: Path) -> None: """Replace the module template's future-API range pins with exact pins. - The shared ``sm create-module`` template ships ``>=1.0,<2.0`` against the + The shared ``smpy create-module`` template ships ``>=1.0,<2.0`` against the framework's eventual stable line, but the workspace-bundled sample has to resolve against whatever the framework version actually is today (``==X`` in pre-1.0). Without rewriting, ``uv sync`` can't satisfy the workspace. diff --git a/framework/cli/simple_module_cli/cli.py b/framework/cli/simple_module_cli/cli.py index 633a68b1..31e01800 100644 --- a/framework/cli/simple_module_cli/cli.py +++ b/framework/cli/simple_module_cli/cli.py @@ -1,13 +1,13 @@ -"""Root `sm` Typer app — scaffolders + plugin mount. +"""Root `smpy` Typer app — scaffolders + plugin mount. Built-in commands: - sm new - sm create-host - sm create-module - sm skills add / list / update + smpy new + smpy create-host + smpy create-module + smpy skills add / list / update Plugins discovered via the ``simple_module_cli.cli_plugins`` entry-point -group are mounted as named subgroups (e.g. ``sm host gen-pages``). +group are mounted as named subgroups (e.g. ``smpy host gen-pages``). """ from __future__ import annotations @@ -101,7 +101,7 @@ def create_module( def main() -> None: - """Entry point for the `sm` console script.""" + """Entry point for the `smpy` console script.""" app() diff --git a/framework/cli/simple_module_cli/new.py b/framework/cli/simple_module_cli/new.py index 1bfdd8dc..16b99195 100644 --- a/framework/cli/simple_module_cli/new.py +++ b/framework/cli/simple_module_cli/new.py @@ -1,4 +1,4 @@ -"""``sm new`` Typer command — flag-driven or interactive scaffolder.""" +"""``smpy new`` Typer command — flag-driven or interactive scaffolder.""" from __future__ import annotations diff --git a/framework/cli/simple_module_cli/package_update.py b/framework/cli/simple_module_cli/package_update.py index e5130799..9b63d58e 100644 --- a/framework/cli/simple_module_cli/package_update.py +++ b/framework/cli/simple_module_cli/package_update.py @@ -1,4 +1,4 @@ -"""``sm package-update`` — bump simple_module_* deps to latest PyPI versions. +"""``smpy package-update`` — bump simple_module_* deps to latest PyPI versions. Walks the project's ``pyproject.toml`` (and any ``[tool.uv.workspace]`` members), finds every dependency whose distribution name starts with ``simple_module_`` / diff --git a/framework/cli/simple_module_cli/plugins.py b/framework/cli/simple_module_cli/plugins.py index be340876..9dd08aec 100644 --- a/framework/cli/simple_module_cli/plugins.py +++ b/framework/cli/simple_module_cli/plugins.py @@ -1,11 +1,11 @@ -"""Plugin discovery for ``sm`` via the ``simple_module_cli.cli_plugins`` group. +"""Plugin discovery for ``smpy`` via the ``simple_module_cli.cli_plugins`` group. Each entry-point's value (``module:attr``) must resolve to a :class:`typer.Typer` instance. The entry-point name becomes the -subcommand namespace under ``sm`` (e.g. ``sm host gen-pages``). +subcommand namespace under ``smpy`` (e.g. ``smpy host gen-pages``). Failed loads (broken import, wrong type) print one line to stderr and -are skipped — ``sm`` keeps working with whatever else loads. +are skipped — ``smpy`` keeps working with whatever else loads. """ from __future__ import annotations diff --git a/framework/cli/simple_module_cli/recipes.py b/framework/cli/simple_module_cli/recipes.py index c3400736..6af9825d 100644 --- a/framework/cli/simple_module_cli/recipes.py +++ b/framework/cli/simple_module_cli/recipes.py @@ -1,6 +1,6 @@ """Per-module post-scaffold recipes. -A recipe is invoked by ``sm new`` after the base host scaffold lands. It +A recipe is invoked by ``smpy new`` after the base host scaffold lands. It performs module-specific actions (write helper scripts, append Make targets, drop a docker-compose stack). The framework layer is kept free of devex concerns — recipes know about Makefiles and compose, framework @@ -68,7 +68,7 @@ def apply(self, target: Path, ctx: ScaffoldCtx) -> None: if path.exists(): raise FileExistsError( f"{path} already exists — refusing to clobber. " - "Remove the file or run `sm new` against an empty directory." + "Remove the file or run `smpy new` against an empty directory." ) run_worker_dest.parent.mkdir(parents=True, exist_ok=True) diff --git a/framework/cli/simple_module_cli/skills_cmd.py b/framework/cli/simple_module_cli/skills_cmd.py index 4e1b5323..79138035 100644 --- a/framework/cli/simple_module_cli/skills_cmd.py +++ b/framework/cli/simple_module_cli/skills_cmd.py @@ -1,4 +1,4 @@ -"""``sm skills`` — install or update agent skill packs in a target project. +"""``smpy skills`` — install or update agent skill packs in a target project. Bundles the SKILL.md packs shipped under ``simple_module_cli/skills/`` and materialises them into a project directory (default ``.claude/skills``) so any @@ -7,9 +7,9 @@ Three subcommands: -* ``sm skills list`` — show every bundled skill and its description. -* ``sm skills add`` — copy (or symlink) skills into the destination. -* ``sm skills update`` — re-copy skills that are already installed at the +* ``smpy skills list`` — show every bundled skill and its description. +* ``smpy skills add`` — copy (or symlink) skills into the destination. +* ``smpy skills update`` — re-copy skills that are already installed at the destination, overwriting them. """ diff --git a/framework/cli/simple_module_cli/templates/host/client_app/pages.ts b/framework/cli/simple_module_cli/templates/host/client_app/pages.ts index 7af0622b..cfbf3262 100644 --- a/framework/cli/simple_module_cli/templates/host/client_app/pages.ts +++ b/framework/cli/simple_module_cli/templates/host/client_app/pages.ts @@ -2,7 +2,7 @@ * Inertia page resolver. * * Module pages are discovered via a generated file (modules.generated.ts) - * emitted by the Python host at boot, or manually via `sm gen-pages`. Each + * emitted by the Python host at boot, or manually via `smpy gen-pages`. Each * installed module contributes an import.meta.glob() call with an absolute * path, so pages shipped inside pip-installed module wheels resolve. * diff --git a/framework/cli/simple_module_cli/templates/host/main.py b/framework/cli/simple_module_cli/templates/host/main.py index eaa91836..f5384831 100644 --- a/framework/cli/simple_module_cli/templates/host/main.py +++ b/framework/cli/simple_module_cli/templates/host/main.py @@ -1,6 +1,6 @@ """Host application entry point. -This file was generated by `sm create-host`. Modules are discovered at boot +This file was generated by `smpy create-host`. Modules are discovered at boot via entry_points; add them to this host's pyproject.toml to install them. """ diff --git a/framework/cli/simple_module_cli/wizard.py b/framework/cli/simple_module_cli/wizard.py index a83edc12..bfe1fba1 100644 --- a/framework/cli/simple_module_cli/wizard.py +++ b/framework/cli/simple_module_cli/wizard.py @@ -1,4 +1,4 @@ -"""Interactive prompt sequence for ``sm new``.""" +"""Interactive prompt sequence for ``smpy new``.""" from __future__ import annotations diff --git a/framework/cli/tests/test_build_packaging.py b/framework/cli/tests/test_build_packaging.py index d0bd4e51..1d56c3df 100644 --- a/framework/cli/tests/test_build_packaging.py +++ b/framework/cli/tests/test_build_packaging.py @@ -40,7 +40,7 @@ def built_artifacts(tmp_path_factory) -> tuple[Path, Path]: def test_wheel_contains_bundled_skills(built_artifacts: tuple[Path, Path]) -> None: - """The wheel must ship the agent skill packs that ``sm skills`` depends on. + """The wheel must ship the agent skill packs that ``smpy skills`` depends on. Regression: ``[tool.hatch.build.targets.wheel.force-include]`` with ``../../skills`` made the wheel re-build from sdist crash with @@ -61,7 +61,7 @@ def test_wheel_contains_bundled_skills(built_artifacts: tuple[Path, Path]) -> No def test_wheel_contains_templates(built_artifacts: tuple[Path, Path]) -> None: - """``sm new`` reads from ``simple_module_cli/templates`` — must ship in the wheel.""" + """``smpy new`` reads from ``simple_module_cli/templates`` — must ship in the wheel.""" _, wheel = built_artifacts with zipfile.ZipFile(wheel) as zf: names = zf.namelist() diff --git a/framework/cli/tests/test_cli_new.py b/framework/cli/tests/test_cli_new.py index b51251b2..e067eeee 100644 --- a/framework/cli/tests/test_cli_new.py +++ b/framework/cli/tests/test_cli_new.py @@ -1,4 +1,4 @@ -"""Tests for the `sm new` / `simple-module new` CLI subcommand.""" +"""Tests for the `smpy new` / `simple-module new` CLI subcommand.""" from __future__ import annotations diff --git a/framework/cli/tests/test_cli_new_regressions.py b/framework/cli/tests/test_cli_new_regressions.py index cdebf268..03f6e77b 100644 --- a/framework/cli/tests/test_cli_new_regressions.py +++ b/framework/cli/tests/test_cli_new_regressions.py @@ -1,4 +1,4 @@ -"""Regression tests for ``sm new`` scaffold bugs filed against released wheels. +"""Regression tests for ``smpy new`` scaffold bugs filed against released wheels. Each test pins a specific issue's repro so the bug can't sneak back in without a CI failure pointing at the fix. diff --git a/framework/cli/tests/test_cli_package_update.py b/framework/cli/tests/test_cli_package_update.py index 56b226ab..019b552f 100644 --- a/framework/cli/tests/test_cli_package_update.py +++ b/framework/cli/tests/test_cli_package_update.py @@ -1,4 +1,4 @@ -"""Tests for `sm package-update`.""" +"""Tests for `smpy package-update`.""" from __future__ import annotations diff --git a/framework/cli/tests/test_cli_wizard.py b/framework/cli/tests/test_cli_wizard.py index 5da208fd..9bcc5735 100644 --- a/framework/cli/tests/test_cli_wizard.py +++ b/framework/cli/tests/test_cli_wizard.py @@ -1,4 +1,4 @@ -"""Tests for the `sm new` interactive wizard.""" +"""Tests for the `smpy new` interactive wizard.""" from __future__ import annotations diff --git a/framework/cli/tests/test_scaffolding_host.py b/framework/cli/tests/test_scaffolding_host.py index 60e35150..d76ecaeb 100644 --- a/framework/cli/tests/test_scaffolding_host.py +++ b/framework/cli/tests/test_scaffolding_host.py @@ -1,4 +1,4 @@ -"""Tests for the module-pages manifest and `sm create-host` scaffolding.""" +"""Tests for the module-pages manifest and `smpy create-host` scaffolding.""" from __future__ import annotations @@ -154,7 +154,7 @@ async def test_env_py_uses_shared_helper(self, tmp_path): assert "for mod in modules:" not in env_py async def test_cli_create_host_runs_end_to_end(self, tmp_path): - """The Click `sm create-host` command produces a working scaffold.""" + """The Click `smpy create-host` command produces a working scaffold.""" from simple_module_cli.cli import app from typer.testing import CliRunner diff --git a/framework/cli/tests/test_scaffolding_module.py b/framework/cli/tests/test_scaffolding_module.py index 4773e570..302aaae4 100644 --- a/framework/cli/tests/test_scaffolding_module.py +++ b/framework/cli/tests/test_scaffolding_module.py @@ -1,4 +1,4 @@ -"""Tests for `sm create-module` scaffolding: module package, CI, static bundling.""" +"""Tests for `smpy create-module` scaffolding: module package, CI, static bundling.""" from __future__ import annotations @@ -74,7 +74,7 @@ async def test_refuses_existing_non_empty_dir(self, tmp_path): create_module(dest, name="MyFeature") async def test_cli_create_module_runs_end_to_end(self, tmp_path): - """The Click `sm create-module` command produces a working scaffold.""" + """The Click `smpy create-module` command produces a working scaffold.""" from simple_module_cli.cli import app from typer.testing import CliRunner diff --git a/framework/cli/tests/test_skills_cmd.py b/framework/cli/tests/test_skills_cmd.py index 58659533..6c5386d5 100644 --- a/framework/cli/tests/test_skills_cmd.py +++ b/framework/cli/tests/test_skills_cmd.py @@ -1,4 +1,4 @@ -"""Tests for the ``sm skills`` subcommand group.""" +"""Tests for the ``smpy skills`` subcommand group.""" from __future__ import annotations diff --git a/framework/db/simple_module_db/migrations.py b/framework/db/simple_module_db/migrations.py index 9d15dec4..c9510e26 100644 --- a/framework/db/simple_module_db/migrations.py +++ b/framework/db/simple_module_db/migrations.py @@ -7,7 +7,7 @@ This abstraction decouples the host from the import mechanics and is the single place where a pip-installed module's ``.models`` submodule gets -loaded — so every host scaffolded by ``sm create-host`` behaves identically +loaded — so every host scaffolded by ``smpy create-host`` behaves identically whether the module was installed via workspace path, PyPI wheel, or ``pip install -e``. """ diff --git a/framework/hosting/README.md b/framework/hosting/README.md index 283ae541..5bc3f8e4 100644 --- a/framework/hosting/README.md +++ b/framework/hosting/README.md @@ -1,6 +1,6 @@ # simple_module_hosting -FastAPI + Inertia.js host runtime for the [simple_module](https://github.com/antosubash/simple_module_python) framework — builds the app, wires the middleware pipeline, and contributes the `sm host` plugin to the standalone `sm` CLI. +FastAPI + Inertia.js host runtime for the [simple_module](https://github.com/antosubash/simple_module_python) framework — builds the app, wires the middleware pipeline, and contributes the `smpy host` plugin to the standalone `smpy` CLI. ## Install @@ -11,7 +11,7 @@ pip install simple_module_hosting For a new project, most users run the generator instead (shipped by the standalone `simple_module_cli` distribution): ```bash -uvx --from simple_module_cli sm new my-app +uvx --from simple_module_cli smpy new my-app ``` ## What it provides @@ -19,7 +19,7 @@ uvx --from simple_module_cli sm new my-app - `create_app(settings)` — returns a fully-wired `FastAPI` instance with all discovered modules registered. - Middleware pipeline (execution order): CorrelationId → RequestLogging → SecurityHeaders → Session → `` → Tenant (opt-in) → Locale → InertiaLayoutData → app. - Inertia wiring — shared props (`auth`, `menus`, `i18n`), `InertiaDep`, page-route lookup. -- `sm host` plugin — `sm host gen-pages` regenerates the frontend pages manifest; `sm host sync-js-deps` installs JS deps declared by installed modules. The `sm` binary itself comes from `simple_module_cli`. +- `smpy host` plugin — `smpy host gen-pages` regenerates the frontend pages manifest; `smpy host sync-js-deps` installs JS deps declared by installed modules. The `smpy` binary itself comes from `simple_module_cli`. ## Usage @@ -40,8 +40,8 @@ if __name__ == "__main__": CLI (after also installing `simple_module_cli`): ```bash -sm host gen-pages # regenerate client_app/modules.generated.ts -sm host sync-js-deps # sync module JS deps into client_app/node_modules +smpy host gen-pages # regenerate client_app/modules.generated.ts +smpy host sync-js-deps # sync module JS deps into client_app/node_modules ``` ## Depends on diff --git a/framework/hosting/simple_module_hosting/__main__.py b/framework/hosting/simple_module_hosting/__main__.py index ee048ef7..058d66ac 100644 --- a/framework/hosting/simple_module_hosting/__main__.py +++ b/framework/hosting/simple_module_hosting/__main__.py @@ -3,7 +3,7 @@ Without this, ``python -m simple_module_hosting.host_cli`` would import the module without running the Typer app — silently no-op'ing commands like ``gen-pages``. Provides the same Typer ``app`` callable that the -``simple_module_cli.cli_plugins`` entry point exposes as ``sm host``. +``simple_module_cli.cli_plugins`` entry point exposes as ``smpy host``. """ from __future__ import annotations diff --git a/framework/hosting/simple_module_hosting/_inertia_setup.py b/framework/hosting/simple_module_hosting/_inertia_setup.py index 1084770b..bd3977eb 100644 --- a/framework/hosting/simple_module_hosting/_inertia_setup.py +++ b/framework/hosting/simple_module_hosting/_inertia_setup.py @@ -28,7 +28,7 @@ def setup_inertia( Two host layouts are supported: ``host/templates`` (the framework's own host package) and ``templates`` at the project root (what - ``sm new`` produces). The first one found wins so it can override + ``smpy new`` produces). The first one found wins so it can override module-contributed templates. """ from fastapi.templating import Jinja2Templates diff --git a/framework/hosting/simple_module_hosting/host_cli.py b/framework/hosting/simple_module_hosting/host_cli.py index e3f49c72..830c6b79 100644 --- a/framework/hosting/simple_module_hosting/host_cli.py +++ b/framework/hosting/simple_module_hosting/host_cli.py @@ -1,4 +1,4 @@ -"""``sm host`` plugin — project-time helpers exposed through the simple-module CLI. +"""``smpy host`` plugin — project-time helpers exposed through the simple-module CLI. Commands here need module discovery (``simple_module_core.discover_modules``) and the manifest helpers; they're not part of the standalone scaffolder. diff --git a/framework/hosting/simple_module_hosting/manifest.py b/framework/hosting/simple_module_hosting/manifest.py index f28bbdfe..14a221fb 100644 --- a/framework/hosting/simple_module_hosting/manifest.py +++ b/framework/hosting/simple_module_hosting/manifest.py @@ -8,7 +8,7 @@ * :func:`read_module_package_json` / :func:`collect_module_js_deps` locate the per-module ``package.json`` shipped inside wheels (or alongside the source for editable installs) and aggregate its ``dependencies`` block. - Used by the ``sm sync-js-deps`` CLI. + Used by the ``smpy sync-js-deps`` CLI. """ from __future__ import annotations @@ -26,7 +26,7 @@ _GENERATED_TS_HEADER = """\ // AUTO-GENERATED by simple_module_hosting.manifest — do not edit by hand. -// Regenerate with: sm gen-pages +// Regenerate with: smpy gen-pages // // Maps ModuleName -> record of page import paths. Paths below are // relative to this file (host/client_app/) so that pages shipped inside @@ -35,7 +35,7 @@ _GENERATED_CSS_HEADER = """\ /* AUTO-GENERATED by simple_module_hosting.manifest — do not edit by hand. - * Regenerate with: sm gen-pages + * Regenerate with: smpy gen-pages * * @source entries for module pages shipped inside pip-installed wheels. * In-repo modules are covered by the static @source glob in @@ -54,7 +54,7 @@ def repo_root_from_client_app(client_app_dir: Path) -> Path: Walks up looking for the nearest ``package.json`` that declares ``workspaces`` (the workspace root npm uses), falling back to any ``package.json``. This handles both the framework repo's - ``host/client_app/`` layout and the flat ``sm new`` scaffold's + ``host/client_app/`` layout and the flat ``smpy new`` scaffold's ``client_app/`` layout (where the parent IS the workspace root). """ here = client_app_dir.resolve() diff --git a/host/client_app/pages/Landing.tsx b/host/client_app/pages/Landing.tsx index 245e46f4..fc5a5938 100644 --- a/host/client_app/pages/Landing.tsx +++ b/host/client_app/pages/Landing.tsx @@ -16,16 +16,16 @@ import { } from 'lucide-react'; const QUICKSTART = `# 1. install python and js deps -$ sm install +$ make install # 2. copy env template $ cp .env.example .env # 3. run migrations -$ sm migrate +$ make migrate # 4. start API + Vite in parallel -$ sm dev +$ make dev `; function Landing() { @@ -108,7 +108,7 @@ function Landing() {

$ - uvx --from simple_module_cli sm new my-app + uvx --from simple_module_cli smpy new my-app
diff --git a/host/client_app/vite.config.ts b/host/client_app/vite.config.ts index c8900d89..3f0db414 100644 --- a/host/client_app/vite.config.ts +++ b/host/client_app/vite.config.ts @@ -21,7 +21,7 @@ let manifest: Record = {}; try { manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')); } catch { - // Manifest absent (sm gen-pages hasn't run yet) — proceed with empty set. + // Manifest absent (smpy gen-pages hasn't run yet) — proceed with empty set. } for (const pagesDir of Object.values(manifest)) { moduleFsAllow.push(path.dirname(pagesDir)); diff --git a/host/locales/en.json b/host/locales/en.json index e206fb8c..4bb92f46 100644 --- a/host/locales/en.json +++ b/host/locales/en.json @@ -16,7 +16,7 @@ "schema_description": "PostgreSQL → schema per module. SQLite → prefixed tables. Each module owns its data.", "inertia_title": "Inertia + React", "inertia_description": "Browse / Create / Edit pages ship inside each module. Vite HMR for the frontend, async FastAPI for the backend.", - "diagnostics_title": "sm doctor", + "diagnostics_title": "make doctor", "diagnostics_description": "Static analyzer catches orphan pages, framework coupling, and migration drift before boot.", "devtools_title": "Async SQLModel", "devtools_description": "SQLAlchemy async + Pydantic + Alembic. Generate migrations per module." diff --git a/host/locales/es.json b/host/locales/es.json index 72b1500d..b4f600c7 100644 --- a/host/locales/es.json +++ b/host/locales/es.json @@ -16,7 +16,7 @@ "schema_description": "PostgreSQL → un esquema por módulo. SQLite → tablas con prefijo. Cada módulo posee sus datos.", "inertia_title": "Inertia + React", "inertia_description": "Páginas Browse / Create / Edit incluidas en cada módulo. Vite HMR para el frontend, FastAPI async para el backend.", - "diagnostics_title": "sm doctor", + "diagnostics_title": "make doctor", "diagnostics_description": "El analizador estático detecta páginas huérfanas, acoplamiento del framework y desviaciones de migración antes del arranque.", "devtools_title": "SQLModel async", "devtools_description": "SQLAlchemy async + Pydantic + Alembic. Genera migraciones por módulo." diff --git a/modules/dashboard/dashboard/endpoints/views.py b/modules/dashboard/dashboard/endpoints/views.py index fe689a84..0adbdfb6 100644 --- a/modules/dashboard/dashboard/endpoints/views.py +++ b/modules/dashboard/dashboard/endpoints/views.py @@ -45,7 +45,7 @@ async def doctor( inertia: InertiaDep, db: AsyncSession = Depends(get_db), ) -> InertiaResponse: - """`sm doctor` mirror — static checks, modules, dev server, env.""" + """`make doctor` mirror — static checks, modules, dev server, env.""" stats = await fetch_dashboard_stats(db, request.app) return await inertia.render( _PAGE_DOCTOR, diff --git a/modules/dashboard/dashboard/pages/Doctor.tsx b/modules/dashboard/dashboard/pages/Doctor.tsx index fb8fc6fd..a770a140 100644 --- a/modules/dashboard/dashboard/pages/Doctor.tsx +++ b/modules/dashboard/dashboard/pages/Doctor.tsx @@ -79,14 +79,14 @@ function Doctor() { return ( } @@ -234,12 +234,14 @@ function Doctor() { Run a command
- {['sm new module orders', 'sm migrate', 'sm doctor', 'sm dev'].map((c) => ( -
- $ - {c} -
- ))} + {['make new-module name=orders', 'make migrate', 'make doctor', 'make dev'].map( + (c) => ( +
+ $ + {c} +
+ ), + )}
diff --git a/modules/settings/settings/cli.py b/modules/settings/settings/cli.py index 217d11d0..d9dd223d 100644 --- a/modules/settings/settings/cli.py +++ b/modules/settings/settings/cli.py @@ -1,4 +1,4 @@ -"""``sm settings`` plugin — currently only ``import-from-env``. +"""``smpy settings`` plugin — currently only ``import-from-env``. One-shot migration: walks every registered module's BaseSettings and writes a SYSTEM-scoped override for each ``SM__`` env diff --git a/modules/settings/settings/env_vars.py b/modules/settings/settings/env_vars.py index 908a3031..71c22d25 100644 --- a/modules/settings/settings/env_vars.py +++ b/modules/settings/settings/env_vars.py @@ -1,7 +1,7 @@ """Map package names to their historical ``SM_*`` env-var prefix. Used by ``_module_settings`` to label fields in the admin UI and by the -``sm-settings import-from-env`` CLI to locate legacy env values. Most +``smpy settings import-from-env`` CLI to locate legacy env values. Most packages follow ``SM__``; the exceptions are listed below. """ diff --git a/modules/settings/tests/test_cli_import.py b/modules/settings/tests/test_cli_import.py index bf7e792e..2fea1026 100644 --- a/modules/settings/tests/test_cli_import.py +++ b/modules/settings/tests/test_cli_import.py @@ -1,4 +1,4 @@ -"""Tests for the ``sm-settings import-from-env`` CLI entry point. +"""Tests for the ``smpy settings import-from-env`` CLI entry point. Exercises ``import_from_env_impl`` directly so we avoid spawning a real process but still cover the env → DB override path the CLI wraps. diff --git a/modules/users/README.md b/modules/users/README.md index f3548fa1..04fad9b3 100644 --- a/modules/users/README.md +++ b/modules/users/README.md @@ -8,7 +8,7 @@ Email+password user management for [simple_module](https://github.com/antosubash pip install simple_module_users ``` -Pre-wired into any app scaffolded with `simple-module new`. +Pre-wired into any app scaffolded with `smpy new`. ## What it provides @@ -16,7 +16,7 @@ Pre-wired into any app scaffolded with `simple-module new`. - Admin invite flow — admin enters an email, recipient clicks a link, sets a password, is logged in. - Public signup toggle (`SM_USERS_ALLOW_SIGNUP`, default `false`). - Bootstrap admin via env vars (`SM_USERS_BOOTSTRAP_EMAIL` + `SM_USERS_BOOTSTRAP_PASSWORD`) — idempotent, only creates if the users table is empty. -- `sm-users create-admin` CLI for ad-hoc admin creation. +- `smpy users create-admin` CLI for ad-hoc admin creation. - Inertia pages for login/register/invite-accept/admin-invite. - Console mailer (logs to stdout) or SMTP mailer (`SM_USERS_MAILER=smtp`). @@ -25,7 +25,7 @@ Pre-wired into any app scaffolded with `simple-module new`. CLI: ```bash -uv run sm-users create-admin --email admin@example.com --password 'change-me' +uv run smpy users create-admin --email admin@example.com --password 'change-me' ``` Bootstrap-on-boot (`.env`): diff --git a/modules/users/tests/test_cli.py b/modules/users/tests/test_cli.py index 2e8af4ef..6a34a26e 100644 --- a/modules/users/tests/test_cli.py +++ b/modules/users/tests/test_cli.py @@ -1,4 +1,4 @@ -"""Tests for the sm-users CLI (users.cli). +"""Tests for the smpy users CLI (users.cli). Strategy: monkeypatch ``users.bootstrap.create_admin`` so tests do not need a real database. This avoids the complexity of standing up a schema-stamped @@ -161,7 +161,7 @@ def test_create_admin_missing_email() -> None: def test_app_help() -> None: - """``sm-users --help`` shows the top-level help text and lists create-admin.""" + """``smpy users --help`` shows the top-level help text and lists create-admin.""" result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "create-admin" in result.output diff --git a/modules/users/users/cli.py b/modules/users/users/cli.py index 1c176e83..385643a9 100644 --- a/modules/users/users/cli.py +++ b/modules/users/users/cli.py @@ -1,9 +1,10 @@ """Command-line entry points for the users module. -Exposed via ``sm-users`` (see pyproject.toml [project.scripts]). +Exposed via ``smpy users`` (see pyproject.toml +[project.entry-points.simple_module_cli.cli_plugins]). - sm-users create-admin --email a@b.test --password sekret [--full-name Me] - sm-users create-admin --email a@b.test --password new --force + smpy users create-admin --email a@b.test --password sekret [--full-name Me] + smpy users create-admin --email a@b.test --password new --force """ from __future__ import annotations diff --git a/modules/users/users/settings.py b/modules/users/users/settings.py index 21332c49..bc644d27 100644 --- a/modules/users/users/settings.py +++ b/modules/users/users/settings.py @@ -31,7 +31,7 @@ class UsersSettings(BaseSettings): require_verification: bool = True # Where the login page sends a successful sign-in. Sites without the - # bundled ``dashboard`` module (``sm new --preset minimal``) override + # bundled ``dashboard`` module (``smpy new --preset minimal``) override # this to wherever their post-login landing lives. login_redirect_url: str = "/dashboard/" diff --git a/skills/README.md b/skills/README.md index ff37f612..7d8be972 100644 --- a/skills/README.md +++ b/skills/README.md @@ -6,22 +6,22 @@ Agent skills for working in a [simple_module_python](https://github.com/antosuba There are two install paths — pick whichever fits your project. -### Option A — `sm skills` (recommended for `simple_module_cli` users) +### Option A — `smpy skills` (recommended for `simple_module_cli` users) -Every project produced by `sm new` already depends on `simple_module_cli`, which ships these skills inside its wheel. From the project root: +Every project produced by `smpy new` already depends on `simple_module_cli`, which ships these skills inside its wheel. From the project root: ```bash -sm skills list # see what's available -sm skills add # install ALL skills into ./.claude/skills/ -sm skills add simple-module-creating # install just one -sm skills add -g # install into ~/.claude/skills (machine-wide) -sm skills add --dest agents/skills # custom target dir -sm skills add --symlink # symlink to the bundled source (good for skill devs) -sm skills update # re-pull updates for skills already installed -sm skills update simple-module-doctor # explicit re-pull (force-overwrites) +smpy skills list # see what's available +smpy skills add # install ALL skills into ./.claude/skills/ +smpy skills add simple-module-creating # install just one +smpy skills add -g # install into ~/.claude/skills (machine-wide) +smpy skills add --dest agents/skills # custom target dir +smpy skills add --symlink # symlink to the bundled source (good for skill devs) +smpy skills update # re-pull updates for skills already installed +smpy skills update simple-module-doctor # explicit re-pull (force-overwrites) ``` -`sm skills` resolves the bundled set against whatever version of `simple_module_cli` is installed, so upgrading the CLI ships skill updates the next time you run `sm skills update`. +`smpy skills` resolves the bundled set against whatever version of `simple_module_cli` is installed, so upgrading the CLI ships skill updates the next time you run `smpy skills update`. ### Option B — `npx skills` (no Python install needed) @@ -38,7 +38,7 @@ The CLI is [vercel-labs/skills](https://github.com/vercel-labs/skills); see its | Skill | Use when | |---|---| -| [simple-module-cli](./simple-module-cli/SKILL.md) | Invoking the `sm` CLI — `sm new`, `sm create-host`, `sm create-module`, `sm host gen-pages`, `sm users create-admin`, etc. | +| [simple-module-cli](./simple-module-cli/SKILL.md) | Invoking the `smpy` CLI — `smpy new`, `smpy create-host`, `smpy create-module`, `smpy host gen-pages`, `smpy users create-admin`, etc. | | [simple-module-creating](./simple-module-creating/SKILL.md) | Adding a new feature package — scaffolding, entry-point, `ModuleMeta` | | [simple-module-conventions](./simple-module-conventions/SKILL.md) | Writing or reviewing module code — the invariant list (SQLModel everywhere, settings layout, framework→plugin direction, etc.) | | [simple-module-database](./simple-module-database/SKILL.md) | Adding SQLModel tables, picking a mixin, or debugging session/transaction behavior | diff --git a/skills/simple-module-cli/SKILL.md b/skills/simple-module-cli/SKILL.md index d3e8e107..e5be5343 100644 --- a/skills/simple-module-cli/SKILL.md +++ b/skills/simple-module-cli/SKILL.md @@ -1,41 +1,41 @@ --- name: simple-module-cli -description: Use when invoking the `sm` CLI for a simple_module_python project — starting a new app, scaffolding a host or a publishable module, regenerating the Inertia page manifest, importing settings overrides from env, creating an admin user, or installing the bundled agent skills. Triggers on "sm new", "sm create-host", "sm create-module", "sm host gen-pages", "sm users create-admin", "sm skills add", or any unfamiliar `sm` subcommand. +description: Use when invoking the `smpy` CLI for a simple_module_python project — starting a new app, scaffolding a host or a publishable module, regenerating the Inertia page manifest, importing settings overrides from env, creating an admin user, or installing the bundled agent skills. Triggers on "smpy new", "smpy create-host", "smpy create-module", "smpy host gen-pages", "smpy users create-admin", "smpy skills add", or any unfamiliar `smpy` subcommand. --- -# simple_module_python: the `sm` CLI +# simple_module_python: the `smpy` CLI -The `sm` command is provided by `simple_module_cli` (installed as a dep of `simple_module_hosting`). It groups four kinds of operations: scaffolding new things, project-time helpers for the host, admin shortcuts for the bundled modules, and installing the bundled agent skills. +The `smpy` command is provided by `simple_module_cli` (installed as a dep of `simple_module_hosting`). It groups four kinds of operations: scaffolding new things, project-time helpers for the host, admin shortcuts for the bundled modules, and installing the bundled agent skills. ## Top-level commands | Command | When to use | |---|---| -| `sm new ` | Greenfield: scaffold a complete app (host + selected modules) in one shot, with an interactive wizard for DB / tenancy / module preset | -| `sm create-host ` | You want just a bare host project; you'll add modules later by `pip install`-ing them | -| `sm create-module ` | You're authoring a publishable module package (separate repo, distributed via PyPI) | -| `sm skills …` | Install / update the bundled agent-skill packs into a project (`add`, `list`, `update`) | -| `sm host …` | Project-time helpers run from inside a host directory (page manifest, JS dep sync) | -| `sm settings …` | Settings-module admin — currently `import-from-env` | -| `sm users …` | Users-module admin — currently `create-admin` | +| `smpy new ` | Greenfield: scaffold a complete app (host + selected modules) in one shot, with an interactive wizard for DB / tenancy / module preset | +| `smpy create-host ` | You want just a bare host project; you'll add modules later by `pip install`-ing them | +| `smpy create-module ` | You're authoring a publishable module package (separate repo, distributed via PyPI) | +| `smpy skills …` | Install / update the bundled agent-skill packs into a project (`add`, `list`, `update`) | +| `smpy host …` | Project-time helpers run from inside a host directory (page manifest, JS dep sync) | +| `smpy settings …` | Settings-module admin — currently `import-from-env` | +| `smpy users …` | Users-module admin — currently `create-admin` | -## `sm new ` — the wizard +## `smpy new ` — the wizard The fastest way from zero to a working app. It calls `create-host` under the hood, then installs and wires up the modules you pick. ```bash # Interactive (asks for DB, tenancy, preset, module list) -sm new MyApp +smpy new MyApp # Non-interactive: take all defaults (sqlite, no tenancy, standard preset) -sm new MyApp --yes +smpy new MyApp --yes # Pick a preset and add extras -sm new MyApp --preset full --tenancy --db postgres -sm new MyApp --preset minimal --with background_tasks,file_storage --yes +smpy new MyApp --preset full --tenancy --db postgres +smpy new MyApp --preset minimal --with background_tasks,file_storage --yes # Scaffold only — skip uv sync / npm install / alembic upgrade head -sm new MyApp --no-install +smpy new MyApp --no-install ``` **Module presets:** @@ -61,23 +61,23 @@ sm new MyApp --no-install | `--yes / -y` | off | Skip prompts; accept defaults | | `--no-install` | off | Skip `uv sync` / `npm install` / `alembic upgrade head` | -## `sm create-host ` — bare host +## `smpy create-host ` — bare host ```bash -sm create-host MyApp # empty host, no modules declared -sm create-host MyApp --with Auth,Products # declare module deps in pyproject.toml -sm create-host MyApp --dest ./apps/myapp # custom destination +smpy create-host MyApp # empty host, no modules declared +smpy create-host MyApp --with Auth,Products # declare module deps in pyproject.toml +smpy create-host MyApp --dest ./apps/myapp # custom destination ``` -`--with` takes **PascalCase module names** (matching `ModuleMeta.name`), not catalog keys. Use this when you want to drive the build yourself rather than via `sm new`'s wizard. +`--with` takes **PascalCase module names** (matching `ModuleMeta.name`), not catalog keys. Use this when you want to drive the build yourself rather than via `smpy new`'s wizard. -## `sm create-module ` — module package +## `smpy create-module ` — module package For module authors publishing to PyPI. Scaffolds a standalone repo containing one module. ```bash -sm create-module orders # writes ./simple_module_orders/ -sm create-module orders --dest ./packages/orders +smpy create-module orders # writes ./simple_module_orders/ +smpy create-module orders --dest ./packages/orders ``` The result is a complete package: `pyproject.toml` with the entry point declared, `module.py` with `ModuleBase`/`ModuleMeta` skeleton, `models.py`, `contracts/`, `endpoints/{api,views}.py`, `pages/`, `locales/en.json`, plus a tests directory wired up with `simple_module_test` fixtures. @@ -86,63 +86,63 @@ The result is a complete package: `pyproject.toml` with the entry point declared For the post-scaffold steps (entry point, Inertia namespace, etc.) see **simple-module-creating**. -## `sm skills` — install the bundled agent skills +## `smpy skills` — install the bundled agent skills `simple_module_cli` ships a set of [SKILL.md](https://agentskills.io/specification) packs (the ones in this directory). Drop them into any project so Claude Code / Cursor / Codex / etc. find them automatically. ```bash -sm skills list # see what's available -sm skills add # install ALL skills into ./.claude/skills/ -sm skills add simple-module-creating simple-module-cli # specific ones only -sm skills add -g # ~/.claude/skills (machine-wide) -sm skills add --dest agents/skills # explicit target dir -sm skills add --symlink # symlink to bundled source (good when iterating on the skills themselves) -sm skills update # re-pull whatever is already installed at the dest -sm skills update simple-module-doctor # explicitly re-pull one (always force-overwrites) +smpy skills list # see what's available +smpy skills add # install ALL skills into ./.claude/skills/ +smpy skills add simple-module-creating simple-module-cli # specific ones only +smpy skills add -g # ~/.claude/skills (machine-wide) +smpy skills add --dest agents/skills # explicit target dir +smpy skills add --symlink # symlink to bundled source (good when iterating on the skills themselves) +smpy skills update # re-pull whatever is already installed at the dest +smpy skills update simple-module-doctor # explicitly re-pull one (always force-overwrites) ``` -**Without `--force`, `sm skills add` skips skills that already exist at the destination** — so re-running it is safe. Use `--force` (or `sm skills update`) to overwrite. +**Without `--force`, `smpy skills add` skips skills that already exist at the destination** — so re-running it is safe. Use `--force` (or `smpy skills update`) to overwrite. -The bundle resolves against your installed `simple_module_cli`. To get newer skills, upgrade the CLI (`uv sync` or `pip install -U simple_module_cli`) and re-run `sm skills update`. +The bundle resolves against your installed `simple_module_cli`. To get newer skills, upgrade the CLI (`uv sync` or `pip install -U simple_module_cli`) and re-run `smpy skills update`. -## `sm host gen-pages` — regenerate the Inertia manifest +## `smpy host gen-pages` — regenerate the Inertia manifest Run from a host project. Scans every installed module's `pages/*.tsx`, writes `client_app/modules.{manifest.json,generated.ts,generated.css}`, and extends Vite's `server.fs.allow`. ```bash -sm host gen-pages # uses ./client_app -sm host gen-pages --host-dir=apps/web/client_app +smpy host gen-pages # uses ./client_app +smpy host gen-pages --host-dir=apps/web/client_app ``` -`sm new` runs this at scaffold time; you only need to call it manually after adding/renaming `.tsx` files mid-session, or after `pip install`-ing a new module that ships pages. +`smpy new` runs this at scaffold time; you only need to call it manually after adding/renaming `.tsx` files mid-session, or after `pip install`-ing a new module that ships pages. -## `sm host sync-js-deps` — install module JS deps +## `smpy host sync-js-deps` — install module JS deps Wheel-installed modules ship `package.json` declarations that need to land in the host's `client_app/node_modules`. This command does that. **In-repo workspace modules don't need it** — npm workspaces resolve them automatically. ```bash -sm host sync-js-deps # uses ./client_app -sm host sync-js-deps --host-client-app=apps/web/client_app +smpy host sync-js-deps # uses ./client_app +smpy host sync-js-deps --host-client-app=apps/web/client_app ``` Run after `pip install ` if the new module ships frontend code. -## `sm settings import-from-env` +## `smpy settings import-from-env` Walks the live environment for every `SM__` variable matching a registered settings dataclass and writes a SYSTEM-tier override into the settings module's store. Useful when promoting from environment-driven config (typical in Docker) to in-DB overrides (manageable in the admin UI) without re-keying values by hand. ```bash -sm settings import-from-env +smpy settings import-from-env ``` -## `sm users create-admin` +## `smpy users create-admin` Bootstraps the first admin user, or rotates an existing admin's password. ```bash -sm users create-admin -e admin@example.com -p hunter2 -sm users create-admin -e admin@example.com -p new-password --force # rotate -sm users create-admin -e admin@example.com -p hunter2 --full-name "Admin" +smpy users create-admin -e admin@example.com -p hunter2 +smpy users create-admin -e admin@example.com -p new-password --force # rotate +smpy users create-admin -e admin@example.com -p hunter2 --full-name "Admin" ``` | Flag | Meaning | @@ -156,15 +156,15 @@ Don't bake `--password` literals into a script you commit; use a secrets store a ## Pitfalls -- **Wrong shell for `--with`.** `sm new` `--with auth,users` (catalog keys, lowercase). `sm create-host` `--with Auth,Users` (`ModuleMeta.name`, PascalCase). They're not interchangeable. -- **Ran `sm new` inside an existing project.** The default `--dest ./` creates a sibling directory. If the directory already exists and is non-empty, the command errors out — pass `--dest` explicitly to disambiguate. -- **Ran `sm host gen-pages` from outside the host directory.** Defaults to `./client_app`; pass `--host-dir` from elsewhere. -- **Forgot `sm host sync-js-deps` after `pip install`-ing a module with pages.** Vite resolves module imports against `client_app/node_modules`; the new module's JS deps won't land until you sync. -- **Used `sm create-module` to add a module to an existing host.** That command is for **publishable** packages, intended to live in their own repo. To add a module to an existing host: install it (`pip install simple_module_` or add to `pyproject.toml` and `uv sync`), then autogenerate a migration. See **simple-module-creating** + **simple-module-migrations**. -- **Calling `sm create-admin` before migrations have run.** The users tables don't exist yet; the command will error. Run `alembic upgrade head` first (or use `sm new` which does it for you when `--no-install` isn't set). +- **Wrong shell for `--with`.** `smpy new` `--with auth,users` (catalog keys, lowercase). `smpy create-host` `--with Auth,Users` (`ModuleMeta.name`, PascalCase). They're not interchangeable. +- **Ran `smpy new` inside an existing project.** The default `--dest ./` creates a sibling directory. If the directory already exists and is non-empty, the command errors out — pass `--dest` explicitly to disambiguate. +- **Ran `smpy host gen-pages` from outside the host directory.** Defaults to `./client_app`; pass `--host-dir` from elsewhere. +- **Forgot `smpy host sync-js-deps` after `pip install`-ing a module with pages.** Vite resolves module imports against `client_app/node_modules`; the new module's JS deps won't land until you sync. +- **Used `smpy create-module` to add a module to an existing host.** That command is for **publishable** packages, intended to live in their own repo. To add a module to an existing host: install it (`pip install simple_module_` or add to `pyproject.toml` and `uv sync`), then autogenerate a migration. See **simple-module-creating** + **simple-module-migrations**. +- **Calling `smpy create-admin` before migrations have run.** The users tables don't exist yet; the command will error. Run `alembic upgrade head` first (or use `smpy new` which does it for you when `--no-install` isn't set). ## Related skills -- **simple-module-creating** — what `sm create-module` produces and the post-scaffold contract -- **simple-module-inertia-pages** — what `sm host gen-pages` regenerates and why -- **simple-module-migrations** — the `alembic upgrade head` step `sm new` runs +- **simple-module-creating** — what `smpy create-module` produces and the post-scaffold contract +- **simple-module-inertia-pages** — what `smpy host gen-pages` regenerates and why +- **simple-module-migrations** — the `alembic upgrade head` step `smpy new` runs diff --git a/skills/simple-module-creating/SKILL.md b/skills/simple-module-creating/SKILL.md index 6a4e5e4f..b1ee30ab 100644 --- a/skills/simple-module-creating/SKILL.md +++ b/skills/simple-module-creating/SKILL.md @@ -12,13 +12,13 @@ description: Use when adding a new feature package to a simple_module_python app ```bash # scaffold a publishable module package in ./orders (run in a fresh repo or # inside a host's modules/ directory) -sm create-module orders +smpy create-module orders # install the new package into the host environment uv sync # or: pip install -e ./orders # if you added a frontend page, regenerate the Inertia manifest -sm host gen-pages --host-dir=client_app +smpy host gen-pages --host-dir=client_app # if you added SQLModel tables, autogenerate + apply a migration uv run alembic revision --autogenerate -m "add orders module" diff --git a/skills/simple-module-inertia-pages/SKILL.md b/skills/simple-module-inertia-pages/SKILL.md index eb60afe2..7825d220 100644 --- a/skills/simple-module-inertia-pages/SKILL.md +++ b/skills/simple-module-inertia-pages/SKILL.md @@ -15,12 +15,12 @@ description: Use when adding or debugging an Inertia.js page in a simple_module_ | Module page (snake-case dir) | `inertia.render("BlogPosts/Index", ...)` | `/blog_posts/pages/Index.tsx` | | Host page | `inertia.render("Landing", ...)` | `/client_app/pages/Landing.tsx` | -The namespace is **PascalCase of the module's directory name**, not the file system path. Directory `blog_posts` → `BlogPosts`. The framework's manifest generator (`sm host gen-pages`) wires up Vite's `import.meta.glob` to resolve these keys at runtime. +The namespace is **PascalCase of the module's directory name**, not the file system path. Directory `blog_posts` → `BlogPosts`. The framework's manifest generator (`smpy host gen-pages`) wires up Vite's `import.meta.glob` to resolve these keys at runtime. After adding or renaming a `.tsx`, regenerate the manifest: ```bash -sm host gen-pages --host-dir=client_app +smpy host gen-pages --host-dir=client_app ``` Boot regenerates it too; mid-session adds need the manual call before HMR sees them. diff --git a/skills/simple-module-migrations/SKILL.md b/skills/simple-module-migrations/SKILL.md index 19775a3f..50019844 100644 --- a/skills/simple-module-migrations/SKILL.md +++ b/skills/simple-module-migrations/SKILL.md @@ -23,7 +23,7 @@ my_host/ # the host project ## How autogenerate sees every module -The host's `migrations/env.py` (scaffolded by `sm create-host`) calls: +The host's `migrations/env.py` (scaffolded by `smpy create-host`) calls: ```python from simple_module_db import build_module_metadata, make_include_object diff --git a/skills/simple-module-testing/SKILL.md b/skills/simple-module-testing/SKILL.md index 1962cb64..d24e5941 100644 --- a/skills/simple-module-testing/SKILL.md +++ b/skills/simple-module-testing/SKILL.md @@ -84,7 +84,7 @@ modules/orders/ └── test_service.py # imports orders.service ``` -The directory must be listed in the root `pyproject.toml` under `[tool.pytest.ini_options].testpaths` for `make test` to pick it up. `sm create-module` adds this entry; if you scaffolded a module by hand, add it. +The directory must be listed in the root `pyproject.toml` under `[tool.pytest.ini_options].testpaths` for `make test` to pick it up. `smpy create-module` adds this entry; if you scaffolded a module by hand, add it. ## Pitfalls