Open your own project
behold opens any chant project. There is nothing to add to your project to get the source graph: no config file, no annotations, no plugin.
The source graph, with nothing set up
Section titled “The source graph, with nothing set up”npx behold preview # the current directorynpx behold preview ../my-project # somewhere elseThat serves your project’s declared graph at http://localhost:4600. It shells your project’s own chant binary, so what you see is what your project builds.
If it doesn’t look right: behold doctor
Section titled “If it doesn’t look right: behold doctor”npx behold doctor # the current directorynpx behold doctor ../my-project --jsonA read-only diagnosis of everything behold needs to serve the project well.
One line each for the project’s kind, its own chant install and version,
declared lexicons, the environments the picker will infer, the kube context
chant binds versus your ambient one, substrate readiness, and committed Ops.
Each is pass, warn or fail with a one-line fix; the exit code is non-zero only
when something failed, so CI can gate on it. It starts no server and changes
nothing.
The distinction it draws is the useful one: a fail is something that stops
behold serving the project (no chant in the project’s node_modules is the
common first-touch one, since behold shells your project’s chant). A warn
costs you the live half rather than the graph: no environment declared, no
cluster binding, an emulator that isn’t up.
Adding the live overlay
Section titled “Adding the live overlay”To colour the graph by what is actually deployed, name an environment:
npx behold serve . --env prodThat needs whatever credentials reading your estate needs; behold uses your ambient environment and holds nothing of its own. Nodes become green (managed), amber (foreign), or blue (declared, not yet observed).
If a read fails, the affected nodes report unobserved rather than being painted absent. A resource behold could not look at is never the same claim as a resource that is not there.
Watching for drift
Section titled “Watching for drift”npx behold serve . --env prod --poll 30Re-queries every 30 seconds and pushes updates to the browser. Add --auto-sync to trigger a committed Op when drift appears: apply heals toward source, pull-request opens a PR adopting live into source. It is off by default and never approves a gate.
Optional: a tier picker
Section titled “Optional: a tier picker”If your project’s source branches on a deployment tier, declare it as a build-time parameter with an enum in chant.config.ts, and behold renders a picker for it:
export default { buildParams: { tier: { type: "string", enum: ["minimal", "prod", "prod-ha"], default: "minimal", env: "KMV_TIER" }, },} satisfies ChantConfig;Your source reads it as params.tier from @intentius/chant/params. When a tier is picked, behold passes it to chant as --param tier=<value>, and also sets the parameter’s env variable when it declares one. behold takes the parameter named tier, or else the first one with a string enum. A project with no such parameter gets no picker.
Before #470 the tier axis was a tiers block in .behold.json. behold no longer reads it, and behold doctor warns where one is left.
Optional: a traffic level for live versus declared
Section titled “Optional: a traffic level for live versus declared”behold can show what your estate costs as the account stands beside what the file says it should, with the difference. Both figures are predictions at a traffic level, and behold never picks one for you. Name it in behold’s per-user settings for the project, a file outside the project at <cache>/behold/settings/<key>.json (~/Library/Caches/behold/settings/ on macOS, ~/.cache/behold/settings/ on Linux, %LOCALAPPDATA%\behold\settings\ on Windows; BEHOLD_CACHE_DIR moves it), where <key> is the same as the hand layout’s:
{ "behaviour": { "traffic": "100 rps, p50" }}A project served before #470 that named it in .behold.json has that block copied into the settings on the first read. behold never writes or deletes .behold.json.
The string goes to chant verbatim as chant graph --traffic. ?traffic= on /api/graph and /api/overlay overrides it for one request. With neither, behold asks for no prediction, which reads as “not looked” and is not a refusal.
With a level, /api/overlay asks chant twice for each member: once about the account (--live) and once about the file. The payload’s meta.behaviour carries live, declared and delta, per member and for the estate, and a card the account no longer holds keeps its declared figure under attrs._behaviourDeclared. The live figure moves with drift. The declared one moves only when a file under the member changes, so behold reads it once per edit.
The figures are an engine’s, reached by chant through CHANT_BEHAVIOUR_ENGINE. behold calls no engine and holds no key. A choudoufu or Terraform member reads through @intentius/chant-lexicon-terraform, which behold installs with itself (#909), since that lexicon is what predicts a .tf root.
Your hand layout
Section titled “Your hand layout”Drag a card or resize a containment box and behold remembers the offset, in your browser and in a per-user cache outside the project (~/Library/Caches/behold/layouts/ on macOS, ~/.cache/behold/layouts/ on Linux, %LOCALAPPDATA%\behold\layouts\ on Windows; BEHOLD_CACHE_DIR moves it). behold writes nothing inside your project, so there is nothing to gitignore. The layout stores offsets rather than positions, so the graph underneath stays chant’s, and behold export bakes it into the exported SVGs.
A .behold/layout.json left by an older behold is read once into the cache and never changed or deleted.
What behold needs from your project
Section titled “What behold needs from your project”Nothing, for the source graph. For the rest:
- Live overlay. An environment your lexicons can read, and credentials for it.
- Action buttons. Ops you committed. Sync appears if the project has an
ApplyOp, Adopt per foreign node if it has aReconcileOp, Run for anything else. A project with no Ops shows no buttons, by design.