The dev CLI
python dev.py <command>
Colon-separated commands work, and so does the plain form: migrate:status and
migrate status are the same command.
Migrations
| Command | What it does |
|---|---|
migrate |
Apply pending migrations |
migrate --step N |
Apply only the first N pending |
migrate --pretend |
Print what would run, touching nothing |
migrate --seed |
Migrate, then run the DatabaseSeeder |
migrate:status |
Which migrations ran, and in which batch |
migrate:rollback |
Refused under absolute data persistence |
migrate:reset |
Refused under absolute data persistence |
migrate:refresh |
Refused under absolute data persistence |
migrate:fresh |
Refused under absolute data persistence |
migrate:install |
Create the migrations table only |
Safety Notice: Craft Engine enforces Absolute Data Persistence. Destructive commands (
migrate:fresh,migrate:reset,migrate:refresh,db wipe) are strictly prohibited in production, test, and automated agent environments. See Database Safety.
Database
| Command | What it does |
|---|---|
db seed |
Run DatabaseSeeder |
db seed --class ProductSeeder |
Run one seeder |
db show |
Connection, driver, host, database |
db tables |
List tables |
db ping |
Verify the connection; non-zero exit on failure |
db wipe --force |
Refused under absolute data persistence |
db wipe exits 1 without --force, and with it the migrator refuses the
operation (DATABASE_DESTRUCTIVE_OPERATION_REFUSED) in every environment. No
database name, _test suffix or in-memory database authorizes a wipe, and
config/database.py carries no allowlist key for it.
Generators
| Command | Creates |
|---|---|
make model Product |
app/Models/Product.py |
make model Product -m |
Model plus a create migration |
make controller Product |
app/Http/Controllers/ProductController.py |
make controller Product -r |
Controller with index/show/store/update/destroy |
make migration create_products_table |
Timestamped migration |
make migration add_color_to_products_table |
An alter migration |
make middleware EnsureAdmin |
app/Http/Middleware/ |
make request StoreProduct |
FormRequest |
make resource Product |
API resource |
make job SendEmail |
Queued job |
make event OrderPlaced |
Event |
make listener NotifyTeam |
Listener |
make policy Product |
Policy |
make seeder Product |
Seeder |
make service Billing |
Plain service class |
make auth [--views] [-f] |
Full authentication stack (Controller, FormRequests, Forge views, routes) |
make admin [-f] |
RBAC admin panel (controllers, Role/Permission/Group models, RBAC migration, Forge views, /admin/* routes, config/auth.py model entries) |
Names are normalised: service_order, service-order and ServiceOrder all
produce ServiceOrder. Suffixes are added once — make controller Product and
make controller ProductController both give ProductController.
Generators refuse to overwrite. Pass --force when you mean it.
Migration names drive the stub: create_*_table produces a create migration and
add_*_to_*_table produces an alter migration, with the table inferred.
Extensions
| Command | What it does |
|---|---|
make module <slug> |
Generate a business module, including a model and migration |
make plugin <slug> |
Generate a capability plugin |
make theme <slug> |
Generate a presentation-only theme |
make connector <slug> |
Generate a headless external integration |
make screen <slug> <screen> |
Add a controller, route, view and translation keys to a module |
extension list / extension status <slug> |
Inspect state, manifest/installed versions, update requirements and health |
extension install <slug> |
Apply initial migrations and seed missing translations from local files |
extension update <slug> |
Apply pending migrations/translations to an installed or inactive extension |
extension activate <slug> |
Register runtime contributions after compatibility checks |
extension deactivate <slug> |
Withdraw owned runtime contributions; preserve data |
extension uninstall <slug> |
Mark an inactive extension uninstalled; preserve data |
Updating requires deploying files and restarting workers before reactivation. See extensions.md for the ordered procedure and architecture.md for kind selection.
Routes
python dev.py route list
python dev.py route list --method POST
python dev.py route list --path /api
python dev.py route list --json
The table shows the ordered global middleware stack plus each route's declared
middleware. --json returns global_middleware and a routes array with
method, uri, name, and middleware fields for tooling and agents.
python dev.py route check
python dev.py route check --json
route:check resolves every route's middleware aliases and action, and exits 1
when one is broken, so it composes in a CI job:
| Finding | Level | Meaning |
|---|---|---|
ROUTE_MIDDLEWARE_INVALID |
error | A route names a middleware alias the kernel cannot resolve |
ROUTE_ACTION_NOT_FOUND |
error | A route points at a controller method that does not exist |
ROUTE_NAME_DUPLICATE |
warning | Two routes share a name, so url_for() resolves to whichever registered last |
The command takes the kernel from the container (app.make("kernel"), bound in
bootstrap/app.py). A project that does not bind it still gets the action and
name checks, but its middleware aliases are not resolved. python dev.py doctor
runs the same route checks along with the rest of the project's wiring.
Data
python dev.py data audit
python dev.py data audit --json
data:audit reads the live database and reports rows that already break the
schema's own promises: a foreign key that points at a parent row which is not
there, and a row in a table that carries tenant_id with none set. It is
read-only — nothing is repaired, because what to do with customer data is the
owner's call — and it exits 1 when it finds an error, so it composes in a CI
job. When multi-tenancy is on, a NULL tenant_id is an error rather than a
warning.
Queue
python dev.py queue work
python dev.py queue work --queue emails
python dev.py queue work --once
See Queues and events.
Outbox
python dev.py outbox relay
python dev.py outbox relay --limit 500
Publishes the messages committed to the transactional outbox. Run it from a worker or a schedule; see Transactional Outbox.
Cache
python dev.py cache clear
Firewall (WAF) & Security Audit
| Command | What it does |
|---|---|
firewall list |
List IP whitelist/blacklist rules and reputation scores |
firewall allow <ip> |
Add an IP address to the trusted whitelist |
firewall block <ip> [-r reason] |
Add an IP address to the permanent blacklist |
security audit [--limit N] |
Display recent authentication attempts and honeypot events |
python dev.py firewall list
python dev.py firewall allow 192.168.1.100
python dev.py firewall block 203.0.113.55 -r "Port scanner detected"
python dev.py security audit --limit 50
AI Coding Agents & Discovery
Craft Engine is optimized for autonomous AI coding agents (Cursor, Claude Code, Windsurf, AGY):
| Command | What it does |
|---|---|
agent:scaffold [-f] |
Bootstrap AI context files (AGENTS.md, .cursorrules, llms.txt, llms-full.txt, .agents/rules/AGENTS.md), install the whole agent catalog into .agents/, and generate the coordination kit, environment guard, pre-commit hook, CI workflow and VS Code files |
agent:rules |
Alias for agent:scaffold |
agent:list [--kind K] |
List catalog entries: agent, skill, command, reference |
agent:install NAME... [-f] |
Install named agents, skills or commands (shared references always come along) |
agent:install --all [-f] |
Install the whole catalog |
python dev.py agent:scaffold
python dev.py agent:list --kind agent
python dev.py agent:install code-reviewer test-driven-development ship
Installs refuse to overwrite an existing entry without --force, and check every
conflict before writing anything. Both commands write to .agents/ and keep
.claude a symlink to it; a project that still has a physical .claude/
directory is refused (AGENT_LEGACY_DIRECTORY) until its contents are moved.
See AI Agents.
Release
| Command | What it does |
|---|---|
release:status |
Print the running identity, the VERSION file and the last recorded sync |
release:sync |
Apply pending migrations, run the release seeders and record the release |
Application
| Command | What it does |
|---|---|
serve |
Development server (--host, --port, --no-reload) |
tinker |
Interactive shell with the app booted |
about |
Environment, debug, Python, database, cache, queue |
doctor [--json] |
Check the project's wiring (routes, identity models, templates, tables, translations) and print each mistake with its fix; exits 1 on an error |
key:generate |
Generate APP_KEY and write it to .env |
tinker gives you app, db and the facades:
>>> from app.Models.User import User
>>> User.query().count()
3
Exit codes
Commands exit non-zero on failure, so they compose in scripts and CI:
python dev.py db ping && python dev.py migrate
Adding a command
dev is built with Typer. Add commands in
engine/cli/app.py:
@cli.command("stats")
def stats():
"""Show application statistics."""
app = get_app()
total = app.make("db").statement("SELECT COUNT(*) AS n FROM users").fetchone()
echo(f"Users: {total['n']}")
Use get_app() to boot the application lazily — importing it at module level
would slow down every command, including --help.