Quick Start
Scaffold, run, and extend a typed Crust CLI.
Create a new project
Create a project
npx create-crust@latest my-cli
cd my-cliPick a package manager once; the tabs remember it across the docs. create-crust detects the package manager that launched it and installs dependencies with the same one. It also asks which runtime the project develops and builds for: Bun (the default), Node.js, or Deno; pass --runtime bun|node|deno to skip the prompt. Deno projects are the exception: they always install with deno install, whichever package manager launched the scaffolder. The scaffolder creates:
Run it
The generated dev script runs src/cli.ts with the runtime you picked (bun run, node, or deno run -A), whichever package manager installed the dependencies.
npm run dev -- Ada --greet Welcome
# Welcome, Ada!The help() extension generates usage from the same definitions:
npm run dev -- --helpmy-cli - A CLI built with Crust
Usage:
my-cli [name] [options]
Arguments:
[name] Your name [default: "world"]
Options:
-g, --greet Greeting to use [default: "Hello"]
-v, --version Show version number
-h, --help Show helpAdd a subcommand
Chain .command() onto the root:
import { Crust } from "@crustjs/core";
import { help, version } from "@crustjs/extensions";
import pkg from "../package.json" with { type: "json" };
const app = new Crust("my-cli", { description: "A CLI built with Crust", version: pkg.version })
.extend(version(), help())
.args({
name: "name",
type: "string",
description: "Your name",
default: "world",
})
.flags({
name: "greet",
type: "string",
description: "Greeting to use",
default: "Hello",
short: "g",
})
.action(({ args, flags, stdout }) => {
stdout(`${flags.greet}, ${args.name}!`);
})
.command("build", (command) =>
command
.flags({ name: "minify", type: "boolean", description: "Minify output" })
.action(({ flags, stdout }) => stdout(`minify: ${flags.minify ?? false}`)),
);
await app.execute();
npm run dev -- build --minify
# minify: true
npm run dev -- Ada
# Hello, Ada!my-cli build routes to the subcommand; anything else still reaches the root action. Inline .command() suits app-local commands. Move to defineCommand and split files when a command needs its own file or reuse.
Build it
npm run build
npm run start Adacrust build stages the publishable npm package(s) in .crust/: Bun and Deno projects get one standalone binary per platform behind a Node launcher at .crust/root/bin/my-cli.js; Node projects get one JavaScript bundle at that path. npm run release publishes them. See Build and distribution for cross-platform targets and the .crust/ layout.
Both create-crust and crust CLI are built with crust. If you would like to see crust in
action. Check out the crust repo on GitHub
Add to an existing project
Install
npm install @crustjs/core @crustjs/extensions
npm install -D @crustjs/crust@crustjs/crust is only needed for crust build. Skip it if you bundle another way or run the source directly. Deno projects install with deno add npm:; see Runtime support.
Configure package.json
{
"private": true,
"type": "module",
"crust": { "runtime": "bun" },
"bin": { "my-cli": "src/cli.ts" },
"scripts": {
"dev": "bun run src/cli.ts",
"build": "crust build",
"release": "crust publish",
"start": "bun .crust/root/bin/my-cli.js"
}
}This is the Bun shape; dev runs the TypeScript source with Bun regardless of which package manager installed the dependencies. bin names the command and points at its source entry: crust build compiles it into .crust/, and npm link exposes the command locally by running the source, so start src/cli.ts with #!/usr/bin/env bun. private keeps a stray npm publish from uploading the source; crust publish ships the staged packages, which are not private. Node and Deno projects set "crust": { "runtime": "node" } or "deno" and use their own dev and start values; see Set up package.json.
Configure tsconfig.json
The scaffolder ships this config for Bun. Match types, module, and moduleResolution if you merge into an existing one; the rest is strictness. Node projects use "types": ["node"]; Deno projects use "lib": ["ESNext", "deno.window"] and "types": [] because deno check reads this file and a supplied lib replaces Deno's own globals.
{
"compilerOptions": {
"lib": ["ESNext"],
"types": ["bun"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true
},
"include": ["src"]
}Next steps
The Guide walks through each layer of a Crust application, from arguments and flags to Extensions, Contexts, testing, and distribution. Pick the topic you need next.