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

See Release synchronization.

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.