Start

Clamp CLI Available

One command for the whole loop: create a project, build and run it, and ask the architecture questions — what is in this process, why is this module here, is this composition valid — with answers people and tools read the same way.

Install#

curl -fsSL https://rustclamp.com/install.sh | bash

Linux or macOS. The installer installs Rust first if it is missing (your projects need it), downloads the prebuilt clamp from the latest release and verifies its SHA-256 checksum, and adds ~/.cargo/bin to your zsh or bash startup file. Open a new terminal, or reload the startup file, and clamp works from any folder. From a source checkout you can also run cargo install --path rustclamp/tooling.

Prebuilt binaries cover Linux x86_64 and macOS on Intel and Apple silicon. On any other platform, or if the download fails, the installer builds clamp from main instead. A Windows build is on the releases page.

Create a project#

$ clamp init hello-clamp
Created RustClamp blank project at hello-clamp
Next: cd hello-clamp && cargo run

$ cd hello-clamp && clamp run
Hello from Clamp!

The default scaffold is the smallest Clamp application — Pico — with the facade taken from its public Git repository until packages are on crates.io:

use rustclamp::prelude::*;

fn main() {
    Clamp::run(|| println!("Hello from Clamp!"));
}

Pick a different starting point with one flag:

FlagCreatesNeeds
--blankPico: one closure in src/main.rs. The default.Rust
--appA composition root in src/app.rs and a sample health module under src/modulesRust
--tuiAn interactive terminal loop; type quit to exitRust
--webA web app: Rust routes and server, a Vite+ and Tailwind 4 frontend, and route testsRust, Node.js and npm

Every project is its own Cargo workspace, so creating one inside another workspace never changes that workspace. Each also gets a cargo dev alias for cargo run and a Procfile.dev for clamp dev. --template application and --template hello-world still work as aliases for --app and --blank.

Run clamp with no arguments for a welcome menu: create a project, check, test, build or run it, inspect architecture JSON, or show help.

Web projects Prototype#

$ clamp init my-site --web
Created RustClamp web project at my-site
Next: cd my-site && clamp dev

$ cd my-site && clamp dev
[clamp] installing frontend dependencies: npm install
[clamp] started vite: npx vp build --watch
[clamp] started app: cargo run
[app] Serving on http://127.0.0.1:8080

The layout follows Laravel's, so each file has an obvious home. Everything the app is made of lives in app/:

PathHolds
app/lib.rsThe map: declares every Rust module, so there are no mod.rs files, and exposes routes() to the tests
app/main.rsweb::App { … }.run(routes): loads .env, starts logging, runs migrate/db:seed when given, links public/storage and serves
app/config/app.rsTyped settings, such as API_PER_MINUTE
app/http/controllers/Handlers. controller.rs is the base: each controller starts with use super::controller::*;
app/http/middleware/Functions that wrap requests; request_log.rs logs each one
app/http/requests/, app/models/Form input with its validation, and the app's data. Empty at first.
app/routes/web.rs, app/routes/api.rsPages and JSON endpoints. GET / is the welcome view and GET /api/health returns JSON.
app/resources/views/Pages, one Vite entry each, starting with welcome.html. errors/4xx.html and errors/5xx.html are the error pages; they get {{ status }} and {{ reason }}. Add errors/404.html to give one code its own page.
app/resources/css/, app/resources/js/Styles and scripts. app.css imports Tailwind.
.envThis machine's settings, git-ignored; .env.example lists the keys
storage/logs/Written at runtime: app.log. Git-ignored.
public/The web root for your own static files. public/build/ is Vite's output and is git-ignored.
tests/Route tests; run them with cargo test

Each new file is declared in lib.rs. Routes read like Laravel's route files:

use rustclamp::web::Router;

pub fn routes(router: Router) -> Router {
    router.view("/", "welcome")
}
pub fn routes(router: Router, settings: &Settings) -> Router {
    let throttle = settings.per_minute(settings.api_per_minute);
    router.group(|api| api.middleware(throttle.middleware()).up("/api/health"))
}

.view answers a GET with a built view; .get, .post and .route take any handler from a request to a response. Paths match in order; a {name} segment, as in /blog/{slug}, matches any one segment and is read with request.param("slug"). A GET that matches no route serves the file of that name from public/, so assets and robots.txt need no routes; anything else is 404. Tests call the same router without a server:

use rustclamp::web::testing::Client;

#[test]
fn health_returns_json() {
    let (routes, _db) = my_site::app().test("");
    assert_eq!(Client::new(&routes).get("/api/health").status, 200);
}

The server lives in RustClamp, not in the project: rustclamp::web, the facade's optional web feature, using only the Rust standard library. It picks the port from PORT or the first free one from 8080, parses requests, serves views from public/build/views and static files from public/, and refuses paths that would leave public/. It answers on a fixed pool of WEB_THREADS threads (default 32), so one slow request does not hold up the rest. Each thread holds at most one request in memory, so with 10 MiB uploads the worst case is 32 × 10 MiB. Behind nginx, which buffers slow clients, that is plenty for sites and internal tools.

What a web app needs beyond routing ships in RustClamp, so the project holds only its own code:

  • render("post", &[("title", &title)]) renders a built view with data: {{ title }} prints it escaped (see Views).
  • security_headers: middleware adding a strict Content-Security-Policy, HSTS, nosniff, X-Frame-Options: DENY, a strict referrer policy and a Permissions-Policy. New projects turn it on in lib.rs.
  • Sessions and csrf(): server-side sessions, flash values with session.take, and a 419 for a form without its token.
  • Throttle::per_minute(5): per-client rate limiting with 429 and Retry-After; .trust_forwarded(true) behind nginx.
  • rustclamp::config reads .env and the environment; rustclamp::log writes storage/logs/app.log, set by LOG_LEVEL and LOG_CHANNEL.
  • error(404) renders the app's error view, or a built-in page.

Development server#

clamp dev starts every line of Procfile.dev at the same time, with each output line prefixed by its name, like concurrently without Node. A web project's file:

vite: npx vp build --watch
app: cargo run
  • Add a line for anything else to run alongside, in name: command form.
  • A command that finishes cleanly drops out; one that fails stops the rest, and clamp dev exits with its status. Ctrl-C stops everything.
  • If there is a package.json and no node_modules, clamp dev runs npm install first.
  • Vite rebuilds on save; refresh the browser to see changes. For Rust changes, restart clamp dev, or use watchexec -r -e rs -- cargo run as the app line.

Update clamp#

clamp self-update

Reinstalls clamp from the source checkout it was built from, or from the latest main on GitHub when that checkout is gone. clamp global-update does the same.

Cargo commands#

clamp check --all-targets
clamp test
clamp build --release
clamp run --example my_app

check, test, build and run pass their arguments and exit status straight through to Cargo in the current directory.

Inspect an application#

Inspection reads a document your application exports from its resolved process projection. It never parses source code, initializes the application, opens connections or starts services.

clamp inspect architecture.json
clamp tree architecture.json --process web
clamp graph architecture.json --json
clamp why architecture.json storage.postgres --process web
clamp doctor architecture.json
CommandAnswers
inspectEverything in the document: processes, modules, providers, contributions and structural counts
treeWhat a process contains, as the path from its roots
graphModule and capability relationships, for tools and diagrams
whyWhy a module is in a process: the inclusion path and the provider selection that put it there
doctorWhether the composition is valid, with a diagnostic for each problem

The application exports the document with inspection_document(&[&projection]); one document can hold several processes of one application, and --process ID picks one. The reference examples 04, 05, 09 and 11 export real projections, and the check runner compares the CLI's output against them.

JSON for tools#

Every inspection command takes --json. The document is versioned:

  • schema_version: 1, an application ID and a status. A resolved document has a processes array; an invalid one has a process ID and diagnostics.
  • Diagnostics carry a stable code, a severity, a human message and structured context.
  • Stable output. Semantic IDs, sorted collections and resolved inclusion paths, so diffs mean something.
  • Forward compatible. Consumers keep unknown codes and fields; a changed meaning or shape gets a new schema version, and clamp rejects versions it does not know.

Counts are descriptive structure, not estimates of memory or runtime cost. Configuration values, lifecycle outcomes, message delivery, transaction and device state are outside the schema.

Exit status#

CodeMeaning
0Valid document
1doctor found an invalid composition
2Command, file or schema error

Help#

$ clamp help
Usage:
  clamp init <project-name> [--blank|--app|--web|--tui]
  clamp <inspect|tree|graph|why|doctor> FILE [MODULE] [--process ID] [--json]
  clamp dev
  clamp self-update
  clamp <check|test|build|run> [Cargo arguments]