The xr CLI

xr is Larust’s artisan. Every command below also works as cargo run -p larust-cli -- <command> from inside the workspace checkout, if you’d rather not install it globally (see Installation).

  1. Project scaffolding
    1. xr new [path] [--auth] [--tauri] [--features <list>] [--workspace <path>]
    2. xr add tauri
  2. Generators (make:*)
  3. Running your app
    1. xr dev [--port <port>] [--debug]
    2. xr build [--fresh]
    3. xr route:list
    4. xr command:list
    5. xr <your-command-name>
    6. xr restart
    7. xr list
    8. xr kill [--id <pid>] [--all]
  4. Database
    1. xr migrate
    2. xr migrate:fresh [--seed]
    3. xr db:seed
    4. xr db:list / xr db:get <key> / xr db:put <key> <value> / xr db:forget <key>
  5. Background work
    1. xr queue:work
    2. xr schedule:work
  6. Deployment & lifecycle
    1. xr optimize
    2. xr deploy [--run] [--service] [--clean | --clean-all]
    3. xr run
    4. xr service:install / xr service:uninstall
    5. xr upgrade [--force] [--legacy] [--stop-running] [--fix]
    6. xr update
    7. xr release [--major|--minor|--patch]
    8. xr audit
  7. Converting an existing Laravel app
    1. xr convert <path> --out <dir> [--workspace <path>]
    2. xr convert --file <blade-path> --destination <xr-path>
  8. Next

Project scaffolding

xr new [path] [--auth] [--tauri] [--features <list>] [--workspace <path>]

Create a new app. Omit path for an interactive wizard that asks for everything below one question at a time instead.

Flag Meaning
--auth Scaffold session-based auth (User model, register/login/logout, guarded routes)
--tauri Also scaffold src-tauri/ (a native desktop build) - see Deployment & Desktop Apps
--features <a,b,c> Enable optional larust-support features: cashier-stripe, db, passport, pennant, permissions, reverb, sanctum, scout, shield, sitemap, socialite - see Optional Features for what each one does
--workspace <path> Resolve the framework’s crates as local path dependencies against a Larust checkout instead of the default version = "..." dependencies against the published crates - for a contributor scaffolding against local, possibly-unpublished framework changes

Every Larust crate is published to crates.io, so by default xr new needs no checkout at all - the generated Cargo.toml depends on the published crates directly. --workspace <path> is the explicit opt-out, not something auto-detected from where you happen to run the command.

xr add tauri

Retrofit Tauri desktop support onto an already-scaffolded app, run from inside it - the only xr add target today (the other optional features splice fixed text into main.rs/routes/*.rs only at generation time, so retrofitting them safely onto an already-hand-edited file isn’t attempted).

Generators (make:*)

Command Generates
xr make:controller <Name> [--resource] A controller; --resource adds all 7 RESTful methods
xr make:model <Name> [--migration] A model; --migration also writes a matching CREATE TABLE migration
xr make:request <Name> A #[derive(FormRequest)] struct
xr make:middleware <Name> A middleware function skeleton
xr make:policy <Name> [--user <Type>] A Policy<U> impl skeleton (--user if your Authenticatable isn’t User)
xr make:factory <Name> A Factory impl skeleton for <Name> in database/factories/ - see Testing
xr make:migration <name> An empty, correctly-numbered migration file
xr make:command <Name> A named CLI command (larust_support::console::Command) - see Named CLI Commands

Running your app

xr dev [--port <port>] [--debug]

Watches your app, rebuilds and hot-swaps it on save with zero dropped requests, and pushes a live-reload signal to any open browser tab. Binds the port immediately (serving a build-status page until the first build finishes), and leaves the last known-good build running if a later one fails. See Your First App.

--debug turns on maximum-verbosity mode for that one session, without editing .env: APP_DEBUG=true (full error detail pages instead of the generic 404/500), LOG_LEVEL=trace (the most verbose logging tier this framework supports), and RUST_BACKTRACE=full (full panic backtraces). Same “explicit flag overrides .env for this run only” precedent --port already sets for APP_PORT. Never use it for a real deploy - see Error Handling’s own APP_DEBUG warning.

On Windows, a rebuild can occasionally fail with failed to remove file ...\target\debug\<name>.exe / Access is denied even though nothing in xr dev’s own process tree holds that file open - almost always a brief antivirus scan of the freshly-linked binary. xr dev already retries a few times automatically before reporting failure, so this is usually invisible; see docs/GOTCHAS.md’s entry on it for the full story (shared root cause with xr upgrade’s identical failure, and that command’s --fix flag).

xr build [--fresh]

Builds (or rebuilds) an app’s frontend assets standalone (npm run build, the same step xr deploy runs before publishing) - a no-op if the app has no node_modules/. --fresh clears Vite’s dependency cache and public/build/ first, for when a stuck build needs a clean slate.

xr route:list

Prints every registered route (method, path, name).

xr command:list

Prints every registered named command’s name and description.

xr <your-command-name>

Any name that does not match one of the fixed subcommands is forwarded to your routes/console.rs::commands() registry. For example:

xr report:posts --format=json

A name matching nothing at all is a loud error, not a silent fall-through into starting the web server.

xr restart

Asks an already-running app to perform a zero-downtime restart handoff - a new process takes over the listening socket before the old one starts draining, so in-flight requests finish and no new connection is ever refused.

xr list

Lists every xr dev session currently running on this machine - PID, app name, port, uptime, and directory. Useful once you’ve got more than one app’s xr dev going at the same time and need to tell them apart.

xr kill [--id <pid>] [--all]

Stops the xr dev session tied to the current directory: sends the currently-running server the same graceful STOP signal xr dev’s own Ctrl+C handler uses, then ends the xr dev watcher process itself. With --id <pid> (the id xr list prints), stops a specific session instead, regardless of which directory you run it from.

--all stops every other running xr process on the machine outright - not just ones xr list knows about. xr list/--id only ever find a session that successfully registered itself at startup; something else entirely (a different terminal’s still-open xr dev, a hung xr deploy --run, any other stray xr invocation) can still be holding the installed xr binary open with nothing in xr list to explain it - exactly the situation that leaves xr upgrade failing with a confusing “Access is denied” on Windows and no obvious way to resolve it. --all finds those by executable name directly from the OS process list instead of relying on that registry, gracefully stopping any served app it recognizes along the way and forcefully terminating everything else.

Database

xr migrate

Runs every pending migration in database/migrations/, in order.

xr migrate:fresh [--seed]

Drops every table (except the framework’s own sessions table) and reapplies every migration from scratch - the honest alternative to a rollback this framework can’t actually offer (migrations have no down()). See Migrations & the Query Builder. --seed also runs database/seeders afterward.

xr db:seed

Runs database/seeders::run() against the current database - see Testing.

xr db:list / xr db:get <key> / xr db:put <key> <value> / xr db:forget <key>

Manage the embedded key-value store from the command line (requires the db optional feature). db:put’s value is parsed as JSON when possible (numbers, booleans, quoted strings), otherwise stored as a plain string.

Background work

xr queue:work

Starts a worker that claims and processes queued jobs until stopped. See Events, Queues, Scheduling & Commands.

xr schedule:work

Runs due scheduled tasks once a second until stopped.

Not safe to run as more than one process against the same app, unless a task explicitly opts into .on_one_server(). See Task Scheduling.

Deployment & lifecycle

xr optimize

Builds production artifacts without publishing or restarting an app: frontend assets when the app has a node_modules/ pipeline, then Cargo’s optimized release binary. Larust compiles routes, templates, and framework code ahead of time, so there is no mutable runtime code cache to generate; this is the safe Rust equivalent of Laravel’s optimization prebuild. Use it in CI or before a deploy, then run xr deploy when you are ready to publish.

xr deploy [--run] [--service] [--clean | --clean-all]

Builds and publishes a release according to DEPLOY_TYPE (.env, "web" by default): cargo build --release, publish to storage/releases/, then the same zero-downtime restart handoff xr restart uses against whatever’s already running. DEPLOY_TYPE=app instead runs cargo tauri build from src-tauri/. --run cold-starts the freshly published release in the background if nothing is currently running to hand off to (the very first deploy) - no effect otherwise. --service does the same, but via xr service:install (a real systemd unit) instead of a bare background process - crash- and reboot-safe from the first deploy, no separate step needed; wins over --run if both are given.

--clean runs cargo clean --release only after a successful web deployment, preserving published releases and development caches. --clean-all runs plain cargo clean instead, removing debug dependencies and all other Cargo target artifacts too. Choose one flag. The next deploy rebuilds dependencies; --clean-all also forces development rebuilds. Neither reduces peak build space or clears node_modules//Cargo downloads. Shared target directories share cleanup too. Desktop deployments reject both flags to preserve bundles. Cleanup errors report failure without rolling back the published release. See Deployment & Desktop Apps.

xr run

Starts the currently-published release (storage/releases/current, written by xr deploy) if nothing is already listening on the app’s own port - a no-op, not an error, if something already is. For an xr deploy run without --run/--service (publishing and starting as separate, independently-auditable steps), or for starting a release back up after it was stopped (xr kill, a crash with no xr service:install in place) with no rebuild or republish involved. Errors clearly if nothing has been published yet (“run xr deploy first”).

xr service:install / xr service:uninstall

Registers (or unregisters) the app as a systemd service - Linux only - so it restarts on an actual crash and starts automatically on boot, closing the gap xr deploy --run alone leaves open (it starts the app once; nothing ties it to a reboot). Run from the app’s own directory, on the machine serving it, after at least one xr deploy - or skip this as its own step and just pass --service to xr deploy directly. See Deployment & Desktop Apps for why this uses Restart=on-failure, not Restart=always.

xr service:install --simple <binary path> registers a minimal deployment instead - just a compiled binary and an .env file, with no xr deploy-managed storage/releases/current to point at. Run it from wherever the binary and .env actually live; see Deployment & Desktop Apps for what it generates and why it also sets LARUST_APP_ROOT in the unit file.

xr upgrade [--force] [--legacy] [--stop-running] [--fix]

Upgrades the xr binary itself (not your app - see xr update for that). By default, reinstalls larust-cli from crates.io (cargo install larust-cli) - matching an xr you got via cargo install larust-cli in the first place. --force reinstalls unconditionally (cargo install larust-cli --force), even if you’re already on the latest version; without it, cargo install already upgrades on its own whenever a newer version exists, and no-ops otherwise.

If another running xr process is holding the installed binary open, cargo install’s own last step (replacing that binary) fails with “Access is denied” on Windows - even when xr list shows nothing running, since that only ever tracks xr dev sessions that registered themselves successfully, not every xr invocation. xr upgrade checks for this upfront and refuses with a clear message naming the pids involved; pass --stop-running to have it stop them itself first (the same mechanism as xr kill --all) and then proceed.

That same “Access is denied” can also show up with no other xr process involved at all - most often Windows Defender (or another antivirus/EDR product) briefly locking the freshly-built .exe while it scans it. xr upgrade already retries automatically a few times before giving up. If it still fails, --fix excludes .cargo\bin from Windows Defender’s real-time scanning (needs an elevated/Administrator shell - if xr isn’t already running as one, it prints the exact Add-MpPreference command to run yourself) and retries once more. Windows-only; a no-op anywhere else.

--legacy switches to the original mechanism instead, for an xr built from a real Larust checkout (install.sh/install.ps1/cargo install --path): fetches, fast-forwards the current branch only if there are new upstream commits, then reinstalls from that checkout. Does nothing if already up to date. --force in this mode skips the freshness check and just reinstalls from the checkout’s current state - the “repair” equivalent of re-running install.sh/install.ps1.

“The checkout it was built from” (used only in --legacy mode) is baked into the binary at cargo install --path time, which won’t be right forever - a moved checkout, an xr binary shared across machines, or a deployment whose layout never matches wherever it happened to get built. Set LARUST_CHECKOUT_ROOT (a real environment variable, or a line in a .env file in whatever directory you run xr upgrade --legacy from) to point it somewhere else without rebuilding xr itself.

xr update

Updates the current app’s Cargo dependencies within their declared version constraints (composer update’s equivalent) - not xr itself.

xr release [--major|--minor|--patch]

Prepares a release of the current app - a thin front-end over plain git, the same shape xr audit/xr update already use over cargo audit/cargo update. Refuses to run with an unclean working tree. With a bump flag, rewrites Cargo.toml’s [package] version (X.Y.Z only - no pre-release/build-metadata handling) and commits that change as chore: release vX.Y.Z; with no flag, tags whatever version is already there. Builds release notes from every commit since the last tag (grouped by this repo’s own commit-type convention - feat/fix/ refactor/perf/docs/test/ci/chore; anything else lands under “Other” rather than being dropped), then creates a local, annotated vX.Y.Z tag with those notes as its message.

Never pushes anything, and never deploys anything. Prints the exact git push && git push origin vX.Y.Z to run yourself when ready, plus a reminder that xr deploy on the server is still the separate next step

  • this command only prepares the tag. Wiring a server to deploy automatically on push (a bare git remote + post-receive hook) is a separate, already-well-understood pattern this doesn’t attempt to reinvent - see Deployment & Desktop Apps.

xr audit

Runs cargo-audit over the resolved workspace lockfile, checking for known security advisories.

Converting an existing Laravel app

xr convert <path> --out <dir> [--workspace <path>]

Converts a whole Laravel app into a fresh, empty <dir> - refuses to run if <dir> already exists and isn’t empty (no incremental/merge support; re-running on an already-converted, hand-edited project needs a new directory). See Converting a Laravel App.

Like xr new, the generated app depends on the published crates.io crates by default - --workspace <path> is the same contributor-only opt-out, resolving local path dependencies against a Larust checkout instead.

xr convert --file <blade-path> --destination <xr-path>

Re-converts a single .blade.php template in isolation (overwriting <xr-path> if it exists) - for pulling one template through a converter fix without redoing the whole project.

Next

Testing covers TestClient and the rest of larust-testing.