Download

Version 26.7.16 is available for each supported desktop platform.

# extract the archive you downloaded above
tar xzf navigator-26.7.16-macos.tar.gz

# macOS only: the binary is unsigned, so clear the quarantine
# Gatekeeper adds to downloads, or the first run is blocked
xattr -d com.apple.quarantine navigator

# put it on your PATH, then confirm it runs
sudo mv navigator /usr/local/bin/
navigator --help

cli

Operator CLI for Neon Law Navigator (binary name: navigator; the crate is still cli, so cargo run -p cli -- … works unchanged). Validates markdown templates against the rule engine, imports clean files into the same SeaORM-managed Postgres web reads from, seeds canonical reference data, prints rows, renders an ER diagram for the schema, and — over a browser-loopback login — drives a live site's matter flow against a short-lived bearer token.

Guarded staging lifecycle

navigator dev staging up|reset|status|down manages local KIND staging. Reset and down require NAVIGATOR_ENVIRONMENT=staging, a managed namespace, and its immutable environment ID.

Getting started

# DB-free subcommand: works on any laptop, no Postgres required.
cargo run -p cli -- validate templates

# DB-touching subcommands take --database-url, falling back to
# the DATABASE_URL environment variable.
export DATABASE_URL=postgres://navigator:navigator@localhost:15432/navigator
cargo run -p cli -- import templates
cargo run -p cli -- list templates
cargo run -p cli -- docs erd | head

# Or install on your PATH
cargo install --path cli
navigator --help

Subcommands split by whether they need a database:

SubcommandNeeds DB?Notes
validatenoLints every .md, runs typed event checks, parses YAML; N104 seed check.
validate-no-client-datanoFlags client emails/phones in store/seeds, templates, web/content.
rendernoValidation-gated template → PDF; --format letter.
formatnoWhitespace + bullet cleanup on one .md.
docs listnoLists published docs pages plus glossary term anchors.
docs glossarynoLooks up workspace vocabulary by term.
scaffoldnoDrops template + workflow + feature stubs.
assets buildnoTranscodes source photos into AVIF/WebP/JPEG.
assets uploadnoPushes public image files to the public assets bucket.
assets fonts uploadnoPushes licensed GORP Regular and Bold WOFF2 files to the assets bucket.
assets pullnoRestores web/public/img/ from the assets bucket for local dev.
assets verifynoChecks every content img/ reference is published at the public origin.
assets stub-referencednoWrites tiny local placeholders for content img/ references.
forms syncnoVendors blank government forms to the assets bucket; writes/verifies pins.
forms fieldsnoPrints a pin-verified blank's AcroForm /T names, one per line.
forms re-authornoRewrites a blank's /T names to questionnaire state paths from its map.
importyesWrites into --database-url Postgres.
listyesAuto-runs migrate + seed before printing.
docs erdyesIntrospects pg_catalog + information_schema.
project createyesRequires entity, client DRI, and product; seeds DRI participation.

The live-site commands need no local database — they are an authenticated HTTP client against a deployed web:

SubcommandRoute hitNotes
auth loginGET /auth/cli/startBrowser-loopback OAuth → ~/.navigator.json (0600).
auth logout / auth whoami(local)Forget / inspect the stored token; whoami does the expiry math locally.
projects listGET /staff/projects.csvRendered as a table, or --json.
project open PROJECT_CODEGET /staff/projects/:idOpen a visible existing matter workbench.
notation createPOST /staff/projects/:id/notations/newCreate a Notation; parks at q1.
intake answerGET/POST …/stepWalk the questionnaire (interactive, --answer/--person, --transcript).
retainer clause…/clausesadd / edit / list the per-matter clauses spliced into the retainer.
retainer approvePOST …/approve-sendRenders + parks the PDF at generate_pdf__retainer_pdf; no envelope.
retainer sendPOST …/sendOne real envelope on prod; deliberate human command. 409 until rendered.
notation statusGET …/review?format=jsonWorkflow state, signature request id, document_ready.
notation approvePOST …/approve-sendRender + park the bound packet (formation form or retainer).
notation request-changesPOST …/request-changesFlag wrong answers (--question/--note); → reask.
notation updatePOST …/reaskRe-collect flagged answers (--answer code=value); resubmit for review.
notation documentGET …/documents/documentDownload the rendered (filled) packet to --out <path>.

Local developer orchestration is nested under dev; these commands are reversible and target the KIND loop:

SubcommandNotes
dev upBring up the shared KIND dependency tier and write .devx/env; up remains a top-level alias.
dev downFull local teardown; routine handoff leaves the shared dependency tier running.
dev env / dev statusPrint host-side env vars or inspect the cluster and port-forward state.
dev kind up / dev kind downCreate or delete only the KIND cluster and ingress/operator base.
dev worktree-envPrepare a topic worktree with its own host web port on the shared dev topology.
dev deploy / dev undeployPull the published images into KIND or remove the navigator namespace.
dev e2e / dev grant-staff / dev logsLocal smoke test, staff-user seed, and navigator-web log tailing.
dev browser-e2eReproduce deploy.yml's browser gate (browser_e2e + accessibility_e2e, pinned Chrome).
dev kustomize kind / dev kustomize gkeRender local or example production overlays.
dev i18nReconcile the English i18n catalog against the Rust call sites; reports missing/unused keys.

Production and cloud operations are nested under ops; treat them as operator commands with real blast radius:

SubcommandNotes
ops shipRoll GKE deployments, trigger images, and Restate registration onto one published YY.M.D tag.
ops gcpProvision project infrastructure and manage IAP IAM helper calls.
ops github setupReconcile GitHub's merge gate and DevX labels; --dry-run prints the plan without writes.
ops restateRegister the public workflows-service URL with Restate Cloud.
ops doctorRead-only cluster diagnostic for wedged jobs and unready workloads.
ops dns / ops rebrand / ops observabilityDNS records, brand pack, and production telemetry setup.

Driving a live site

navigator auth login mints a short-lived (~8h) bearer token like gcloud auth login does — it opens the browser, reuses the site's existing OIDC session, and lands the token on a 127.0.0.1 loopback listener. The token is the same HMAC-signed session blob the browser cookie carries, presented as Authorization: Bearer; the server resolves it back into the caller's session, so every command runs the same handler — and the same staff_review gate, role check, and authored_by provenance — the browser does. Sending a retainer for signature stays a deliberate authenticated human command (retainer send); it is never exposed as an LLM-routable tool.

The send is a durable two-step. retainer approve fires approved, the worker durably renders + persists the retainer PDF, and the workflow parks at generate_pdf__retainer_pdf — no envelope yet. retainer send then confirms the PDF is present (notation status shows document_ready:true) and dispatches exactly one envelope. Splitting the two is what makes the pipeline safe against a real worker whose render is a separate durable invocation: send returns 409 with a JSON reason — not an opaque 500 — when the PDF isn't ready yet, so the operator retries rather than racing.

navigator auth login --host www.neonlaw.com           # browser → ~8h token, stored 0600 at ~/.navigator.json
navigator auth whoami                                  # "nick@neonlaw.com (admin) — expires in 7h52m"
navigator projects list                           # table (or --json)
navigator project open shook-estate               # prints the matter workbench URL if visible
navigator notation create onboarding__retainer \
  --project shook-estate --client-email nick@shook.family
navigator retainer approve <notation-id>          # renders + parks the PDF (no envelope)
navigator notation status <notation-id>           # state + signature request id + document_ready
navigator retainer send <notation-id>             # dispatches one real envelope (409 until document_ready)
navigator auth logout

--host is optional after a single login (the sole stored host is used); pass it to pick between prod, staging, and a local http://localhost:8080 KIND run, each keyed separately in the credential file.

Creating a matter or notation — project create or notation create — first runs the conflict check before any row is written. The CLI is non-interactive, so any finding (a hard adverse block or a softer review item) refuses the open; resolve it in the portal, where authorized staff can review and acknowledge the findings, then retry. The graph itself is documented in the glossary. project open PROJECT_CODE does not create anything; it only opens an existing matter the stored token can already see.

The DB-backed project create command also requires --product-code <code> so every new matter is correlated to an active service in the product catalog before any notation is added.

Forming an LLC from the CLI

A person can form a Nevada LLC end to end without opening a browser. notation create starts a questionnaire-driven onboarding__* Notation inside an existing matter; intake answer then walks the questionnaire one question at a time over the same /staff/notations/:id/step route the browser POSTs. The CLI reads each question's prompt, answer_type, and (for a radio) its choices from that route's ?format=json branch — it never scrapes HTML — and posts a people_list answer as the widget's p{row}_{part} fields.

notation create --project <project-code> resolves the matter through the projects visible to the stored token. The template is read from that Project's git repo (templates/<code>.md at HEAD) when authored there, else from the bundled firm catalog, then auto-saved as an immutable project-scoped version and opened pinned to it — one front door, no separate import step. The template is validated first and creation refuses loudly if it has any blocking rule violation.

In interactive mode intake answer shows one prompt per question — a radio lists its choices, and a people_list is entered row by row (a blank name ends the rows).

Pass --transcript <file> to pre-fill the walk from a recorded sitting. Batch coverage runs server-side first: the questions the transcript answers are seeded as proposed answers (source = extracted) that the walk then offers as Enter-to-accept defaults — never silently accepted — and the questions it did not cover still prompt. It is an input mode of the same walk, so the interactive or scripted loop runs afterward either way.

navigator intake answer <notation-id> --transcript /tmp/sitting.txt
navigator auth login --host http://localhost:8080
navigator notation create nv__llc_formation --project libra --client-email libra@example.com
navigator intake answer <notation-id>
navigator notation status <notation-id>
navigator notation approve <notation-id>
navigator notation document <notation-id> --out /tmp/llc.pdf

To script it (no prompts), answer non-interactively — scalar answers in the order the questionnaire asks, and one --person per people_list row:

navigator intake answer <notation-id> \
  --answer "Libra" --answer "libra@example.com" --answer "Bright Star Ventures" \
  --answer "Neon Law Registered Agent" --answer "members" \
  --person 'name=Libra,street=1 Main St,city=Las Vegas,state=NV,zip=89101,country=USA' \
  --answer "2026-07-01"

A clean staff-entered walk auto-renders the packet on the last answer and drives the matter to the signature wait, so notation approve is an idempotent confirmation rather than a separate render step; notation document then downloads the same per-notation PDF the review surface shows. The whole round-trip is proven against an in-process web app in tests/llc_formation_e2e.rs.

Public image assets

assets build resizes + re-encodes the curated source photos (manifest: views::assets::GALLERY) into responsive AVIF, WebP, and JPEG width variants under web/public/img/<slug>/. Standalone blog or illustration heroes can be dropped directly under web/public/img/<slug>/ as PNGs. assets upload then pushes that tree to the public assets bucket (--bucket, default NAVIGATOR_ASSETS_BUCKET) through the cloud crate's StorageService, stamping a bounded Cache-Control (~1 week, never immutable). assets pull is the inverse — it downloads the published files from the bucket back into web/public/img/ so a fresh clone (or any developer without the source JPEGs) can serve the images locally. assets fetch-referenced downloads only the img/… paths referenced under web/content from a public HTTP origin (no GCP ADC) into web/public/ for a developer or operator who wants the real local bytes. assets stub-referenced writes tiny valid placeholders at those same paths for CI image packaging checks after the real public origin has been verified. assets verify closes the loop: because web/public/img/ is gitignored and nothing in git carries the bytes, it fetches every img/… image reference under web/content from an explicit origin and fails if any 404s. The deploy integration job runs it twice after dev e2e: against the public bucket (production publication gate) and against http://localhost:8080/public (KIND local serve gate).

# Curate the gallery (needs the source JPEGs):
cargo run -p cli -- assets build    # /tmp sources → web/public/img
cargo run -p cli -- assets upload   # web/public/img → gs://<project>-assets/img

# Confirm every content image reference is live before shipping:
cargo run -p cli -- assets verify   # web/content refs → GET <NAVIGATOR_ASSET_BASE_URL>/img/…
cargo run -p cli -- assets verify --base-url http://localhost:8080/public

# Hydrate referenced heroes into web/public with the real published bytes:
cargo run -p cli -- assets fetch-referenced --base-url "$PUBLIC_ASSET_ORIGIN"

# Write local placeholders after the real public origin has been verified:
cargo run -p cli -- assets stub-referenced --out web/public

# Restore images on a fresh clone (no source JPEGs needed):
cargo run -p cli -- assets pull     # gs://<project>-assets/img → web/public/img

First-run note. web/public/img/ is gitignored — the image files ship from the bucket in production, never from git. A fresh clone therefore has empty image slots until you populate them. Run assets pull to download the already-published files (no source JPEGs, no re-encode), or assets build if you have the responsive photo sources. This is intentional and matches how workshop/marketing assets are handled; everything else under web/public (Bootstrap, brand SVGs) is tracked and renders immediately. With NAVIGATOR_ASSET_BASE_URL unset the page markup resolves images against /public, so once the directory is populated the KIND dev loop serves them with zero configuration. Full pipeline: docs/assets.md.

Licensed webfonts

GORP Serif WOFF2 files are operator assets, not repository assets. A licensed operator supplies its own Regular and Bold files, then publishes them to fonts/gorp-serif/ in the same public assets bucket:

cargo run -p cli -- assets fonts upload --dir '/path/to/GORP Serif/WOFF'

The command uses NAVIGATOR_ASSETS_BUCKET unless --bucket is supplied and refuses a partial pair. Production reads the public origin from NAVIGATOR_ASSET_BASE_URL; local development can use ignored copies under web/public/fonts/gorp-serif/.

Navigator source code is Apache-2.0/MIT, while GORP Serif is licensed separately from TrashType.

What's next

cli's shipped binary depends on rules and store — no web dep, so it stays small and starts instantly. (web, workflows, and pdf are dev-dependencies only, for the in-process end-to-end test in tests/llc_formation_e2e.rs that drives the binary against a real app on a loopback port; they never link into the shipped binary.) Integration tests under tests/ drive the compiled binary end-to-end via assert_cmd / CARGO_BIN_EXE_navigator against per-test Postgres schemas spun up via store::test_support. To add a subcommand, extend the Command enum in src/main.rs and wire it to a module.