CLI Reference

B4.run ships a single b4 binary with seventeen commands: add, approvals, build, check, client-tools, dev, docs, eval, inspect, memory, routes, run, start, test, threads, typegen, and verify. Most commands read b4.config.ts from the current working directory or from --cwd <path>. b4 dev always uses the current working directory.

Running the CLICopy link to section: Running the CLI

Always run B4.run through the b4 bin: npx b4 <command>, pnpm exec b4 <command> or a package.json script. @b4run/cli installs the bin into node_modules/.bin, and it resolves wherever the package lives.

b4 checkCopy link to section: b4 check

b4 check validates the app structure and configuration.

text
b4 check

It loads b4.config.ts, resolves the app root, runs discoverRoutes, and parses each route's tool definitions with discoverToolDefinitions. It reports:

  • A b4.config.ts that fails to load.
  • An app root package.json without "type": "module" (B4_E1006). B4.run loads b4.config.ts and every route index.ts as ES modules. Without the field, Node compiles them as CommonJS, and B4.run won't recognise a route exported with export default agent(...).
  • A route directory whose index.ts has no recognisable export (B4_E1007), with the file path and the exports it did find. If a nested package.json without "type": "module" made Node load the module as CommonJS, the message names that file. To keep a directory that isn't a route out of discovery, prefix it with _.
  • A route whose index.ts exports more than one of agent, workflow, graph, chain (B4_E1008), with the file path and the kinds it exported. For both codes, b4 check lists every offending route at once. When an app has both, it lists the unrecognised ones first, with a count of the multi-kind ones.
  • A tool file that fails to parse.
  • An agent route tool whose name starts with client_ (B4_E1001). That prefix is reserved for client-provided tools, and route preparation refuses it at run time too.
  • A stale build manifest (see below).
  • A memory.distill.retry with a key other than maxAttempts, or a maxAttempts that isn't a whole number of at least 1; an unknown key in memory.distill; a non-object memory or memory.distill; and a near miss on memory itself, such as Distill, distil, or a retry or maxAttempts placed there (B4_E1009). b4 memory consolidate and reflect refuse the same config before building a model.
  • An agentsMd that isn't an object, an unknown key in it such as writeable, or a writable that isn't a boolean (B4_E1010). Route preparation refuses the same config.
  • An unknown build.targets entry and, when "hono" is one of them, every feature that target cannot serve (B4_E1005). b4 build applies the same gate, so you learn about all of them before you build.

When .b4/build/modules.mjs exists (the node target writes it), b4 check compares its routes with the routes on disk. A mismatch, such as a route renamed since the last build, fails with the missing and extra route ids and asks you to re-run b4 build.

When "hono" is a configured target, b4 check compares .b4/build/modules.edge.mjs the same way. It's a separate artifact with its own copy of every route's static imports, and an app that builds for hono alone has no modules.mjs. A missing manifest is fine, and so is a leftover edge manifest when hono isn't a target.

The output is a B4.run app is valid: N routes discovered. line followed by a per-route - <pathname> (<kind>) summary. On any violation, it exits non-zero with a detailed message.

Troubleshooting importsCopy link to section: Troubleshooting imports

When a route, tool, or config module fails to load with the ESM error does not provide an export named X, B4.run prints the offending package with a likely cause and fix, in place of the raw SyntaxError. There are two usual causes:

  • An older @langchain/core got hoisted. Run npm ls @langchain/core to find the duplicate, then upgrade or dedupe so the installed version satisfies B4.run's peer range (^1.2.12).

  • A CommonJS dependency imported with named bindings. Under B4.run's ESM resolver a CommonJS package only has a default export, so import { thing } from "x" fails. Use a default import and destructure instead:

    ts
    import pkg from "x"
    const { thing } = pkg

    Or import the package's ESM build if it ships one.

b4 verifyCopy link to section: b4 verify

b4 verify runs five checks in one call: app, routes, typegen, deps and runtime. Run it before b4 dev, b4 start, or a deploy. A green b4 verify means "this app will boot in this environment."

text
b4 verify
b4 verify --json

Flags:

  • --cwd <path>: operate on a different app root.
  • --json: print a structured report ({ status, appRoot, checks, counts }) in place of the text output. A failed check carries error.message, plus error.code when the failure has an error-code entry (for example B4_E1006 for an app root that isn't an ES module). Text mode shows the same code in a [B4_Exxxx] See <docs> footer.
  • --env-file <path>: path to a .env file (overrides b4.config.ts env and the default ./.env).

The deps check looks for missing packages and missing env vars. It's provider-aware: it works out which API-key env vars to look for from the providers your routes use. An Anthropic-only app needs ANTHROPIC_API_KEY, an OpenAI app needs OPENAI_API_KEY, a multi-provider app needs both, and a local Ollama app needs none. A missing key is only a warning, because the runtime environment may supply it.

The runtime check gates environment readiness:

  • Node: asserts the running Node version is at least 24.0.0, the active LTS line (it bundles npm 11 or later and ships node:sqlite unflagged). An older version fails verify with a non-zero exit.
  • Docker: runs only when b4.config.ts configures a sandbox provider. It runs the provider's daemon preflight and fails if the daemon is unreachable.

See Deployment for the recommended workflow.

b4 routesCopy link to section: b4 routes

b4 routes lists every route B4.run discovered, with its pathname.

text
b4 routes
b4 routes --json

Output:

text
Discovered 2 B4.run routes in /path/to/app
/navlog -> src/app/navlog/index.ts
/admin/users -> src/app/(internal)/admin/users/index.ts

Use it to confirm that route groups and dynamic segments parse the way you expect.

b4 typegenCopy link to section: b4 typegen

b4 typegen regenerates .b4/b4.generated.d.ts plus the per-route .b4/routes/<routeSlug>/tools.json and .b4/routes/<routeSlug>/state.json manifests.

text
b4 typegen

The success log reports route, tool-schema, and stateful-route counts. b4 build reads the tools.json files to emit LangGraph entries.

Tool input types compile with the app's own tsconfig.json (the one nearest the app root, honoring extends, paths, and baseUrl). If a declared input type resolves to any, say through a path alias the tsconfig doesn't declare, the command fails and names the tool file and the unresolved type. It won't write an empty schema.

b4 buildCopy link to section: b4 build

b4 build writes deployment artifacts for each target in build.targets (default: ["node", "langsmith"]).

text
b4 build
b4 build --clean
b4 build --out-dir dist/vercel

Flags:

  • --cwd <path>: operate on a different app root.
  • --clean: wipe .b4/build/ before writing.
  • --out-dir <dir>: where the vercel target publishes its Build Output tree, relative to the app root (default .vercel/output). It overrides build.vercel.outDir for this run, and it's an error when "vercel" isn't a configured target.

Here's what each target writes:

  • node: .b4/build/server.mjs, which boots serveRuntime (the real B4.run runtime), and a hardened Dockerfile in the app root (or in .b4/build/ if the app root already has one). Run the build output with node .b4/build/server.mjs (the generated Dockerfile's entry point) or docker build/docker run. b4 start doesn't use the build output: it serves the app's source tree, so it reads route modules, middleware and the thread access policy from source.
  • langsmith: .b4/build/langgraph.json plus per-route entry files under .b4/build/<routeSlug>.ts, which you hand to LangSmith. The JSON includes graphs (keyed by <routeId>#<kind>), dependencies: ["."], env (.env.example if present, else .env), and node_version: "22". For an agent route, the entry imports the default agent() descriptor, turns it into a LangGraph graph, and wires in every tool B4.run discovered for the route.
  • hono (opt-in): .b4/build/app.mjs, a Hono app around B4.run's web-standard fetch handler, export defaulted for Cloudflare Workers, Vercel, or Bun. It also writes .b4/build/modules.edge.mjs (the module manifest, free of Node built-ins), .b4/build/stores.mjs (a per-request Postgres store factory), and a wrangler.toml scaffold if the app root has none. Deploy with wrangler deploy.
  • vercel (opt-in): a Vercel Build Output API tree. See below.

Pick the targets with build.targets in b4.config.ts, for example { build: { targets: ["node"] } }. The list replaces the defaults, so an app that also deploys to the edge names both: { build: { targets: ["node", "hono"] } }. Target-specific settings sit next to the list, for example { build: { targets: ["vercel"], vercel: { outDir: "dist/vercel" } } }.

The vercel targetCopy link to section: The vercel target

The tree holds config.json and the runtime Node 24 function at functions/b4.func/ (the self-contained index.mjs plus its .vc-config.json). The function serves the same edge subset as hono. The tree lands in .vercel/output, where Vercel deploys from, unless build.vercel.outDir (or --out-dir) points elsewhere.

With build.vercel configured, the target also:

  • copies a static directory into static/,
  • bundles each declared function into functions/<name>.func/, and
  • orders routes as your routes, the filesystem, the runtime, then the SPA fallback.

The runtime function is named b4.func (or whatever functionName says), so it never shadows static/index.html.

The target stages and validates the whole tree, then swaps it in with one rename, so the output directory can't contain the app root. The validator checks that config.json is version 3 and still routes to the runtime function, so a composed config with extra routes and functions passes.

The target also checks the app-root vercel.json for a buildCommand that runs b4 build and fluid: true:

  • With no vercel.json, it writes a recommended one.
  • If your vercel.json is missing either setting, it prints a warning and writes the recommended file to .b4/build/vercel.json for reference.
  • It fails on fluid: false.

A prebuilt flow (vercel deploy --prebuilt from CI, no Vercel Git integration) never runs buildCommand, so set build.vercel.reconcileVercelJson: false there. The target then leaves any committed vercel.json alone, including one with fluid: false. Keep Fluid compute on in the Vercel project either way, because the function is built for that concurrency model. See the vercel target and Vercel.

Edge feature gateCopy link to section: Edge feature gate

The hono target serves a subset of B4.run. The build fails with B4_E1005, naming every offending config key and file at once, when the app uses any of these:

  • the sandbox,
  • backends.filesystem or backends.exec,
  • a config-supplied store,
  • toolOutput,
  • a workspace/ directory, or
  • route-level long-term memory.

The vercel target applies the same gate, and so does b4 check whenever hono or vercel is a configured target. See Edge and Hono.

If you upgrade an app that sets both toolOutput and "hono", a build that passed before now fails. Remove the key, or drop "hono" from build.targets. See Upgrading.

See Deployment for how the targets fit together.

b4 startCopy link to section: b4 start

b4 start serves the app in production with the real B4.run runtime (Agent Protocol, AG-UI, and /healthz). It binds 0.0.0.0:8000 by default.

text
b4 start
b4 start --host 127.0.0.1 --port 3000

Flags:

  • --host <host>: host to bind. Default: 0.0.0.0 (or the HOST env var).
  • --port <number>: port to bind. Default: 8000 (or the PORT env var).

This is what the node build target's generated Dockerfile runs (CMD ["node", ".b4/build/server.mjs"]), and it's the only server that runs the execution sandbox in production. See Node and Docker.

b4 runCopy link to section: b4 run

b4 run invokes one route, reading JSON from stdin and writing JSON to stdout.

text
echo '{"messages":[{"role":"user","content":"Hello"}]}' | b4 run '/navlog'

Flags:

  • --cwd <path>: operate on a different app root.
  • --url <url>: run against a live dev server rather than the in-process runtime.

The route argument can be the route id (/navlog) or its entry file (src/app/navlog/index.ts). Dynamic segment values come from the JSON input. With --url, b4 run POSTs to /threads/<t-cli-uuid>/runs/wait with { route: "<routeId>#<kind>", input }.

Without --url, b4 run regenerates the .b4/ typegen output first, so a tool you just added or changed is bound with its current input schema. You don't need to run b4 typegen yourself. If regeneration fails, for example because a tool's input type can't be resolved, b4 run prints a warning on stderr and runs the route with the schemas already on disk.

b4 testCopy link to section: b4 test

b4 test runs every colocated run.test.ts scenario in the app.

text
b4 test
b4 test src/app/(public)/hello

Flags:

  • --cwd <path>: operate on a different app root.

The optional positional [path] argument limits the run to scenarios under a subdirectory. To target a live dev server, add .server(url) to the scenario's builder chain inside run.test.ts. (b4 test has no --url flag.) Before running in-process scenarios, b4 test regenerates the .b4/ typegen output the same way b4 run does. It exits non-zero on any failure and prints a diff for each scenario that didn't match. See Testing for how to write scenarios.

b4 evalCopy link to section: b4 eval

b4 eval runs every colocated *.eval.ts over its dataset, reports per-case scores, then gates on the aggregate.

text
b4 eval
b4 eval src/app/chat
b4 eval --live
b4 eval --record
b4 eval --json

Flags:

  • --cwd <path>: operate on a different app root.
  • --live: run against the real model (requires OPENAI_API_KEY). Never use it in CI.
  • --record: record real-model responses into sibling fixture files (requires OPENAI_API_KEY). Never use it in CI. Mutually exclusive with --live.
  • --json [file]: write a JSON report. Defaults to .b4/eval-report.json.

The optional positional [path] limits discovery to a subdirectory. By default each case replays its aimock fixtures, which is deterministic and safe for CI. --live calls the real provider while you tune prompts locally. --record saves real-model responses as fixture files you commit, and plain b4 eval replays them. A failing gated eval exits non-zero, so CI fails when quality drops below the bar. Informational evals (no gate/threshold) never affect the exit code. See Evals for how to write them.

b4 devCopy link to section: b4 dev

b4 dev starts the local runtime with hot reload and the Agent Protocol (AP) HTTP endpoints. It always binds 127.0.0.1.

text
b4 dev
b4 dev --port 3001

Flags:

  • --port <n>: HTTP port. Default: dynamically allocated. Pass --port for a stable address.
  • --env-file <path>: path to a .env file (overrides b4.config.ts env and the default ./.env).

The default port changes from run to run, so pass --port (or read the port b4 dev prints) before you copy a curl example.

If the loaded environment has LANGSMITH_API_KEY and LANGCHAIN_TRACING_V2 isn't set, b4 dev turns on LangSmith tracing by setting LANGCHAIN_TRACING_V2=true. Set LANGCHAIN_PROJECT to choose the project that receives the traces. See Observability for tracing and Agent Protocol for the protocol reference.

b4 memoryCopy link to section: b4 memory

b4 memory inspects and manages the app's long-term memory store, the typed records the agent writes with its generated remember tool. Use it to review and promote the candidate writes that the default memory: { writes: "candidate" } config holds back from recall.

text
b4 memory list
b4 memory search <query>
b4 memory inspect <id>
b4 memory approve <id>
b4 memory reject <id>
b4 memory forget <id>
b4 memory prune [--cap <n>] [--namespace <prefix>]
b4 memory consolidate [--dry-run] [--namespace <prefix>] [--model <id>] [--provider <id>] [--max-batches <n>]
b4 memory reflect [--dry-run] [--namespace <prefix>] [--model <id>] [--provider <id>] [--max-batches <n>]

Subcommands:

  • list: list pending candidate records.
  • search <query>: list candidates whose content or namespace matches <query>.
  • inspect <id>: print one record as formatted JSON.
  • approve <id>: promote a candidate to an active record so recall surfaces it.
  • reject <id>: drop a candidate without promoting it.
  • forget <id>: delete a record by id.
  • prune: run episodic retention by hand. It deletes expired records (TTL) and enforces the per-namespace episode cap. --cap <n> overrides the cap for this pass, and --namespace <prefix> limits it to namespaces that match the prefix.
  • consolidate: run distillation's compaction pass. It groups old episodic records by namespace and ISO week, summarizes each group with one model call, then supersedes the sources and gives them a TTL so prune removes them later.
  • reflect: run distillation's insight pass. It derives durable insights from each namespace's records newer than its watermark. Insights are written as candidate by default. Approve them with approve <id> or the Inspector.

consolidate and reflect share the same flags. Below the configured thresholds, both print one line and exit 0 without creating a model or needing an API key. So it's safe to run the cron recipe (b4 memory consolidate && b4 memory reflect) nightly on any app. Their flags:

  • --dry-run: report the plan without making any model call.
  • --namespace <prefix>: scope the pass.
  • --model <id> / --provider <id>: override memory.distill.
  • --max-batches <n>: cap the work (and spend) for one invocation.

These are the only b4 memory subcommands that spend model tokens. See Distillation for the full configuration block.

Flags:

  • --cwd <path>: operate on a different app root.

b4 inspectCopy link to section: b4 inspect

b4 inspect opens the B4.run Inspector, a localhost-only browser UI for browsing, searching, and approving the app's long-term memory records.

text
b4 inspect

The inspector binds to 127.0.0.1 on a free port and prints the URL to open.

Flags:

  • --cwd <path>: operate on a different app root.
  • --port <number>: bind the inspector to a stable localhost port.
  • --env-file <path>: path to a .env file (overrides b4.config.ts env and the default ./.env).

b4 addCopy link to section: b4 add

b4 add fetches a blueprint (an integration guide written for coding agents) and prints it to stdout, so you can hand it to your coding agent.

text
b4 add                 # list available blueprints, grouped by category
b4 add pgvector        # print the pgvector blueprint
b4 add <url>           # fetch a third-party blueprint from any URL

b4 add only prints the guide. Your agent applies the changes, and you review them. To use a self-hosted catalog, set B4_BLUEPRINTS_URL.

See Blueprints for authoring and catalog details.

b4 approvalsCopy link to section: b4 approvals

b4 approvals manages the records behind approval grants.

text
b4 approvals prune
b4 approvals prune --retention <ms>

prune deletes grant records voided because the thread moved past the prompt longer ago than the retention window. A consumed grant is voided when its resumed turn completes; an outstanding grant, or a consumed grant whose resume never completed, is never deleted, however old. The window is approvals.grantRetentionMs (7 days by default); --retention <ms> overrides it for one pass. The runtime runs the same pass on its own, at most once an hour, so this is for cron or a one-off. It prints pruned: <n>. With grants on and no store file yet, it creates the default store file (.b4/interrupt-grants.sqlite) and prunes nothing; with grants off and no store file, it says so and exits cleanly.

Flags:

  • --retention <ms>: retention window for this pass, a positive integer of at most one year.
  • --cwd <path>: operate on a different app root. Give it before the subcommand (b4 approvals --cwd app prune), as with b4 memory.

b4 docsCopy link to section: b4 docs

b4 docs prints the B4.run docs bundled with the installed CLI, so you (or a coding agent) can read the docs for the version you're running without going to the network. With no topic, it lists the available topics and the index. With a topic, it prints that doc to stdout.

text
b4 docs
b4 docs README
b4 docs cli

The optional positional [topic] picks one doc by slug (with or without the .md suffix). An unknown topic exits non-zero and lists the available topics. In a source checkout, build the CLI first (pnpm --filter @b4run/cli build) so the bundled docs exist.

b4 client-toolsCopy link to section: b4 client-tools

b4 client-tools manages the records behind client-provided tools on the AG-UI endpoint.

text
b4 client-tools prune
b4 client-tools prune --retention <ms>

prune deletes records that can no longer affect a turn: answered or voided client calls settled longer ago than the retention window, outstanding client calls whose expiry is older than it, and server tool calls settled longer ago than it. An outstanding, unexpired client call and an unsettled server call are never deleted. The window is server.agui.clientToolRetentionMs (7 days by default) and never shorter than clientToolTtlMs; --retention <ms> overrides it for one pass. The runtime runs the same pass on its own when a turn settles, at most once an hour, so this is for cron or a one-off. It prints pruned: <n>, or says so when the app has no client tool store.

Flags:

  • --retention <ms>: retention window for this pass, a positive integer of at most one year.
  • --cwd <path>: operate on a different app root. Give it before the subcommand (b4 client-tools --cwd app prune), as with b4 memory.

b4 threadsCopy link to section: b4 threads

b4 threads inspects and reattaches to Agent Protocol threads on a running B4.run server.

text
b4 threads tail <thread-id>
b4 threads tail <thread-id> --url http://127.0.0.1:3000
b4 threads tail <thread-id> --header "authorization: Bearer $TOKEN"
b4 threads tail <thread-id> --json

tail rejoins a thread through GET /threads/:thread_id/runs/stream (the reattach endpoint). Use it to recover after a disconnect, a reload, or a lost terminal. It prints a snapshot first, then follows whatever comes next:

  • When a turn is streaming in that server process, the snapshot carries the committed transcript plus the turn's output so far, and the command then tails live frames until the turn emits done.
  • When no turn is live there, the snapshot is the durable one (the latest checkpoint plus any parked human-in-the-loop prompts), and the command exits right away. This path reads from the checkpoint, so it works across restarts and replicas.

The header line reports status, whether the attach is live, the turn's run_started_at, and the checkpoint anchor. If run_started_at changes between reattachments, a different turn now holds the thread. anchor ties a resumed turn back to the run it continues.

Attaching never cancels anything or takes the thread's run slot, so it's safe to tail a thread someone else is driving. To stop a run, use POST /threads/:thread_id/cancel.

Flags:

  • --url <url>: base URL of the running B4.run server (default http://127.0.0.1:3000).
  • --header <name: value>: extra request header, repeatable. Use it when the server's middleware authenticates the thread's route.
  • --json: print raw SSE frames as JSON, one per line.

tail exits with:

  • 1 when the stream ends without a terminal done frame, or the server detaches the viewer (at the per-thread viewer cap, or when the consumer falls behind). Reconnect for a fresh snapshot.
  • 2 when the server is unreachable, the thread doesn't exist, or the thread has never run (so there's no route to authorize against).

Exit codesCopy link to section: Exit codes

CodeMeaning
0Success
1Validation failure (e.g. b4 check) or scenario failure (e.g. b4 test)
2Configuration / runtime error (missing b4.config.ts, bad appDir, scenario load failure)

Non-zero exit codes from underlying tools (Commander, child processes) may be propagated unchanged.