The Faby CLI
One binary. The entire toolchain. faby is the compiler, interpreter, formatter, test runner, REPL, package manager and AI assistant for the Faby language — and it is live on NPM now.
#Overview
The Faby CLI follows the same philosophy as the language: zero configuration, one canonical way, machine-friendly output. Every command:
- works with no config file — conventions over configuration, always
- supports
--jsonfor structured output that agents and CI pipelines can parse - returns deterministic, documented exit codes (see Exit Codes)
- is offline by default — AI-powered commands are explicit, opt-in and clearly marked
| Command | Purpose |
|---|---|
| faby init | Scaffold a new Faby project |
| faby run | Execute a program with the interpreter (instant start) |
| faby build | Compile to native binary or WASM |
| faby check | Type-check, verify contracts, lint — without running |
| faby fmt | Format to the one canonical form |
| faby test | Run tests and contract-derived property checks |
| faby repl | Interactive session |
| faby explain | AI: explain any program in plain language |
| faby gen | AI: generate a flow from an intent description |
| faby review | AI: review changes for intent drift and contract gaps |
| faby add / remove | Manage dependencies (content-addressed, lockfile-first) |
| faby publish | Publish a package to the Faby index |
| faby doctor | Diagnose your installation and environment |
#Installation — NPM
The recommended way to install the Faby CLI is through NPM — the @fabycode/cli package is live. The v0.1 Genesis release ships the JavaScript runtime (the same engine as the browser playground); the platform-native binary replaces it transparently with v0.2.
# install globally (recommended) $ npm i -g @fabycode/cli # or run ad-hoc without installing $ npx @fabycode/cli run hello.fy # or add to a project as a dev tool $ npm i -D @fabycode/cli # verify the installation $ faby version faby 0.1.0 (genesis) — designed by claude-fable-5
Alternative install channels
# pin a specific version $ npm i -g @fabycode/cli@0.1.0 # shell installer without Node — ships with v0.2 "Pulse" $ curl -fsSL https://faby.codes/install.sh | sh # coming soon
NPM is where the agents already live. Every AI coding tool can npm i -g @fabycode/cli today without new infrastructure — which makes Faby instantly available to the systems it was designed for.
#Quick Start
$ npm i -g @fabycode/cli $ faby init hello && cd hello created hello/main.fy created hello/faby.lock $ faby run main.fy Hello, Faby. $ faby check ✓ 1 file · 0 errors · 2 contracts verified statically $ faby build --native → hello (1.9 MB, static, 412ms)
#Anatomy & Global Flags
Global flags work on every command:
| Flag | Description |
|---|---|
| --json | Emit machine-readable JSON instead of human output. Stable schema, versioned with the CLI. |
| --quiet, -q | Suppress everything except errors and requested output. |
| --verbose, -v | Show timing, cache decisions and internal phases. |
| --no-color | Disable ANSI colors (auto-detected for pipes and CI). |
| --offline | Hard-fail any command that would touch the network. |
| --version | Print version and exit. |
| --help, -h | Contextual help for any command or subcommand. |
With --json, every diagnostic carries a stable code, a byte-exact span, and a fix suggestion when one exists — so models can apply repairs without guessing.
#faby init
Scaffold a new project. Creates the minimal canonical layout — a main.fy and a faby.lock. No config file, because there is nothing to configure.
| Flag | Description |
|---|---|
| --lib | Library layout: src/lib.fy with a public flow and inline tests. |
| --agent | Agent starter: intent-annotated flow with use ai wired up. |
| --web | Web service starter: use http with routes and a health check. |
$ faby init my-agent --agent created my-agent/main.fy (intent-annotated agent starter) created my-agent/faby.lock → next: cd my-agent && faby run main.fy
#faby run
Execute a program with the tree-walking interpreter. Cold start under 50ms — built for tight agent loops and rapid iteration. Contracts are always enforced in run mode.
| Flag | Description |
|---|---|
| --watch, -w | Re-run on file change. State of the terminal is preserved between runs. |
| --trace | Print every flow entry/exit with timing — a built-in flame view for the terminal. |
| --cap <caps> | Capability allow-list: net, fs, env, proc, ai. Anything not listed is denied at runtime. Example: --cap net,fs. |
| -- args... | Everything after -- is passed to the program's args(). |
# run a crawler that may use the network but NOT the filesystem $ faby run crawler.fy --cap net -- https://faby.codes fetched 12 pages · 0 failures · 1.4s # watch mode while developing $ faby run server.fy --watch ▲ watching 3 files — last run OK (38ms)
Without --cap, a program gets no network, filesystem, process or AI access. Generated code runs in a deny-by-default sandbox — you grant exactly what it needs.
#faby build
Ahead-of-time compilation through the Faby IR. Produces a single static binary or a WASM module. Builds are deterministic: same input, same byte-identical output, on any machine.
| Flag | Description |
|---|---|
| --native | Compile to a static native binary for the host (default). |
| --wasm | Compile to a WASM module + generated JS/TS bindings. |
| --release | Full optimization. Roughly 3–8× faster output, longer compile. |
| --target <triple> | Cross-compile: linux-x64, linux-arm64, darwin-arm64, windows-x64, … |
| --contracts <mode> | full (default) keeps all runtime guards · proven keeps only unproven ones · off strips guards (requires --i-accept-the-risk). |
| -o <out> | Output path. Defaults to the flow file's name. |
# release binary for a Linux server, built on a Mac $ faby build server.fy --release --target linux-x64 -o dist/server ⠿ parse 12ms · check 31ms · prove 44ms · codegen 380ms → dist/server (2.3 MB, static, linux-x64) contracts: 17 proven statically, 3 runtime guards kept # WASM for the browser, with TypeScript bindings $ faby build engine.fy --wasm -o web/engine → web/engine.wasm (412 KB) · web/engine.d.ts · web/engine.js
#faby check
The full static pipeline without execution: parse, type-check, refinement inference, contract proving, lints. This is the command CI should run on every commit.
| Flag | Description |
|---|---|
| --intent | AI: verify each intent block still matches what its flow does; report drift with evidence. |
| --strict | Lints become errors; unproven contracts must be explicitly acknowledged. |
| --fix | Apply all safe, mechanical fixes (unused imports, canonical renames). |
| --watch | Continuous checking while you edit. |
$ faby check --strict --json | jq '.summary' { "files": 14, "errors": 0, "contracts": { "proven": 41, "guarded": 6 }, "intents": { "checked": 9, "drifted": 0 } }
#faby fmt
Formats code into the one canonical form. On already-valid code, faby fmt changes nothing — it is the identity function. There are no options to configure, which is the point.
| Flag | Description |
|---|---|
| --check | Exit 1 if anything would change; print a minimal diff. For CI. |
| --stdin | Format from stdin to stdout — for editor integrations. |
#faby test
Runs inline test blocks and auto-generates property-based tests from your contracts: every expect/ensure pair becomes a fuzzed property the runner attacks with generated inputs.
| Flag | Description |
|---|---|
| --filter <pattern> | Run only tests whose name matches the pattern. |
| --props <n> | Inputs generated per contract property (default 256). |
| --seed <n> | Fix the fuzzing seed for reproducible failures. |
| --fail-fast | Stop at the first failure. |
$ faby test --props 1000 ✓ 23 tests · 8 contract properties × 1000 inputs · 0 failures (2.1s) slowest: transfer.conservation (312ms)
#faby repl
An interactive session with the full standard library. Multi-line flows, type inspection with :t, and instant reload of your project's flows with :load.
faby> [1, 2, 3] |> map(_ * 10) |> sum 60 : Int faby> :t "ada@faby.codes" Text — hint: Email.parse(…) to lift into a semantic type faby> :load main.fy loaded 4 flows from main.fy
#faby explain AI
Plain-language explanation of any Faby program: what it does, which contracts protect it, where the risks are. Works on files you wrote — or on generated code you're about to trust.
| Flag | Description |
|---|---|
| --flow <name> | Explain a single flow instead of the whole file. |
| --audience | Tune the depth: dev (default), reviewer (risk-focused), exec (one paragraph, no jargon). |
| --model <id> | Model override. Defaults to claude-fable-5. |
#faby gen AI
Generate a flow from an intent. The output always arrives with the intent block attached, contracts proposed, and faby check already passed — generation and verification in one step.
$ faby gen "dedupe a list of emails case-insensitively, keep first occurrence" \ --sig "flow dedupe(xs: [Email]) -> [Email]" -o dedupe.fy → dedupe.fy · intent attached · 2 contracts proposed ✓ faby check passed before writing to disk
#faby review AI
Review a diff the way Faby thinks about code: does the change still satisfy each governing intent, are contracts weakened, did failure policies change. Designed to gate AI-generated pull requests.
| Flag | Description |
|---|---|
| --diff <ref> | What to review — a git ref/range (default: working tree vs HEAD). |
| --gate <list> | Fail (exit 3) if any listed dimension regresses: intent, contracts, caps. |
#faby add / remove
Dependency management is lockfile-first and content-addressed: faby.lock pins exact content hashes, so a build can never silently change because a registry did.
$ faby add markdown@1.2 + markdown 1.2.0 (sha256:9f2c…) → faby.lock audited: no caps requested beyond fs:read
Every package declares the capabilities it needs. faby add shows them before anything is written — a package that suddenly wants net in a patch release is a hard prompt, not a silent upgrade.
#faby publish
Publish a library to the Faby package index. Publishing runs the full gauntlet locally first: check --strict, test, fmt --check and a capability manifest review.
#faby doctor
Diagnoses the installation: binary integrity, PATH issues, Node bridge version, cache health, registry reachability. The first thing to run when something feels off.
$ faby doctor ✓ binary faby 0.1.0 (genesis), darwin-arm64, signature OK ✓ node bridge 18.19.0 via @fabycode/cli ✓ cache 142 MB, healthy ✓ registry reachable (89ms) no issues found
#Shell Completions
# zsh $ faby completions zsh > ~/.zfunc/_faby # bash $ faby completions bash >> ~/.bashrc # fish $ faby completions fish > ~/.config/fish/completions/faby.fish
#Environment Variables
| Variable | Description |
|---|---|
| FABY_HOME | Cache and toolchain root. Default: ~/.faby. |
| FABY_MODEL | Default model for AI commands. Default: claude-fable-5. |
| FABY_API_KEY | API key used by AI commands and the ai stdlib module. |
| FABY_OFFLINE | Set to 1 to behave as if --offline were always passed. |
| FABY_REGISTRY | Override the package registry URL (for mirrors / air-gapped setups). |
| NO_COLOR | Respected everywhere, per the no-color.org convention. |
#Exit Codes
Exit codes are part of the CLI's contract — stable across versions, safe to script against:
| Code | Meaning |
|---|---|
| 0 | Success. |
| 1 | Program error: the user's code failed (type error, failed contract, runtime fail). |
| 2 | Usage error: unknown command, bad flags, missing file. |
| 3 | Gate failure: --check / --gate / --strict conditions not met. |
| 4 | Capability denied: program requested an ungranted capability. |
| 5 | Network/registry failure (only possible without --offline). |
| 10 | Internal CLI fault — always a bug, please report it. |
#CI Integration
The canonical CI pipeline is three commands. With NPM as the install channel it drops into any existing Node-based CI without new tooling:
jobs: verify: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 20 } - run: npm i -g @fabycode/cli - run: faby fmt --check - run: faby check --strict --json - run: faby test --props 512
#Troubleshooting
faby: command not found after npm install
Your global npm bin directory is not on PATH. Run npm prefix -g and add its bin folder to PATH — or use npx @fabycode/cli as a zero-setup fallback.
AI commands fail with capability denied: ai
By design. Set FABY_API_KEY and grant the capability explicitly: faby run agent.fy --cap ai,net.
Build output differs between machines
It shouldn't — builds are deterministic. If you see a difference, run faby doctor and file a bug with both outputs; this is a release-blocking defect for us.
Anything else
Run faby doctor --json and open an issue, or ping @fabylanguage.