21. The CLI
The archlang CLI covers the whole toolchain surface: validating and formatting files, running governance and CI gates, writing model fields, exporting the resolved model, rendering diagrams headlessly, serving the HTTP API, running the language server, checking evidence bindings against the code repository, and checking a deployed system’s conformance against the model. Twelve subcommands, one binary. This chapter walks through what each one does, what flags it takes, and how to fit them into CI and pre-commit hooks.
Install
Section titled “Install”npm install -g @archlang/cliThat puts archlang on your PATH. The CLI is a thin layer over @archlang/engine (parsing, resolution, validation) and @archlang/lsp (the shared package loader) — everything those packages need ships inside the binary. render’s PNG output goes through @archlang/render, which bundles a native resvg binary and the Inter font — the only subcommand with a native dependency; everything else is pure JS.
archlang --helparchlang --version--version prints the toolchain version. --help lists every subcommand and its flags.
archlang validate
Section titled “archlang validate”archlang validate <path>Parses every .arch file under <path> (recursively, respecting nested package.archspace boundaries), runs the resolver, runs the validator. Prints diagnostics to stdout. Exits non-zero if anything failed.
archlang validate . # current directoryarchlang validate examples/demo # a specific packagearchlang validate orders.arch # a single file (its package is auto-detected)Single-file drafts are first-class. A standalone .arch with no surrounding package is a perfectly good way to start — it can depend on locally-defined types and the standard library, which is enough to model a real idea fast. The CLI validates one on its own; promote it into a managed package later. (See the standalone-file rules in Chapter 11 and Chapter 12.)
The diagnostics use the same format the LSP produces, which means the same error you see in your editor is the one the CLI prints. Examples:
orders.arch:14:5 ERROR Required field 'team' is not fulfilled and not droppedcheckout.arch:8:9 ERROR Process step callee 'Payments' resolves to a module, not an interfaceThe summary line counts three distinct severities — they are not interchangeable:
2 errors, 1 warning, 4 todos, 0 info- Errors are contradictions — the model says something that can’t be true. Fix them.
- Warnings are discouraged-but-valid — a smell, not a lie.
- TODOs are draft debt, not bugs: a referenced-but-undefined module, interface, or subprocess that the compiler synthesized as a stub. A
TODOmeans missing detail, not wrong. Process-first drafting produces them by design; they’re fine mid-draft.
A mature model shouldn’t ship with open TODOs. Pass --complete (alias --no-todos) to enforce the zero-TODO merge gate: the run then exits 3 if any todo-severity diagnostic remains, and prints a located, per-category checklist of what’s left instead of just a count.
archlang validate --strict <path> # treat warnings as errors too (exit 2)archlang validate --complete <path> # fail (exit 3) if any TODO remains; prints the punch listarchlang validate --verbose <path> # print every TODO in full, not the grouped summaryWatch mode
Section titled “Watch mode”archlang validate --watch <path>Re-runs validation on every .arch save. Useful for keeping a terminal pane open next to your editor when you don’t want to enable the inline preview. Runs until Ctrl-C.
CI use
Section titled “CI use”The non-zero exit code is the contract. In CI:
archlang validate .Pull requests fail until validation passes. Combine with format --check and check below.
archlang format
Section titled “archlang format”archlang format <path>Rewrites the file (or every .arch file under a directory) into canonical form: normalized whitespace, consistent indentation. It also mints a stable ID for any ID-less declaration by default — the formatter is where the spec §3.1 “let the tooling mint it” rule actually runs. The formatter does not change semantic content otherwise — it only normalizes layout.
archlang format --check <path> # exit non-zero if anything would changearchlang format --diff <path> # print the diff that would be applied, but don't writearchlang format --no-mint <path> # skip ID minting; keep bare drafts barearchlang format --indent=2 <path> # spaces per indent level (default 4)archlang format --tabs <path> # use tabs instead of spaces--check is the CI flag. --diff is the local “what would this do?” flag. Neither writes to disk.
If a file has parse errors, whitespace formatting still proceeds over the parseable regions, but ID minting is skipped for that file (minting writes a permanent ID — never into a source the parser can’t make sense of).
Stable IDs
Section titled “Stable IDs”An ID is meaningless and permanent — a truly-random token that never changes even when the thing it names is renamed, re-scoped, or moved. Because hand-randomizing is hard, let the tooling mint it for you. The formatter (and editor autocomplete) generates truly-random IDs for any declaration that touches the format path without one. Prefer the tool over hand-writing every time.
Don’t hand-edit a stable ID once written — never mutate it, and don’t embed a domain or type name in it (anything meaningful eventually goes stale and tempts a rename, which defeats the point). Renames anchor on the ID, so the diff engine sees one renamed declaration instead of a delete-plus-add (Chapter 13).
archlang check
Section titled “archlang check”archlang check <path>Runs four phases in sequence and reports all of them even if an earlier one fails, so you see the whole picture instead of fix-rerun-fix: validate, then policy-check, then format --check, then evidence. The exit code is the max across phases — 0 all clean, 1 if validate found errors, a policy raised an active error finding, format would change files, or an evidence binding is broken, 2 under --strict if only warnings were found.
check is the heavier sibling of validate. Use validate in the inner editor loop where you want fast feedback; use check in CI and at major checkpoints.
archlang check --strict <path>Change-gate dry run
Section titled “Change-gate dry run”archlang check <path> --against=mainAdds a further phase: resolves the workspace both at the given git ref (base) and in the working tree (head), then evaluates the v0.10 when change gates over that diff and reports which gates fired and what review each would require. It also re-evaluates the two-arm waiver rule in the diff context — a waiver introduced or modified in this change is unreviewed by definition, so any finding it would otherwise suppress is reactivated in the report even though the head-state view (render/LSP/policy-check) shows it waived. This is a dry run: the CLI holds no approval state, so a fired gate just fails the check (exit 1) — recording and enforcing actual approvals is Studio’s job, not the CLI’s.
archlang policy-check
Section titled “archlang policy-check”archlang policy-check <path>Evaluates every policy declaration in the workspace against the derived dependency graph and reports the active (non-waived) findings. Policy severities:
error— fails the gate (exit 1).warning— reported; fails only under--strict(exit 2).advisory— report-surface only, never fails.
archlang policy-check --strict <path>archlang policy-check --json <path>A policy that uses a not-yet-supported selector or expression feature is skipped with a note on stderr rather than crashing the run — the rest of the policy set still enforces.
archlang evidence
Section titled “archlang evidence”archlang evidence <path> [--repo=<path>] [--json]Checks every sources: evidence binding in the workspace against the commit pinned in package.archspace (Chapter 37): the cited path is a blob at that revision, the line range fits inside it, and this checkout’s origin is the declared repository. --repo points at the code checkout; without it the current directory is used.
https://github.com/acme/shop @ a1b2c3d ✓ Checkout src/checkout/index.ts:12-88 ✗ Checkout src/checkout/tax.ts:5-40 · Ledger src/ledger.ts
1 verified, 1 broken, 1 unverifiedThe exit code is 1 only if some binding is broken. A binding that could not be checked at all — no repository, no pin, the wrong repository — is reported as unverified and does not fail the run. The asymmetry is deliberate: a broken binding is a defect the author can fix, an unverifiable one usually means the machine running the check has no repository. unverified is still never a pass, and nothing displays it as one.
All seven evidence diagnostics are warning-severity, so they surface in validate and check without failing them; only the broken-binding exit code does that.
archlang set
Section titled “archlang set”archlang set <path> <target> <field> <value> [--aspect]archlang set <path> <target> <text> --descriptionHeadless model write. <target> addresses a module by #id, a qualified path (Platform.Auth), or a unique name. Writes go through the same mutation layer the board/inspector editors use, so hand formatting and comments survive — and the write refuses (exit 1, printing the reason) instead of ever corrupting the file.
archlang set . Payments version 2.1 # field: version: 2.1archlang set . Payments team Core --aspect # aspect: team: Corearchlang set . Payments "Owns payments" --descriptionBuilt for CI/CD use — bumping a version field on release, stamping a deploy environment — where a script needs to touch one field without hand-editing .arch source.
archlang export
Section titled “archlang export”archlang export json <path>Dumps the resolved workspace model as JSON — same shape as GET /api/model on archlang serve, so a CI job that wants the model can skip booting a server.
archlang export json . -o model.json # write to a file instead of stdoutarchlang export json . --stdlib=<path> # override the stdlib lookupThe payload is { model: { modules, processes, subprocesses, views, types, documents?, backlinks? }, diagnostics: [...] }.
archlang export html
Section titled “archlang export html”archlang export html . -o architecture.htmlWrites ONE self-contained HTML file: the viewer shell, your workspace and the fonts all inlined. It opens by double-clicking it, on a machine that has never installed anything of ours, with the network unplugged — no server, no deployed viewer, nothing to configure.
That makes it the artifact for the places the embed of Chapter 23 cannot reach: attach it to a review, mail it to an architect, drop it in a release, hand it to someone outside your network. It is a full viewer inside the file — nav, spaces, process and table views, your .md documents (read-only, since the snapshot is frozen), the images they embed, PNG/SVG export — minus the editor and compare, which a frozen snapshot has no use for.
archlang export html . -o out.html --force # emit despite error diagnostics-o is required (the file is megabytes of markup). The command refuses a workspace with error diagnostics unless you pass --force, and refuses to overwrite one of your own source files. --force also lets -o write outside the current directory, which is otherwise refused.
Text renders with the bundled Inter, the same font the headless PNG export uses — so the artifact matches archlang render more closely than it matches your browser, which substitutes its own system font.
It is an export, not a substitute for the embed: the file is a snapshot, so it does not update when the model does. For a living diagram on a page you control, embed the viewer.
archlang render
Section titled “archlang render”archlang render [path] --out=<file.svg|file.png> [--view=board|bpmn|flow|sequence] ...Headless SVG/PNG export — no browser, no server. It’s the same walk→layout→SVG pipeline the preview server’s /api endpoints and the MCP arch_render tool use.
archlang render . --view=board --out=board.svg # module board (default view)archlang render . --view=bpmn --process=Orders.Checkout --out=flow.png --scale=3archlang render . --view=flow --process=Orders.Checkout --out=flow.svgarchlang render . --view=bpmn --process=Orders.Checkout --out=diff.svg --diff-base=../old-workspacearchlang render . --view=board --out=compare.svg --diff-base=../old-workspace # Before/Delta/After + compare receiptarchlang render . --view-name=CheckoutOps --out=ops.svg # a declared flow viewFlags:
--out=<file>— required, and must end in.svgor.png(the extension picks the format;--format=svg|pngoverrides which bytes are written, not the required extension). The path must resolve inside the current directory and may not be one of the model’s own sources.--view=<v>—board(default),bpmn,flow, orsequence.bpmn/flow/sequencerender a process and require--process.--view-name=<name>— render a declaredviewby name instead (itsflowbody picks plain/sequence/bpmn, including a bpmn view’s lane/pool layout; view instances bind their knobs). Mutually exclusive with--processand--diff-base.--process=<name>— qualified dotted process name (e.g.Orders.Checkout), required forbpmn/flow/sequence.--expand=all|<ids>— expanddo-subprocess nodes:all, or a comma-separated list of do-node ids.--diff-base=<path>— diff against an older workspace at this path. With--view=boardit renders a Before / Delta / After artifact (Delta is the union of both revisions, the only panel where an added and a removed thing appear together) and adds acompareblock to the receipt: per-change classifications, an explicit identity statement, both sides’ raw and semantic hashes, and alimitationsarray naming what the comparison cannot tell you. With--view=bpmnit renders the union process scaffold with change accents.flow/sequencehave no diff renderer and refuse.--scale=<n>— PNG only: zoom factor for resolution (default 2, clamped to 8).--force— render even though the model has error diagnostics, and allow an--outoutside the current directory. It never allows overwriting a model source.--stdlib=<path>— override the stdlib lookup path.
PNG rasterization goes through @archlang/render’s bundled resvg — deterministic output, bundled Inter fonts, no system font dependency.
Validate, then deliver. A model with error diagnostics is refused (exit 1) rather than rendered: the artifact would be missing whatever failed to resolve, and exit 0 would tell CI nothing is wrong. The artifact is written through a staging file renamed into place, so a refused or failed run leaves the previous artifact — the last good one — exactly where it was. Every run drops a <out>.receipt.json beside the target naming the input hash, the artifact hash, the counts and what the receipt does not claim (no perceptual review is ever asserted); on a refusal the receipt names the retained last-good artifact’s hash and lists each blocking diagnostic as a repair receipt — its registered code, the msgKey/params the message was rendered from (did-you-mean candidates included) and supportedFixes, which names the model or solver input a repair edits. A diagnostic whose root cause is another diagnostic in the same file is dropped, so the list names causes, not symptoms. The MCP arch_render tool (force: true, a <outFile>.receipt.json) and the server’s /api render routes (?force=1, HTTP 409 + the refusal receipt, X-ArchLang-Receipt on success) apply the same three rules.
archlang serve
Section titled “archlang serve”archlang serve [path] [--port=<n>] [--web-root=<path>] [--quiet]Starts the HTTP API server — the resolved model plus REST endpoints, and an optional web UI if --web-root points at a built one. Wraps @archlang/server’s CLI; every flag is forwarded through verbatim. Default port is 3100; --port=0 picks an ephemeral port. Runs forever — SIGINT/SIGTERM to exit.
archlang lsp
Section titled “archlang lsp”archlang lspStarts the language server on stdio, for editor/IDE clients that want to launch the LSP as a subprocess rather than depend on a bundled copy. VS Code and IntelliJ bundle the LSP directly (see Chapter 22); archlang lsp is for building your own integration. Runs forever until the client sends the LSP exit notification or the process receives SIGINT/SIGTERM.
archlang conform
Section titled “archlang conform”archlang conform <path> --source=jaeger|tempo --endpoint=<url>Checks a deployed system against the model using OpenTelemetry traces — the model-to-reality direction, complementing validate’s syntax-to-model direction. Two dimensions: service-graph conformance (aggregate edges seen in traces vs. edges the model declares) and per-trace process conformance (does an observed trace match a modeled process’s steps, in order).
archlang conform . --source=jaeger --endpoint=https://jaeger.internal \ --metrics-endpoint=https://prom.internal --lookback=3600 --limit=1000 --json--source=jaeger|tempoand--endpoint=<url>are required.--metrics-endpoint=<url>— optional metrics backend for richer conformance signal.--lookback=<sec>— trace query window (default 3600, clamped to a week).--limit=<n>— max traces to pull (default 1000, clamped to 100,000).- Auth: set
ARCHLANG_OTEL_TOKENin the environment; it’s sent as the backend’sAuthorizationheader and never logged or echoed.
Exit 1 on any conformance violation (telemetry-only edges the model doesn’t declare, unknown traces, extra/missing/reordered steps) or if the backend returns no data at all.
archlang info
Section titled “archlang info”archlang info <path>Prints a summary of the package: name, version, file/module/type/process/view/document counts, declared spaces, and the transitive dependency tree.
Use cases:
- Onboarding. New team member runs
archlang infoagainst the package they’re about to work in; they get a one-screen overview without opening files. - CI artifacts. Save the output as a build artifact so PR reviewers can see what a branch’s architecture looks like at a glance.
- Migration audits. Run against a legacy package being modeled, then re-run after each refactor pass.
Combining the commands
Section titled “Combining the commands”A representative pre-commit hook:
#!/usr/bin/env bashset -earchlang format --check .archlang validate .A representative CI job:
archlang format --check .archlang check . --against=origin/mainarchlang info . > arch-summary.txtformat --check ensures every file is canonical. check --against runs validate + policy-check + format-check + evidence plus the change-gate dry run against the PR’s base branch. info produces the artifact for reviewers.
Exit codes
Section titled “Exit codes”Treat any non-zero exit as failure for CI purposes.
0— success, no errors.1— error-severity diagnostics, an active policy error, a format mismatch under--check, a broken evidence binding, a conformance violation, or a load failure.2—--strictand at least one warning-severity diagnostic/finding (validate,check,policy-check).3—--completeand at least one todo-severity diagnostic remains (validateonly).64— usage error: unknown subcommand, missing required argument.
validate --watch and serve run until interrupted; their reported exit code (0) reflects a clean shutdown via SIGINT/SIGTERM, not a validation result.
What’s not a subcommand
Section titled “What’s not a subcommand”Diagram diffing isn’t its own subcommand — it’s two flags on the commands above: render --diff-base=<path> renders the visual diff (a Before/Delta/After board, or a BPMN union scaffold with --view=bpmn), and check --against=<ref> runs the change-gate diff against a git ref. The underlying diff is also a library API (@archlang/engine’s BpmnEdge.diff and friends) that the viewer’s diff mode consumes directly.
There’s no plugin system — everything the CLI does is one of the twelve subcommands above; customization happens by consuming the library packages (@archlang/engine, @archlang/viewer-core, @archlang/render) directly.
Summary
Section titled “Summary”- Twelve subcommands:
validate,format,check,policy-check,evidence,set,export,render,serve,lsp,conform,info. validatefor the inner loop;check(optionally--against=<ref>) for CI;policy-checkfor governance;info/exportfor summaries and tooling.setwrites a single field/aspect/description headlessly;renderproduces headless SVG/PNG;serveruns the HTTP API;lspruns the language server;conformchecks a live system’s telemetry against the model.- Exit codes are stable; pipelines key off them.
- No plugin system, no standalone diff subcommand — diffing lives in
render --diff-base/check --againstand the engine’s diff library API.
What’s next
Section titled “What’s next”Chapter 22: Editor Integration → — what the LSP gives you in VS Code and JetBrains.