qtmp — Q-Templates: Developer Environment Automation CLI
A Python CLI that detects your project environment, resolves dependencies between tech stacks, generates an execution plan, and runs the package-manager commands — instead of you hunting through docs and typing them one by one.
pip3 install qtmp
qtmp create react
qtmp add tailwind
qtmp plan add sqlalchemy # dry run first
qtmp add sqlalchemy # then actually run it
qtmp doctor
Why this isn't just "a script that runs npm install"
- Recipe architecture — every piece of the stack (React, Tailwind,
FastAPI, SQLAlchemy, ...) is a self-contained
Recipewith three methods:requires(),detect(),plan(). Adding a new one is one file + one line in a registry — nothing in the CLI, planner, or executor changes. - Real dependency resolution —
qtmp add sqlalchemyin an empty folder resolves to: scaffold FastAPI → install the Postgres driver → install SQLAlchemy, in that order, with shared dependencies (a "diamond") only ever installed once. - Project detection — reads
package.json,vite.config.*,pyproject.toml,requirements.txt,docker-compose.yml,tailwind.config.*once per run into aProjectContext, so every recipe andqtmp doctorsee the same picture of "what's already here." - Dry-run planning —
qtmp plan add Xshows the exact commands that would run, against the real dependency graph, without touching the filesystem.qtmp add Xis the same resolution path, one step later. - Idempotent by construction — every recipe's
detect()is checked before itsplan()is even called. Runningqtmp add tailwindtwice reports "already installed, nothing to do" instead of reinstalling or corrupting config. - Fail-fast, resumable execution — if a step fails (bad network, missing tool), everything after it stops. Already-applied steps are skipped on the next attempt; nothing after the failure was touched.
Commands
| Command | What it does |
|---|---|
qtmp create react / qtmp create fastapi / qtmp create frontend-basic |
Scaffold a brand-new project |
qtmp <capability> |
Shorthand — qtmp fastapi == qtmp create fastapi; qtmp tailwind == qtmp add tailwind (creatable capabilities go to create, everything else to add) |
qtmp add <capability> |
Resolve dependencies and install/configure a capability |
qtmp plan add <capability> |
Same resolution, dry-run only — prints the commands, changes nothing |
qtmp doctor |
Diagnose which tools are on PATH and what this directory looks like |
qtmp list |
List every registered capability |
qtmp cheatsheet [name] |
View built-in or your own personal cheatsheets |
qtmp --version |
Print the installed version |
--cwd works in either position — qtmp --cwd ./my-app create react and qtmp create react --cwd ./my-app both work identically.
Beginner React (b) gets Vite + Tailwind automatically. Advanced (a)
gets TypeScript, Tailwind, shadcn/ui, React Router, Axios, and a
src/components|pages|hooks|lib layout — all resolved and applied
through the same planner/executor path as a manual qtmp add.
frontend-basic (aliases: frontend, html-css-js, vanilla) scaffolds
a plain index.html + style.css + script.js — no framework, no npm,
no network required. Good for confirming files land where you expect
without waiting on any package manager.
Re-running create is safe
Every creatable recipe checks the target folder before scaffolding:
- Doesn't exist / empty → scaffolds normally.
- Already a valid project of that type → skipped, reports "already exists," doesn't touch it.
- Exists, non-empty, and isn't that project type → clean error, doesn't blindly run a scaffolder into someone else's files.
Architecture
qtmp/
├── context.py ProjectContext.detect(cwd) -- one detection pass, shared everywhere
├── planner.py Step, Plan, resolve_plan() -- pure dependency-graph resolution
├── executor.py apply(plan) -- runs pending steps, skips satisfied ones, fails fast
├── doctor.py read-only environment + project diagnostics
├── recipes/
│ ├── base.py Recipe ABC: requires() / detect() / plan() / (optional) create()
│ ├── react.py provides "react", creatable
│ ├── tailwind.py provides "tailwind", requires "react" only if no node project exists yet
│ ├── router_axios.py provides "router" / "axios" / "shadcn" (shadcn requires "tailwind")
│ ├── fastapi.py provides "fastapi", creatable
│ ├── database.py provides "postgres" / "db-driver-postgres" / "sqlalchemy"
│ ├── frontend_basic.py provides "frontend-basic", creatable -- plain HTML/CSS/JS
│ └── __init__.py REGISTRY = {capability_name: recipe_instance} -- the only file
│ you edit to add a new recipe
├── cheatsheet_manager.py bundled vs. personal (~/.qtmp/cheatsheets/) cheatsheets
└── cli.py argparse subcommands, all going through resolve_plan()/apply();
also handles --cwd extraction (works before/after the
subcommand) and shorthand expansion ('qtmp fastapi' -> 'qtmp create fastapi')
The dependency-resolution example, end to end
qtmp add sqlalchemy on an empty folder resolves like this:
sqlalchemy
├── requires: fastapi -> not detected -> scaffold venv, install fastapi+uvicorn, write main.py
└── requires: db-driver-postgres
└── requires: fastapi -> already resolved above, not repeated
-> not detected -> pip install psycopg2-binary
-> not detected -> pip install sqlalchemy
Run it for real and check the output of qtmp plan add sqlalchemy first
— that's the exact plan the executor will follow, nothing hidden.
Adding a new recipe
# qtmp/recipes/docker.py
from qtmp.recipes.base import Recipe
from qtmp.planner import Step
class DockerRecipe(Recipe):
name = "Docker"
provides = "docker"
def detect(self, ctx):
return ctx.has_docker_compose
def plan(self, ctx):
return [Step("Write docker-compose.yml", apply_fn=lambda: ...)]
# qtmp/recipes/__init__.py
from qtmp.recipes.docker import DockerRecipe
REGISTRY["docker"] = DockerRecipe()
That's the entire integration surface. cli.py, planner.py, and
executor.py never need to know Docker exists.
Testing
pip install -e ".[dev]"
pytest -v
60 tests covering:
- CLI helpers — shorthand expansion (
qtmp fastapi→create fastapi), and--cwdextraction working in either position, including the--cwd=valueform and repeated occurrences. - Existing-target safety —
createskips cleanly on an already-valid project of the same type, and raises a clear error (not a crash) on a genuine name collision with unrelated content. - Project detection — Node/Vite/Python/venv/Docker/Tailwind sniffing, and that a malformed
package.jsondoesn't crash detection. - Dependency resolution — ordering, diamond dependencies installed once, unknown capabilities raising a clear error, and the exact FastAPI→driver→SQLAlchemy shape from the design doc.
- Idempotency & safe execution — a fully-satisfied chain is a no-op; a failure mid-plan halts everything after it and reports which step failed; already-applied steps are never re-run.
- Individual recipes —
detect()/plan()/requires()logic for React, Tailwind, Router, Axios, shadcn, FastAPI, and the database chain, with no real npm/pip/network calls — commands are asserted on, never executed, in unit tests. - Cheatsheets — personal overrides taking precedence over bundled ones, and falling back correctly on removal.
Recipe tests never shell out for real — plan() is asserted on
directly, so these pass identically whether or not Node/Python/Postgres
happen to be installed on the machine running CI.
Honest limitations (not yet done)
- Cross-platform: path handling is OS-aware (
os.name == "nt"branches for venv paths), but this has only actually been run on macOS/Linux. Windows support is written for, not verified. - Package manager choice: assumes
npmandpip. Nopnpm/yarn/poetry/uvalternative paths yet — a natural next recipe-level feature (a--pmflag resolved per-ecosystem). postgresrecipe deliberately does not auto-install a local Postgres server (that usually needssudo/ a package-manager choice we shouldn't make for you) — it only guides you and checks forpsqlon PATH. Automating a sudo-requiring step didn't feel like something a CLI should do silently, even in service of a demo.- AI-assisted setup from an earlier iteration was removed in this
rewrite to keep the core deterministic and testable; if it comes
back, it'll be a strictly optional layer on top of
resolve_plan(), never a replacement for it.
Metadata
Release files for qtmp 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| qtmp-1.0.0.tar.gz | 30.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qtmp-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 58.6 kB
Release files / qtmp-1.0.0.tar.gz
| Download URL | qtmp-1.0.0.tar.gz |
|---|---|
| Size | 30.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6448af0f4776f40b172b30874c561b81bc09ca6ab1c11bfac911072f479d86d0
|
|
BLAKE2b-256 checksum How to use checksums |
92762e013045acb5d703bbceaa10d4d206b30ffb59b6a29ad54e913f1a4d0bb1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / qtmp-1.0.0-py3-none-any.whl
| Download URL | qtmp-1.0.0-py3-none-any.whl |
|---|---|
| Size | 27.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e9b5854f63d37c2268645e91e39699a07a6cb1cc62cee3cdd4bf1d47bc616477
|
|
BLAKE2b-256 checksum How to use checksums |
701bd9846f78fa445f71f6e02091add4ceeb718f1d831e3695d3635159a5f2b9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|