Skip to content

CLI (`bynk` driver)

The bynk driver is the developer front-end — it links the compiler pipeline in-process and orchestrates the Node toolchain (bynk is to bynkc as cargo is to rustc). A fresh cargo install bynk is self-contained: it compiles, checks, and formats without a separately-installed bynkc. This page is the argument and exit-code reference for its subcommands. The pure-pipeline binary bynkc (compile, check, fmt, test) survives for CI and direct use.

The everyday commands — bynk check, bynk fmt, and bynk test — mirror their bynkc counterparts exactly (same flags, same output, same exit codes), so the two are drop-in equivalent. check and fmt run the pipeline in-process; test delegates to the bynkc the driver resolves (see Which bynkc?), so an editor or developer inherits the driver’s resolution instead of locating the compiler themselves.

bynk <command> [options]
CommandWhat it does
bynk doctorCheck whether your machine is ready to compile, test, and deploy.
bynk newScaffold a new, runnable project.
bynk devBuild the project and serve it locally with wrangler dev.
bynk deployProvision each context’s Cloudflare resources and deploy every Worker, in dependency order.
bynk checkType-check a file or project without writing output.
bynk fmtFormat .bynk source files in place.
bynk testDiscover and run a project’s tests.
bynk explainExplain a diagnostic code — what the rule is, why it exists, how to fix it.

Scaffold a new project: a complete, runnable single-context HTTP service you can serve immediately with bynk dev. See the guide Start a new project for a worked walkthrough.

bynk new <PATH> [--name NAME]
ArgumentDefaultMeaning
PATH(required)Directory to create for the new project (e.g. hello or ./hello). Parent directories are created.
--name NAMEPATH’s final componentProject name / context identifier. Must be a legal Bynk identifier (a letter followed by letters, digits, or underscores — no dashes or dots).

What it writes

<PATH>/
├── bynk.toml # [project] name/version + optional [paths] include/exclude
├── .gitignore # /.bynk
└── src/
└── <name>.bynk # context <name> — a GET "/" HTTP service

Behaviourbynk new is pure, offline file-writing: it shells nothing, compiles nothing, and reads no network, so it works before bynkc, Node, or wrangler are installed.

  1. Derive the project name from PATH’s final component (or --name) and validate it as a legal Bynk identifier — both [project] name and the starter’s context use it.
  2. Refuse to clobber: if the target exists and is non-empty, fail before writing anything. An empty directory is fine; VCS/OS cruft (.git, .gitignore, .DS_Store, …) doesn’t count as non-empty.
  3. Write the scaffold and print next steps (cd <path> && bynk dev).

Exit code0 on a written scaffold. A non-empty target or a name that isn’t a legal identifier exits non-zero, touching nothing.

Notes

  • bynk new never overwrites a file it didn’t create, and never runs git init or writes outside the project — the scaffold drops cleanly into an existing repository.
  • The .gitignore covers only /.bynk, the build directory bynk dev writes (compiled workers and local wrangler state).

Build the project and serve it locally — one step in place of the manual bynkc compile + cd + wrangler dev recipe. See the guide Run your project locally for a worked walkthrough.

bynk dev [PATH] [--context NAME]... [--base-port N] [--inspect] [--inspect-port N] [-- <wrangler args>]
ArgumentDefaultMeaning
PATH.A directory inside the project. The root is found by walking up for bynk.toml.
--context NAMEevery contextWhich context to serve. Repeatable; omit to serve them all with their service bindings wired. Accepts the dotted name (a.b) or its worker-directory form (a-b).
--base-port N8787First port of the per-context allocation: context i is served on N + i, in the order listed.
--inspectoffServe with the V8 inspector enabled (wrangler dev --inspector-port) so a JavaScript debugger can attach. Breakpoints set in .bynk sources resolve through the emitted source maps, composed into the worker bundle. Prints an inspector URL per context on start.
--inspect-port N9229First inspector port for --inspect, allocated per context exactly as --base-port is.
-- <wrangler args>Everything after -- is forwarded to wrangler dev verbatim (e.g. -- --var K:V). Ports are the driver’s to allocate: passing -- --port or -- --inspector-port against an allocation is an error naming the flag that owns it.

Behaviour

  1. Locate the project root and read [paths] include.
  2. Pre-flight the deploy capability (bynkc, Node, wrangler) exactly as doctor does; a missing required tool fails here, before any build, with doctor’s remedy text.
  3. Compile to the managed .bynk/dev/ build directory (gitignored automatically; the workers/ tree is cleared before each build).
  4. Select the workers: every context, or the --context subset. An unknown name fails and lists the available contexts.
  5. Run one wrangler dev per context, each from inside its own worker directory, in local mode (Miniflare) — no namespace provisioning is needed and wrangler.toml is served untouched. The processes discover each other through wrangler’s dev registry and wire the generated [[services]] bindings between themselves, so a cross-context call resolves locally.
  6. Watch the .bynk sources; a save rebuilds in place and the affected workers hot-reload. Any worker exiting stops the rest — a survivor’s bindings would point at a context that is gone.

Exit code — On a successful hand-off, bynk dev exits with the exit code of the first wrangler to stop (a clean Ctrl-C stop is a 0). A pre-flight or build failure exits non-zero before serving.

Notes

  • bynk dev provisions nothing. For bynk dev -- --remote, a prior bynk deploy must have recorded the KV namespace id in bynk.deploy.lock.
  • wrangler is resolved with the same provenance ordering as doctor (project-local node_modules/.binPATHnpx). An npx resolution is surfaced as a notice — it downloads on first use.

Provision the Cloudflare resources each context declares — KV namespaces, queues, and Durable Object migrations — set its secrets, and deploy every context’s Worker, in Service-Binding dependency order. See Deploy to Cloudflare for the workflow.

bynk deploy [PATH] [--context NAME] [--dry-run] [--format short|json] [--yes]
[--secrets-file PATH] [--secret NAME]... [--force] [-- <wrangler args>]
ArgumentDefaultMeaning
PATH.A directory inside the project. The root is found by walking up for bynk.toml.
--context NAMEevery contextDeploy this context alone, assuming the contexts it consumes are already live. A dependency that has never been deployed is named and refused rather than pushed into — as is one that is live but no longer provides the contract this context was compiled against (bynk.deploy.contract_skew, since v0.177; see Contract skew). Accepts the dotted name (a.b) or its worker-directory form (a-b).
--dry-run, --planoffPrint the per-context plan and resolved order, then exit without changing Cloudflare or bynk.deploy.lock. Works offline — it never authenticates.
--format FORMATshortPlan output: line-oriented short or machine-readable json.
--yesoffSkip the confirmation required before a resource is created or a Worker is published. Required for non-interactive calls.
--secrets-file PATHRead secret names and values from a dotenv-style NAME=value file. Never committed, never persisted: values move to wrangler secret put and are dropped.
--secret NAMESet this named secret, taking its value from the environment (or a prompt). Repeatable. Needed only for a bynk.Secrets name — an actor’s declared auth secret needs no flag.
--forceoffOverwrite a secret that is already set. The default sets only the missing ones, so a re-deploy does not cut a fresh Cloudflare secret version for every secret every time.
-- <wrangler args>Everything after -- is forwarded to wrangler deploy verbatim, for every context deployed.

Behaviour — the command pre-flights Node and Wrangler, compiles into .bynk/deploy/, reads what each context declares from its generated wrangler.toml, topologically sorts the Service-Binding graph so every binding target is uploaded before the Worker that binds to it, prints a per-context plan carrying that order, checks Wrangler authentication, and then provisions, materialises, sets secrets, and deploys each context in turn. The order is a correctness requirement, not a nicety: Cloudflare resolves a Service Binding at upload and rejects a Worker whose target does not yet exist. A consumes cycle cannot arise — the compiler rejects one before emit.

Secretsdeploy sets three kinds, and the plan marks which is which:

  • declared — an actor’s auth secret (Bearer(secret = "…"), Signature(secret = "…")). Supply only its value; you never name it. A declared secret with no value is a hard error, because deploying without it would answer every request 401.
  • read — a literal bynk.Secrets name (Secrets.get("X")). The compiler sees it, but get returns Option, so a missing value is a warning, not an error: the program already handles None.
  • supplied — anything you name with --secrets-file or --secret that the compiler did not find itself.

A Secrets.get call with a computed name cannot be planned. The compiler warns (bynk.secrets.computed_name), and the plan says so rather than under-reporting — secrets incomplete <worker>, or secrets_complete: false on the context in --format json. A short read list with secrets_complete: false is not a census — see the deploy guide.

Values are read from --secrets-file first, then the environment, then a prompt when a terminal is attached; without one, a missing value is an error naming the secret rather than a silent blank. The environment supplies values only — it is never scanned for names. A secret value never reaches bynk.deploy.lock, generated config, or the plan, in any format.

Per context, the plan carries one line per resource it declares:

Plan lineMeaning
kv create|reuse <namespace>A KV namespace is created, or its recorded id reused.
queue create|reuse <name>A queue is provisioned before the push, which wrangler deploy will not do for you. reuse forecasts that it already exists; existence is checked against Cloudflare at provision time regardless, so a queue deleted out-of-band is restored.
migration <tag> (advisory — wrangler deploy applies it)The Durable Object migration the push will apply. Advisory: Cloudflare owns the applied-migration record and bynk keeps none, so this states what will be asked for, never what is already applied. In --format json it is {"tag": …, "applied_by": "wrangler deploy"}.
deploy|redeploy <worker>The Worker is pushed; redeploy when the ledger has pushed it before.

Each context’s Cloudflare ids and its deployed state are recorded in the committed, secret-free bynk.deploy.lock at the project root, written as each resource lands. A recorded id is reused on later deploys. CI refuses to create an unrecorded namespace; provision it locally and commit the lock file first. That refusal covers KV alone — a queue is addressed by the name in your source, so CI creating one strands nothing.

A run is resumable, not transactional: a failure stops the run, keeps and records what already landed (there is no rollback), and names what did not. A re-run re-pushes in the same order — it does not skip contexts already live, so a changed context always ships; the plan reports those as redeploy.

Exit code0 on a successful plan or deploy, and on a clean Ctrl-C stop. A Wrangler failure exits with Wrangler’s own exit code — the first one to fail, since the run stops there — so a CI job reads the same code it would from wrangler deploy directly. The driver’s own failures exit 1: missing tools or authentication, declined confirmation, compilation failures, an unrecorded CI resource, a --context whose dependency was never deployed, and a --context whose live dependency has drifted from the contract it was compiled against (bynk.deploy.contract_skew) — deploying that would ship a caller its callee rejects on every call.


Type-check a .bynk file or project without writing output — the same behaviour as bynkc check, through the driver. Runs the compiler pipeline in-process: no separately-installed bynkc is required.

bynk check [INPUT] [--format rich|short]
ArgumentDefaultMeaning
INPUT.A .bynk file, or a project root directory (a bynk.toml or src/ subdir selects project mode; otherwise the directory is itself the source tree).
--formatrichrich is the source-context rendering; short emits one terse path:line:col: severity[category]: message line per diagnostic, for the VS Code problem-matcher, CI, and scripts.

Exit code0 when the input type-checks (warnings are surfaced but do not fail the build, per the diagnostics rule); non-zero on any error-severity diagnostic.


Format .bynk source files in place — the same behaviour as bynkc fmt, through the driver, run in-process.

bynk fmt <INPUTS>... [--check] [--indent tab|spaces] [--indent-width N] [--max-line-width COLUMNS] [--trailing-comma|--no-trailing-comma] [--no-config]
ArgumentDefaultMeaning
INPUTS(required)Files to format. Pass - to read from stdin and write the formatted result to stdout.
--checkoffReport files that are not already canonically formatted without writing changes. Exits non-zero if any file would change. For CI.
--indent[fmt] indent, else tabIndent with tabs or spaces. Tabs are the default so each reader sets their own width in their editor.
--indent-width N[fmt] indent_width, else 2Spaces per nesting level. Rejected when the run resolves to tabs, where it would have no effect.
--max-line-width COLUMNS[fmt] max_line_width, else 100Soft target line width. A construct wider than this wraps across lines where the grammar allows; one with no break point in it (a long string literal) is left long.
--no-trailing-commaoffOmit the trailing comma in multi-line records, sums, list literals and exports clauses. --trailing-comma is its opposite (and overrides a project’s trailing_comma = false); the later of the two wins.
--no-configoffIgnore the project’s bynk.toml [fmt] section and format to the canonical style plus whatever flags this run passes.

Behaviour — each file is formatted and rewritten only when it changes; a file already canonical is left untouched. A file that does not parse is reported and skipped; the other inputs are still processed.

Where the style comes from — three layers, each overriding the one before: the canonical defaults, then the project’s [fmt] section in bynk.toml, then the flags this run passes. The manifest is found by walking up from each file, so one command may format files from two projects and give each its own style. --check judges files against those resolved options, so a project on a non-default style has a CI gate that can pass. --no-config skips the manifest layer for a run that wants the canonical style whatever project it is pointed at; nothing here ever writes to bynk.toml.

Format-on-save in the editor reads the same [fmt] section through the same reader, so the two agree by construction.

Exit code0 when every input was formatted (or, under --check, already canonical). Non-zero if a file could not be read/written, failed to parse, or (under --check) was not canonical.


Discover and run a project’s test declarations — the same behaviour as bynkc test, through the driver. Unlike check and fmt, test delegates to the resolved bynkc (it orchestrates external tsc/node anyway), forwarding every flag verbatim. Requires tsc (with Node.js) or tsx on PATH, exactly as bynkc test.

bynk test [INPUT] [-o OUTPUT] [--no-run] [--format rich|json] [--inspect] [--seed HEX] [--case NAME] [--coverage]
ArgumentDefaultMeaning
INPUT.Project root directory.
-o, --output OUTPUT<input>/outWhere to write the compiled TypeScript test-runner modules.
--no-runoffSkip the runner. With --format rich, emit the generated test files; with --format json, emit a discovery document listing every suite and case without running them.
--formatrichrich is the grouped ✓ / ✗ human output; json is a single pinned JSON document of results, for tooling.
--inspectoffLaunch the runner under Node’s inspector (node --inspect-brk) and print the inspector URL. Requires Node ≥ 22.18 (or ≥ 23.6 unflagged). Does not run tsc.
--seed HEXrandomRoot seed for generative property tests (e.g. 0x5f3a). A failing property prints the seed it used; re-running with --seed <hex> reproduces the run byte-for-byte.
--case NAMERun only test cases whose name matches NAME — the filter behind the editor’s per-case ▷ Run Test lens. No effect with --no-run.
--coverageoffAfter the run, report statement/line coverage attributed to .bynk source (rich table, or a coverage block under --format json). Requires the tsc → node path — incompatible with --inspect and --no-run.

Exit code — follows the runner’s own process status: 0 when every case passed, non-zero on a failing case, a compile error, or a missing runner.


Explain a diagnostic code — the longer-form answer behind a bynk.* error, the analogue of rustc --explain. When you hit an error like bynk.resolve.unknown_type, bynk explain bynk.resolve.unknown_type prints what the rule is, why it exists, a minimal before/after example, and a link to the relevant Book concept page. Runs entirely in-process — it shells nothing and reads no network, so the blurb is the complete, offline answer.

bynk explain <CODE>
ArgumentMeaning
CODEThe diagnostic code to explain, e.g. bynk.resolve.unknown_type.

The explanations are curated highest-traffic-first, so coverage grows over time: a recognised code that is not yet curated prints its one-line summary and a pointer to the diagnostic index rather than a full explanation. The same compiler-owned mapping powers the editor’s clickable diagnostic-code links (codeDescription), so the CLI and the editor never disagree.

Exit code0 for any code the compiler recognises (explained or not); non-zero for an unrecognised code.


bynk locates the compiler it needs — for test, and for the check/fmt escape hatch below — in this order:

  1. the BYNK_BYNKC environment variable, if set (an explicit pin);
  2. bynkc on PATH;
  3. a bynkc sibling of the running bynk binary (how a paired install ships).

bynk check and bynk fmt run in-process and need none of this — unless BYNK_BYNKC is set, in which case they shell that pinned compiler so an externally-managed bynkc still governs the result. bynk doctor reports this resolution and any driver↔compiler version skew.


Survey the toolchain — grouped by capability — and print the exact remedy for anything missing. Documented in full in the guide Check your environment with bynk doctor.

bynk doctor [PATH] [--only CAPABILITY] [--strict] [--format human|short|json]
ArgumentDefaultMeaning
PATH.Project directory, for project-local node_modules/.bin resolution.
--only CAPABILITYScope the check — and the exit code — to one of compile, test, deploy, editor, build.
--strictTreat every warning (optional gaps, npx provisionability, minor skew) as a failure. For CI.
--formathumanhuman is a grouped table; short and json are the stable scriptable surface.

Exit code — Bare bynk doctor is informational: it exits 0 unless bynkc itself is unusable. --only <capability> gates on that capability; --strict fails on any warning.