CLI Reference

Ferro provides a powerful CLI tool for project scaffolding, code generation, database management, and development workflow automation.

Installation

cargo install ferro-cli

Or build from source:

git clone https://github.com/albertogferrario/ferro
cd ferro/ferro-cli
cargo install --path .

Project Commands

ferro new

Create a new Ferro project with the complete directory structure.

# Interactive mode (prompts for project name)
ferro new

# Direct creation
ferro new my-app

# Skip git initialization
ferro new my-app --no-git

# Non-interactive mode (uses defaults)
ferro new my-app --no-interaction

Options:

OptionDescription
--no-interactionSkip prompts, use defaults
--no-gitDon't initialize git repository

Generated Structure:

my-app/
├── src/
│   ├── main.rs
│   ├── bootstrap.rs
│   ├── routes.rs
│   ├── controllers/
│   │   └── mod.rs
│   ├── middleware/
│   │   ├── mod.rs
│   │   └── cors.rs
│   ├── models/
│   │   └── mod.rs
│   ├── migrations/
│   │   └── mod.rs
│   ├── events/
│   │   └── mod.rs
│   ├── listeners/
│   │   └── mod.rs
│   ├── jobs/
│   │   └── mod.rs
│   ├── notifications/
│   │   └── mod.rs
│   ├── tasks/
│   │   └── mod.rs
│   ├── seeders/
│   │   └── mod.rs
│   └── factories/
│       └── mod.rs
├── frontend/
│   ├── src/
│   │   ├── pages/
│   │   │   └── Home.tsx
│   │   ├── layouts/
│   │   │   └── Layout.tsx
│   │   ├── types/
│   │   │   └── inertia.d.ts
│   │   ├── app.tsx
│   │   └── main.tsx
│   ├── package.json
│   ├── tsconfig.json
│   ├── vite.config.ts
│   └── tailwind.config.js
├── Cargo.toml
├── .env
├── .env.example
└── .gitignore

Development Commands

ferro serve

Start the development server for both backend and frontend. Auto-reload is opt-in via --watch; without it, the r key triggers rebuilds on demand.

# Start both backend and frontend. No file watching; press r to rebuild on demand.
ferro serve

# Enable file-watch auto-reload with a 500ms trailing-edge debounce.
ferro serve --watch

# Custom ports.
ferro serve --port 8080 --frontend-port 5173

# Backend only (no frontend dev server).
ferro serve --backend-only

# Frontend only (no Rust compilation).
ferro serve --frontend-only

# Skip TypeScript type generation.
ferro serve --skip-types

Options:

OptionDefaultDescription
--port8080Backend server port
--frontend-port5173Frontend dev server port
--backend-onlyfalseRun only the backend
--frontend-onlyfalseRun only the frontend
--skip-typesfalseSkip TypeScript type generation
--watchfalseEnable file-watch auto-reload (500ms debounce)

Key bindings (when stdin is a TTY):

KeyAction
rRebuild the backend and regenerate types. If a build is in flight, it is cancelled and restarted.
q or Ctrl-CGraceful shutdown.

When stdin is not a TTY (for example, when ferro serve is piped or run under a process supervisor), the r key is unavailable and the banner says so; Ctrl-C still works.

What it does:

  1. Starts the Rust backend via an in-process supervisor that owns the cargo run child directly. Auto-reload is opt-in via --watch; without it, the supervisor waits for the r key to trigger a rebuild.
  2. Starts the Vite frontend dev server.
  3. With --watch, watches *.rs files under src/ with a 500ms trailing-edge debounce; a burst of saves produces one rebuild after the burst settles.
  4. On every rebuild (manual or file-triggered), regenerates TypeScript types from Rust InertiaProps structs.
  5. Auto-resolves port conflicts: if the frontend port is in use, the next available port is selected and propagated to the backend via VITE_DEV_SERVER.

ferro generate-types

Generate TypeScript type definitions from Rust InertiaProps structs.

ferro generate-types

This scans your Rust code for structs deriving InertiaProps and generates corresponding TypeScript interfaces in frontend/src/types/.

Includes route generation: generate-types also runs route generation internally, producing TypeScript route helpers alongside the type definitions.

ferro clean

Remove build artifacts from the project.

# Remove all build artifacts (runs cargo clean)
ferro clean

# Remove only artifacts older than N days (requires cargo-sweep)
ferro clean --sweep 7

Options:

OptionDefaultDescription
--sweep <days>Remove only artifacts older than N days. Requires cargo install cargo-sweep.

What it does:

  1. Without --sweep: runs cargo clean, removing the entire target/ directory.
  2. With --sweep <days>: invokes cargo sweep --maxage <days> to remove only stale artifacts, preserving recently compiled dependencies.

Code Generators

All generators follow the pattern ferro make:<type> <name> [options].

ferro make:controller

Generate a controller with handler methods.

# Basic controller
ferro make:controller UserController

# Resource controller with CRUD methods
ferro make:controller PostController --resource

# API controller (JSON responses)
ferro make:controller Api/ProductController --api

Options:

OptionDescription
--resourceGenerate index, show, create, store, edit, update, destroy methods
--apiGenerate API-style controller (JSON responses)

Generated file: src/controllers/user_controller.rs

#![allow(unused)]
fn main() {
use ferro::{handler, Request, Response, json_response};

#[handler]
pub async fn index(req: Request) -> Response {
    json_response!({ "message": "index" })
}

#[handler]
pub async fn show(req: Request) -> Response {
    json_response!({ "message": "show" })
}

// ... additional methods for --resource
}

ferro make:middleware

Generate middleware for request/response processing.

ferro make:middleware Auth
ferro make:middleware RateLimit

Generated file: src/middleware/auth.rs

#![allow(unused)]
fn main() {
use ferro::{Middleware, Request, Response, Next};
use async_trait::async_trait;

pub struct Auth;

#[async_trait]
impl Middleware for Auth {
    async fn handle(&self, request: Request, next: Next) -> Response {
        // TODO: Implement middleware logic
        next.run(request).await
    }
}
}

ferro make:action

Generate a single-action class for complex business logic.

ferro make:action CreateOrder
ferro make:action ProcessPayment

Generated file: src/actions/create_order.rs

#![allow(unused)]
fn main() {
use ferro::FrameworkError;

pub struct CreateOrder;

impl CreateOrder {
    pub async fn execute(&self) -> Result<(), FrameworkError> {
        tracing::info!("executing resource action");
        Ok(())
    }
}
}

ferro make:auth

Scaffold a complete authentication system with migration, controller, and setup instructions.

# Generate auth scaffolding
ferro make:auth

# Force overwrite existing files
ferro make:auth --force

Options:

OptionDescription
--force, -fOverwrite existing auth controller and migration

Generated Files:

  • src/migrations/m{timestamp}_add_auth_fields_to_users.rs -- ALTER TABLE migration adding password, remember_token, and email_verified_at fields to the existing users table
  • src/controllers/auth_controller.rs -- Controller with register, login, and logout handlers

What it does:

  1. Generates an ALTER TABLE migration (assumes users table already exists)
  2. Creates an auth controller with register/login/logout handlers
  3. Registers the controller module in src/controllers/mod.rs
  4. Prints setup instructions for the auth provider and route registration

The command uses an ALTER TABLE approach because most projects already have a users table with basic fields. The migration adds only the authentication-specific columns.

See also: Authentication guide for the complete auth setup walkthrough.

ferro make:event

Generate an event struct for the event dispatcher.

ferro make:event UserRegistered
ferro make:event OrderPlaced

Generated file: src/events/user_registered.rs

#![allow(unused)]
fn main() {
use ferro_events::Event;

#[derive(Debug, Clone, Event)]
pub struct UserRegistered {
    pub user_id: i64,
}
}

ferro make:listener

Generate a listener that responds to events.

ferro make:listener SendWelcomeEmail
ferro make:listener NotifyAdmins

Generated file: src/listeners/send_welcome_email.rs

#![allow(unused)]
fn main() {
use ferro_events::{Listener, Event};
use async_trait::async_trait;

pub struct SendWelcomeEmail;

#[async_trait]
impl<E: Event + Send + Sync> Listener<E> for SendWelcomeEmail {
    async fn handle(&self, event: &E) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        tracing::info!("handling event");
        Ok(())
    }
}
}

ferro make:job

Generate a background job for queue processing.

ferro make:job ProcessImage
ferro make:job SendEmail

Generated file: src/jobs/process_image.rs

#![allow(unused)]
fn main() {
use ferro_queue::{Job, JobContext};
use serde::{Deserialize, Serialize};
use async_trait::async_trait;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessImage {
    pub image_id: i64,
}

#[async_trait]
impl Job for ProcessImage {
    async fn handle(&self, ctx: &JobContext) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        tracing::info!(id = self.image_id, "processing item");
        Ok(())
    }
}
}

ferro make:notification

Generate a multi-channel notification.

ferro make:notification OrderShipped
ferro make:notification InvoiceGenerated

Generated file: src/notifications/order_shipped.rs

#![allow(unused)]
fn main() {
use ferro_notifications::{Notification, Notifiable, Channel};

pub struct OrderShipped {
    pub order_id: i64,
}

impl Notification for OrderShipped {
    fn via(&self) -> Vec<Channel> {
        vec![Channel::Mail, Channel::Database]
    }
}
}

ferro make:migration

Generate a database migration file.

ferro make:migration create_posts_table
ferro make:migration add_status_to_orders

Generated file: src/migrations/m20240115_143052_create_posts_table.rs

#![allow(unused)]
fn main() {
use sea_orm_migration::prelude::*;

#[derive(DeriveMigrationName)]
pub struct Migration;

#[derive(DeriveIden)]
enum Item {
    Table,
    Id,
    Name,
    CreatedAt,
}

#[async_trait::async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .create_table(
                Table::create()
                    .table(Item::Table)
                    .if_not_exists()
                    .col(ColumnDef::new(Item::Id).integer().not_null().auto_increment().primary_key())
                    .col(ColumnDef::new(Item::Name).string().not_null())
                    .col(ColumnDef::new(Item::CreatedAt).timestamp().not_null())
                    .to_owned(),
            )
            .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .drop_table(Table::drop().table(Item::Table).to_owned())
            .await
    }
}
}

ferro make:inertia

Generate an Inertia.js page component with TypeScript types.

ferro make:inertia Dashboard
ferro make:inertia Users/Profile

Generated files:

  • frontend/src/pages/Dashboard.tsx
  • src/controllers/ (props struct)

ferro make:json-view

Generate a JSON-UI view file with AI-powered component generation.

# Generate a view (AI-powered if ANTHROPIC_API_KEY is set)
ferro make:json-view UserIndex

# Generate with a description for AI context
ferro make:json-view UserEdit --description "Edit form for user profile"

# Specify a layout
ferro make:json-view Login --layout auth

# Skip AI generation, use static template
ferro make:json-view Dashboard --no-ai

Options:

OptionDescription
--description, -dDescription of the desired UI for AI generation
--layout, -lLayout to use (default: app)
--no-aiSkip AI generation, use static template

How it works:

  1. Scans your models and routes for project context
  2. Sends context to Anthropic API (Claude Sonnet) for intelligent view generation
  3. Falls back to a static template if no API key is configured or AI generation fails
  4. Generates a view file in src/views/ and updates mod.rs

Requirements:

  • Set ANTHROPIC_API_KEY in your environment for AI-powered generation
  • Without the key, a static template with common components is generated
  • Model override via FERRO_AI_MODEL environment variable

Generated file: src/views/user_index.rs

#![allow(unused)]
fn main() {
use ferro::{
    Action, Component, ComponentNode, JsonUiView, TableColumn, TableProps,
    TextElement, TextProps,
};

pub fn view() -> JsonUiView {
    JsonUiView::new()
        .title("User Index")
        .layout("app")
        .component(ComponentNode {
            key: "heading".to_string(),
            component: Component::Text(TextProps {
                content: "User Index".to_string(),
                element: TextElement::H1,
            }),
            action: None,
            visibility: None,
        })
        // ... additional components based on AI context or static template
}
}

ferro make:task

Generate a scheduled task.

ferro make:task CleanupExpiredSessions
ferro make:task SendDailyReport

Generated file: src/tasks/cleanup_expired_sessions.rs

#![allow(unused)]
fn main() {
use ferro::{Task, Schedule};
use async_trait::async_trait;

pub struct CleanupExpiredSessions;

#[async_trait]
impl Task for CleanupExpiredSessions {
    fn schedule(&self) -> Schedule {
        Schedule::daily()
    }

    async fn handle(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        tracing::info!("running scheduled task");
        Ok(())
    }
}
}

ferro make:seeder

Generate a database seeder.

ferro make:seeder UserSeeder
ferro make:seeder ProductSeeder

Generated file: src/seeders/user_seeder.rs

#![allow(unused)]
fn main() {
use ferro::Seeder;
use async_trait::async_trait;

pub struct UserSeeder;

#[async_trait]
impl Seeder for UserSeeder {
    async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        // TODO: Seed data
        Ok(())
    }
}
}

ferro make:factory

Generate a model factory for testing.

ferro make:factory UserFactory
ferro make:factory PostFactory

Generated file: src/factories/user_factory.rs

#![allow(unused)]
fn main() {
use ferro::Factory;
use fake::{Fake, Faker};

pub struct UserFactory;

impl Factory for UserFactory {
    type Model = user::Model;

    fn definition(&self) -> Self::Model {
        // TODO: Define factory
        todo!()
    }
}
}

ferro make:error

Generate a custom error type.

ferro make:error PaymentFailed
ferro make:error ValidationError

ferro make:resource

Generate an API resource for transforming models into structured JSON responses.

# Basic resource (generates struct with placeholder fields)
ferro make:resource UserResource

# Auto-append "Resource" suffix if not present
ferro make:resource User

# With model path for automatic From<Model> implementation
ferro make:resource UserResource --model entities::users::Model

Options:

OptionDescription
--model, -mModel path for auto-generating From<Model> implementation

Generated file: src/resources/user_resource.rs

#![allow(unused)]
fn main() {
use ferro::{ApiResource, Resource, ResourceMap, Request};

#[derive(ApiResource)]
pub struct UserResource {
    pub id: i32,
    // Add fields from your model here
    // #[resource(rename = "display_name")]
    // pub name: String,
    // #[resource(skip)]
    // pub password_hash: String,
}
}

Field attributes:

AttributeDescription
#[resource(rename = "name")]Rename field in JSON output
#[resource(skip)]Exclude field from JSON output

The #[derive(ApiResource)] macro generates the Resource trait implementation, which provides to_resource() and to_json() methods. When --model is specified, a From<Model> implementation is generated to map model fields to resource fields.

After generation, add pub mod user_resource; to src/resources/mod.rs.

See also: API Resources guide for the complete resource system documentation.

ferro make:scaffold

Generate a complete CRUD scaffold: model, migration, controller, and Inertia pages.

# Basic scaffold
ferro make:scaffold Post

# With field definitions
ferro make:scaffold Post title:string content:text published:bool

# Complex example
ferro make:scaffold Product name:string description:text price:float stock:integer

Field Types:

TypeRust TypeDatabase Type
stringStringVARCHAR(255)
textStringTEXT
integeri32INTEGER
biginti64BIGINT
floatf64DOUBLE
boolboolBOOLEAN
datetimeDateTimeTIMESTAMP
dateDateDATE
uuidUuidUUID

Generated Files:

src/
├── models/post.rs           # SeaORM entity
├── migrations/m*_create_posts_table.rs
├── controllers/post_controller.rs
frontend/src/pages/
├── posts/
│   ├── Index.tsx
│   ├── Show.tsx
│   ├── Create.tsx
│   └── Edit.tsx

ferro make:api

Generate REST API endpoints for a set of models.

# Generate API for specific models
ferro make:api User Post

# Generate API for all detected models
ferro make:api --all

# Skip confirmation prompt and exclude specific fields
ferro make:api User --yes --exclude password_hash,secret_token

# Include all fields, disabling auto-exclusion of sensitive field names
ferro make:api User --include-all

Options:

OptionDefaultDescription
modelsOne or more model names (positional)
--allfalseGenerate API for all detected models
--yes, -yfalseSkip confirmation prompt
--excludeComma-delimited list of field names to exclude from generated endpoints
--include-allfalseDisable auto-exclusion of sensitive field names

What it does:

  1. Detects models in src/models/ (or uses the provided list)
  2. Generates controller, resource, and route entries for each model
  3. Automatically excludes fields matching sensitive patterns: password, secret, token, hash, key, salt, private, credentials
  4. --exclude accepts comma-delimited values (e.g. password_hash,secret_token)
  5. --include-all disables the auto-exclusion list entirely

ferro make:api-key

Generate an API key with SHA-256 hashing.

# Generate a live API key
ferro make:api-key "Production Bot"

# Generate a test key
ferro make:api-key "CI Testing" --env test

Options:

OptionDefaultDescription
nameDisplay name for the key (positional, required)
--envliveKey environment: live or test

What it does:

  1. Generates a cryptographically random API key prefixed with fe_<env>_
  2. Hashes the key with SHA-256 (the hash is what gets stored)
  3. Prints the raw key once — it is not stored in plaintext and cannot be recovered
  4. Outputs a ready-to-run SQL INSERT statement and a Rust snippet for verifying keys in handlers

ferro make:lang

Create translation files for a locale.

ferro make:lang fr
ferro make:lang pt-br

Options:

OptionDescription
nameBCP 47 locale tag (positional, required; e.g. en, fr, pt-br, zh-hans)

Generated files:

  • lang/<locale>/validation.json — Validation error message translations
  • lang/<locale>/app.json — Application-level string translations

ferro make:policy

Create an authorization policy for a model.

ferro make:policy Post
ferro make:policy PostPolicy --model Post

Options:

OptionDefaultDescription
namePolicy name (positional, required)
--model, -mName without Policy suffixThe model this policy guards

Generated file: src/policies/<name>_policy.rs

#![allow(unused)]
fn main() {
pub struct PostPolicy;

impl PostPolicy {
    pub fn view(&self, user: &User, post: &Post) -> bool {
        true
    }

    pub fn create(&self, user: &User) -> bool {
        true
    }

    pub fn update(&self, user: &User, post: &Post) -> bool {
        user.id == post.user_id
    }

    pub fn delete(&self, user: &User, post: &Post) -> bool {
        user.id == post.user_id
    }
}
}

ferro make:projection

Create a service projection definition.

ferro make:projection user
ferro make:projection order --from-model

Options:

OptionDefaultDescription
nameProjection name (positional, required)
--from-modelfalsePopulate fields from the matching SeaORM model in src/models/

Generated file: src/projections/<name>.rs

ferro make:stripe

Scaffold Stripe billing integration.

# Basic Stripe integration
ferro make:stripe

# Include Stripe Connect scaffolding
ferro make:stripe --connect

Options:

OptionDefaultDescription
--connectfalseInclude Stripe Connect webhook and connect account ID field

Generated files:

  • src/stripe/mod.rs — Stripe module root
  • src/stripe/webhook.rs — Stripe webhook handler
  • src/stripe/listeners.rs — Stripe event listeners
  • src/stripe/connect_webhook.rs — Connect webhook handler (with --connect)
  • src/migrations/m<timestamp>_create_tenant_billing_table.rs — Billing table migration (with --connect)

ferro make:theme

Create a JSON-UI theme with Tailwind v4 semantic tokens.

ferro make:theme ocean
ferro make:theme corporate

Options:

OptionDescription
nameTheme name (positional, required)

Generated files:

  • themes/<name>/tokens.css — Tailwind v4 @theme block with 23 semantic token slots
  • themes/<name>/theme.json — Empty JSON object for intent template overrides

The tokens.css file uses the Tailwind v4 @theme directive and defines semantic color, typography, and shape tokens. The theme.json file is a placeholder for overriding how structural intents (Browse, Focus, Collect, etc.) render for this theme.

ferro make:whatsapp

Scaffold WhatsApp Cloud API integration.

ferro make:whatsapp

No flags. Generates a complete WhatsApp webhook and listener scaffold.

Generated files:

  • src/whatsapp/mod.rs — WhatsApp module root
  • src/whatsapp/webhook.rs — WhatsApp webhook handler
  • src/whatsapp/listeners.rs — WhatsApp message listeners

Database Commands

ferro db:migrate

Run all pending migrations.

ferro db:migrate

ferro db:rollback

Rollback the last batch of migrations.

ferro db:rollback

ferro db:status

Show the status of all migrations.

ferro db:status

Output:

+------+------------------------------------------------+-------+
| Ran? | Migration                                       | Batch |
+------+------------------------------------------------+-------+
| Yes  | m20240101_000001_create_users_table            | 1     |
| Yes  | m20240101_000002_create_posts_table            | 1     |
| No   | m20240115_143052_add_status_to_posts           |       |
+------+------------------------------------------------+-------+

ferro db:fresh

Drop all tables and re-run all migrations.

ferro db:fresh

Warning: This is destructive and will delete all data.

ferro db:seed

Run database seeders to populate the database with test data.

# Run all seeders
ferro db:seed

# Run a specific seeder
ferro db:seed --class UserSeeder

Options:

OptionDescription
--classRun only a specific seeder by name

How it works:

The command delegates to cargo run -- db:seed in your project, which executes the registered seeders. Seeders are Rust structs implementing the Seeder trait, located in src/seeders/.

If no seeders directory exists, the command will prompt you to create one with ferro make:seeder <name>.

See also: ferro make:seeder to generate new seeder files.

ferro db:sync

Synchronize the database schema and generate entity files.

# Sync entities from existing database
ferro db:sync

# Run migrations first, then sync
ferro db:sync --migrate

Options:

OptionDescription
--migrateRun pending migrations before syncing

This command:

  1. Discovers the database schema (tables, columns, types)
  2. Generates SeaORM entity files in src/models/entities/
  3. Creates user-friendly model wrappers with the Ferro Model API

ferro db:query

Execute a raw SQL query against the database.

# Simple SELECT query
ferro db:query "SELECT * FROM users LIMIT 5"

# Query with conditions
ferro db:query "SELECT id, name, email FROM users WHERE active = true"

# Count query
ferro db:query "SELECT COUNT(*) FROM posts"

Features:

  • Reads DATABASE_URL from .env file
  • Supports SQLite and PostgreSQL databases
  • Displays results in a formatted table
  • Handles NULL values gracefully
  • Shows row count after results

Example Output:

+-----+-------+-------------------+
| 1   | Alice | alice@example.com |
| 2   | Bob   | bob@example.com   |
+-----+-------+-------------------+

→ 2 row(s)

Use Cases:

  • Quick data inspection during development
  • Debugging database state
  • Verifying migration results
  • Ad-hoc queries without external tools

Deployment Commands

ferro do:init

Initialize DigitalOcean App Platform deployment. Generates .do/app.yaml only — CI and Dockerfile scaffolding are handled by separate commands (ci:init, docker:init).

ferro do:init           # write .do/app.yaml if missing
ferro do:init --force   # regenerate an existing spec

The GitHub repo is auto-detected from git remote get-url origin; the region is hardcoded to fra1 in the template (edit the file afterwards to change it). The command reads .env.production to enumerate env keys for a commented-out scaffold; if .env.production is missing the command fails hard.

Generated file:

  • .do/app.yaml — App Platform spec with sanitized app name, auto-detected github.repo, region: fra1, a services: web entry, a workers: entry per non-test [[bin]], and a commented-out envs: scaffold. No databases: block is emitted.

Options:

  • --force, -f — Overwrite an existing .do/app.yaml.

See do:init for the full page.

Docker Commands

ferro docker:init

Generate a production-ready Dockerfile and .dockerignore. The Docker build consumes the project Cargo.toml directly — no dual manifest is written. Developers working against an unpublished ferro checkout maintain an uncommitted [patch.crates-io] block in their project Cargo.toml.

ferro docker:init          # write Dockerfile + .dockerignore
ferro docker:init --force  # overwrite existing files

Options:

  • --force, -f — Overwrite existing Dockerfile / .dockerignore.

Generated files:

  • Dockerfile — multi-stage build, base image defaults to rust:slim-bookworm.
  • .dockerignore

[package.metadata.ferro.deploy] in Cargo.toml:

docker:init reads optional deploy metadata from the project's Cargo.toml:

[package.metadata.ferro.deploy]
runtime_apt = ["libpq5", "ca-certificates"]
copy_dirs = ["migrations", "templates"]
  • runtime_apt — additional apt packages installed into the runtime stage.
  • copy_dirs — extra directories copied from the builder into the runtime image.

ferro docker:compose

Manage Docker Compose services.

# Start services
ferro docker:compose up

# Stop services
ferro docker:compose down

# Rebuild and start
ferro docker:compose up --build

Scheduling Commands

ferro schedule:run

Run scheduled tasks that are due.

ferro schedule:run

This executes all tasks whose schedule indicates they should run now. Typically called by a system cron job every minute:

* * * * * cd /path/to/project && ferro schedule:run >> /dev/null 2>&1

ferro schedule:work

Start the scheduler worker for continuous task execution.

ferro schedule:work

This runs in the foreground and checks for due tasks every minute. Useful for development or container deployments.

ferro schedule:list

Display all registered scheduled tasks.

ferro schedule:list

Output:

+---------------------------+-------------+-------------------+
| Task                      | Schedule    | Next Run          |
+---------------------------+-------------+-------------------+
| CleanupExpiredSessions    | Daily 00:00 | 2024-01-16 00:00  |
| SendDailyReport           | Daily 09:00 | 2024-01-15 09:00  |
| PruneOldNotifications     | Weekly Mon  | 2024-01-22 00:00  |
+---------------------------+-------------+-------------------+

Storage Commands

Create a symbolic link from public/storage to storage/app/public.

ferro storage:link

This allows publicly accessible files stored in storage/app/public to be served via the web server.

Validation & Diagnostics

ferro api:check

Verify that the local API server is running and MCP-compatible.

# Check local API server for MCP integration
ferro api:check

# Custom URL and API key
ferro api:check --url http://localhost:8080 --api-key fe_live_xxx

# Custom OpenAPI spec path
ferro api:check --spec-path /api/docs/openapi.json

Options:

OptionDefaultDescription
--urlhttp://localhost:8080Base URL of the running API server
--api-keyAPI key for testing authenticated endpoints
--spec-path/api/openapi.jsonPath to the OpenAPI spec endpoint

What it does:

  1. Checks server reachability by sending a GET request to --url
  2. Fetches the OpenAPI spec from <url><spec-path>
  3. Validates the spec structure (version, paths, info object)
  4. Tests API key authentication if --api-key is provided
  5. Prints ferro-api-mcp configuration for connecting AI tools to this server

ferro projection:check

Validate service projection definitions.

Requires the projections feature. Build with cargo build --features projections or add projections to the default features in Cargo.toml before running this command.

# Validate all projections
ferro projection:check

# Validate a single projection by function name
ferro projection:check --name user_service

Options:

OptionDefaultDescription
--nameCheck only the named projection function

What it does:

  1. Discovers all projection definitions in src/projections/
  2. Validates that each projection's field types, intent hints, and visibility rules are well-formed
  3. Reports structural errors and type mismatches with file and line references

ferro validate:contracts

Validate Inertia TypeScript contracts against Rust handler props.

# Validate all contracts
ferro validate:contracts

# Filter by route prefix
ferro validate:contracts --filter /users

# JSON output for CI integration
ferro validate:contracts --json

Options:

OptionDefaultDescription
--filter, -fOnly validate routes matching this prefix
--jsonfalseOutput results as JSON (useful for CI)

What it does:

  1. Compares Rust InertiaProps structs with the generated TypeScript interfaces in frontend/src/types/
  2. Reports field name mismatches, type incompatibilities, and missing optional fields
  3. With --json, outputs a machine-readable result for use in CI pipelines

AI-Assisted Development

ferro mcp

Start the Model Context Protocol (MCP) server for AI-assisted development.

ferro mcp

The MCP server provides introspection tools that help AI assistants understand your Ferro application structure, including routes, models, controllers, and configuration.

ferro boost:install

Install AI development boost features.

ferro boost:install

This sets up configuration for enhanced AI-assisted development workflows.

ferro claude:install

Install Ferro Claude Code skills to enable /ferro:* slash commands in Claude Code.

# Install all skills
ferro claude:install

# Force overwrite existing skills
ferro claude:install --force

# List available skills without installing
ferro claude:install --list

Options:

OptionDescription
--force, -fOverwrite existing skill files
--list, -lList available skills without installing

Installed Location: ~/.claude/commands/ferro/

Available Skills:

CommandDescription
/ferro:helpShow all available Ferro commands
/ferro:infoDisplay project information
/ferro:routesList all registered routes
/ferro:route:explainExplain a specific route in detail
/ferro:modelGenerate a new model with migration
/ferro:modelsList all models with fields
/ferro:controllerGenerate a new controller
/ferro:middlewareGenerate new middleware
/ferro:dbDatabase operations (migrate, rollback, seed)
/ferro:testRun tests with coverage options
/ferro:serveStart the development server
/ferro:newCreate a new Ferro project
/ferro:tinkerInteractive database REPL
/ferro:diagnoseDiagnose errors using MCP introspection

Skills leverage ferro-mcp for intelligent code generation and project introspection.

Command Summary

CommandDescription
newCreate a new Ferro project
serveStart development server
generate-typesGenerate TypeScript types
cleanRemove build artifacts
make:actionCreate an action class
make:apiGenerate REST API endpoints for models
make:api-keyGenerate an API key
make:authScaffold authentication system
make:controllerCreate a controller
make:errorCreate a custom error
make:eventCreate an event
make:factoryCreate a model factory
make:inertiaCreate an Inertia page
make:jobCreate a background job
make:json-viewCreate a JSON-UI view (AI-powered)
make:langCreate translation files for a locale
make:listenerCreate a listener
make:middlewareCreate middleware
make:migrationCreate a migration
make:notificationCreate a notification
make:policyCreate an authorization policy
make:projectionCreate a service projection
make:resourceCreate an API resource
make:scaffoldCreate complete CRUD scaffold
make:seederCreate a database seeder
make:stripeScaffold Stripe billing integration
make:taskCreate a scheduled task
make:themeCreate a JSON-UI theme
make:whatsappScaffold WhatsApp integration
db:migrateRun migrations
db:rollbackRollback migrations
db:statusShow migration status
db:freshFresh migrate (drop all)
db:seedRun database seeders
db:syncSync database schema
db:queryExecute raw SQL query
do:initGenerate DigitalOcean App Platform spec (.do/app.yaml)
ci:initGenerate GitHub Actions CI workflow (.github/workflows/ci.yml)
doctorRun project health diagnostics (nine checks)
docker:initGenerate Dockerfile and .dockerignore
docker:composeManage Docker Compose
schedule:runRun due scheduled tasks
schedule:workStart scheduler worker
schedule:listList scheduled tasks
storage:linkCreate storage symlink
api:checkVerify API server and MCP integration
projection:checkValidate service projection definitions
validate:contractsValidate Inertia TypeScript contracts
mcpStart MCP server
boost:installInstall AI boost features
claude:installInstall Claude Code skills

Environment Variables

The CLI respects these environment variables:

VariableDescription
DATABASE_URLDatabase connection string
APP_ENVApplication environment (development, production)
RUST_LOGLogging level