feat(cli): sm new wizard + standalone simple_module_cli distribution - #60
Merged
Conversation
Designs an upgraded \`sm new\` that scaffolds a project with any chosen subset of modules (preset or custom) and lands a runnable Celery worker + beat + Redis stack when background_tasks is selected — no manual Makefile / docker-compose / run_worker.py editing required.
Implementation plan for the spec at docs/superpowers/specs/2026-04-26-cli-modules-and-bg-jobs-design.md. Seven TDD tasks: package split, catalog + expand_deps, wizard, background_tasks recipe + opt-in templates, create_app_project refactor, sm new flag wiring, lint/typecheck pass.
Preparing the file for split into a package — catalog, wizard, recipes, new will land in dedicated modules. Console-script entry point unchanged.
Adds CATALOG, PRESETS, and expand_deps() — pure data + one pure function. Will be wired into 'sm new' in a follow-up.
db -> tenancy -> preset (or custom checkbox loop) -> confirm. Auto-adds required deps with a printed note. No new TUI dependency.
Recipes lay down post-scaffold artifacts (run_worker.py, docker-compose with redis/worker/beat, worker.Dockerfile, Makefile targets, env var) without touching framework scaffolding. Templates live under templates/host/_optional/ and are skipped by the default copy walker.
Default 'standard' preset preserves existing behavior. Selected modules drive both Python deps (from catalog) and post-scaffold recipes — so selecting background_tasks now lays down a runnable Celery stack.
Scaffolds a project with any chosen subset of modules. Selecting background_tasks lands a runnable Celery worker + beat + Redis stack via docker compose, host Make targets, and scripts/run_worker.py — no manual editing required. Wizard path (sm new <name>) prompts: db -> tenancy -> preset -> confirm. Flag path (sm new <name> --yes --preset full --with file_storage) skips prompts. Auto-adds transitive deps with a printed note in both modes.
- Split create_app_project from scaffolding.py into app_project.py to stay under the 300-line cap. - SM009 (framework -> plugin coupling) now skips templates/* paths; the new background_tasks run_worker.py template intentionally imports from the background_tasks package once it has been copied into a host project, where it is no longer framework code. - ruff format + import-sort fixes, FrozenInstanceError specificity in test_cli_catalog.
- Move duplicated _set_env_key from app_project.py and cli/recipes.py
into simple_module_hosting/_env.py.
- Use scaffolding._to_pascal_case for CATALOG.display normalization
instead of ad-hoc display.replace(' ', '').
- Replace direct attribute assignment with __setattr__ in the
frozen-dataclass test so it doesn't need ty/ruff suppressions.
Carves the scaffolder out of simple_module_hosting into a new PyPI
distribution called simple-module with deps {typer, tomlkit} only —
no framework runtime deps. Single 'sm' console script; plugin
subcommands (host gen-pages, users create-admin, etc.) discovered via
the simple_module.cli_plugins entry-point group. All sm-* sibling
scripts go away.
9-task TDD plan: bootstrap distribution, move utilities and scaffolding, move catalog/wizard/recipes/new/app_project, port Click to Typer, implement plugin discovery, carve sm host plugin, convert users + settings to plugins, final dep-guard + cleanup.
New workspace member framework/cli/ shipping the simple-module PyPI distribution. Contains stub cli.py for the sm entry point; real commands land in subsequent commits.
Pure utilities; first pieces of the simple-module distribution. Hosting keeps temporary workspace dep on simple-module so existing modules can import from both during the migration.
create_host, create_module, _apply_template_files, and the entire templates/ tree relocate from simple_module_hosting to the new simple_module package. Hosting's scaffolding.py becomes a re-export shim so existing import sites keep working through Task 7. Recipes template path updated accordingly.
…ple_module All scaffolding logic now lives in the simple_module package. Hosting's cli/__init__.py click group still works (it imports new_project from the new location); fully replaced by the Typer port in the next commit.
- All Click decorators rewritten in Typer's Annotated[] style. - Wizard uses typer.prompt / typer.confirm. - Tests use typer.testing.CliRunner (drop-in for click.testing). - simple_module_hosting drops sm/simple-module console scripts. - simple_module_hosting/cli/ package deleted. - Plugin discovery hook added (no-op stub; real impl next commit).
discover_and_mount() walks the entry-point group, validates each load target is a typer.Typer, and mounts it under its entry name. Broken or duplicate plugins log one line to stderr and are skipped.
gen-pages and sync-js-deps move out of the deleted sm 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.
Drops sm-users and sm-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 rewritten as a Typer app (was hand-rolled Click-style argv parsing). README updated for the new command shape; pytest testpaths gain framework/cli/tests.
- Delete simple_module_hosting.scaffolding shim and the workspace dep on simple_module. - Add framework/cli/tests/test_no_framework_deps.py to guard against future dep drift in the standalone scaffolder. - Use enum.StrEnum (Python 3.12+) for Db / Preset. - README + Makefile reflect the final sm host/users/settings shape.
- scripts/new_module.py uses simple_module.case.to_pascal_case instead of its private to_class_name helper. - app_project.py hoists previously-deferred catalog/recipes/scaffolding imports to module scope; the import cycle they were breaking no longer exists after the carve-out. - new_project drops the db_value/tenancy_value shadow vars; the two branches now assign the final tuple directly.
The PyPI distribution name 'simple-module' is already taken. Rename to
'simple-module-cli' (and importable package 'simple_module_cli').
- framework/cli/simple_module/ → framework/cli/simple_module_cli/
- All Python imports: from simple_module.X → from simple_module_cli.X
- Entry-point group simple_module.cli_plugins → simple_module_cli.cli_plugins
(plugin registrations in hosting / users / settings updated)
- Console scripts sm / simple-module now point at simple_module_cli.cli:main
- Templates package: simple_module.templates → simple_module_cli.templates
- Release workflow: simple_module_cli added to publish-pypi matrix
- Dep-guard test pinned to distribution('simple-module-cli')
- README + CHANGELOG + hosting README refer to the new name
scripts/check_metadata.py enforces 'simple_module_*' names across the
framework. Switch the new distribution from kebab 'simple-module-cli'
to underscore 'simple_module_cli' to match every other framework
package (simple_module_core, simple_module_hosting, etc.).
- pyproject.toml: name = 'simple_module_cli'
- README + CHANGELOG snippets use the underscore form for install
instructions (PyPI normalizes either way; we keep the convention
consistent with the rest of the repo)
- test_no_framework_deps pins distribution('simple_module_cli')
- framework/cli/README.md gains explicit Install / Usage sections
(required by scripts/check_readmes.py)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two related improvements to the
smCLI, landing on one branch:sm new— now scaffolds a project with any chosen subset of modules (preset or custom) and lands a runnable Celery worker + beat + Redis stack whenbackground_tasksis selected. Wizard or flag-driven; transitive deps auto-resolved.simple_module_clidistribution — the scaffolder + Typer CLI moves out ofsimple_module_hostinginto a new PyPI package whose only deps aretyper+tomlkit. Singlesmconsole script; plugin subcommands (sm host gen-pages,sm users create-admin,sm settings import-from-env) discovered via thesimple_module_cli.cli_pluginsentry-point group. Allsm-*sibling scripts (sm-users,sm-settings, hosting'ssm) are gone.What changed
CLI feature work (
sm new)framework/cli/simple_module_cli/{catalog,wizard,recipes,new}.pywith a hardcoded module catalog, transitiveexpand_deps, presets (minimal/standard/full/ custom), and aBackgroundTasksRecipethat dropsscripts/run_worker.py,docker-compose.yml(redis + worker + beat),docker/worker.Dockerfile, Make targets, and theSM_BG_TASKS_BROKER_URLenv entry.create_app_projectaccepts aselected=module list; defaultstandardpreset preserves the existing pre-wired set.Standalone distribution
simple_module_cli(importable assimple_module_cli) atframework/cli/, deps:typer>=0.12,tomlkit>=0.13. No framework runtime deps; guarded byframework/cli/tests/test_no_framework_deps.py.simple_module_cli.plugins.discover_and_mountwalks thesimple_module_cli.cli_pluginsentry-point group and mounts each plugin as a named subgroup (broken/duplicate plugins log to stderr and are skipped).simple_module_hostingkeeps its runtime; losescli/,scaffolding.py,app_project.py,_env.py, andtemplates/. Gainshost_cli.py(Typer app forgen-pages+sync-js-deps).usersandsettingsmodules dropsm-users/sm-settingsconsole scripts; register Typer apps via the new entry-point group instead.settings/cli.pyrewritten as a Typer app.Makefile:sm gen-pages→sm host gen-pages;sm sync-js-deps→sm host sync-js-deps.Release plumbing
.github/workflows/release.ymlpublish-pypimatrix gainssimple_module_cli.bump_version.pyalready auto-discoversframework/*/pyproject.tomlso no script change needed.Docs / cleanup
README.md,CHANGELOG.md,framework/hosting/README.md, and the newframework/cli/README.mdupdated for the renamed CLI distribution and thesm host/users/settingsplugin shape.docs/superpowers/{specs,plans}/for future reference.Test plan
All gates verified locally; CI runs the same set:
make ci-python-lint— ruff format + check, all cleanmake ci-python-typecheck— ty, all cleanmake ci-check-file-size— 300-line cap respectedmake ci-js-lint— biome (1 pre-existing warning)make ci-js-typecheck— tsc per workspacemake test-py— 1017 passed, 6 deselectedmake test-js— 8 passed (vitest)make build— vite production buildscripts/check_metadata.py— all package metadata OKscripts/check_readmes.py— all READMEs OKmake doctor— 0 errors (1 pre-existing SM003 warning unrelated)End-to-end smoke:
uv run sm --helplistsnew,create-host,create-module, plus mounted pluginshost,users,settingsuv run sm new demo --yes --preset full --no-installproduces a project withscripts/run_worker.py,docker-compose.yml,docker/worker.Dockerfile, Make targets, and the broker env entryNotes for review
simple_module_hostingpackage no longer ships ansmconsole script — everything goes through the newsimple_module_clidistribution. Fresh installs will need both packages for the full CLI surface (or justsimple_module_clifor the scaffolder alone).sm newkeeps working in degraded environments.simple_module_cli) to matchsimple_module_core/simple_module_hosting/ etc., per the convention enforced byscripts/check_metadata.py. PyPI normalizes this tosimple-module-clion the URL side.