@legionworks/facet
v1.10.5
Published
<p align="center"> <strong>◆ facet</strong> </p>
Maintainers
Readme
Problem
An agent can emit a diagram or chart and see something that looks fine in the transcript. That is not evidence that the artifact rendered. A page can also produce a screenshot or page-shim report that says it rendered correctly; the page controls both claims.
Facet stores the source bytes without interpreting them, then asks independent validation layers what they observed. The verdict is bound to the exact revision SHA. Artifact code can influence the page, but it cannot rewrite the protocol observation after the fact.
What it does
- Publishes
markdown,mermaid,svg,chart, statichtml, and declared-modetsxartifacts. - Stores immutable revisions and keeps a ring of up to 50 per artifact.
- Records a stored Tier 0 verdict in every publish response; Tier 1 isolated browser validation and Tier 2 display-only inspection are explicit follow-up work.
- Compares lexical expectations with protocol, isolated-world, and page-shim observations.
- Serves a sandboxed, loopback gallery with revision-bound evidence and SSE updates.
- Retains Tier 1 WebP captures of the whole artifact, console output, and protocol observations under an owner-only evidence directory. Interactive TSX declares animated WebP eligibility; static artifacts become eligible only when live CSS or Web Animations are detected.
- Defaults the gallery theme to
system; users can choosedarkorlightand the tab keeps that choice. - File-mode export returns local artifact and required sidecar paths with byte counts;
--include-bytesopts into base64 in the envelope. - Keeps the service byte-dumb: renderers and parsers stay outside
src/service/**.
Current gallery captures use the Mermaid fixture in resolved dark and the chart fixture in resolved light. They are 3840×2160 PNG display assets; Tier 1 evidence is WebP for the whole artifact.
Install
Facet is distributed as @legionworks/facet. Bun is the required runtime
(version 1.4.0 or newer); install it first using the official Bun
installation instructions. npm and pnpm
distribute Facet, but do not provide Bun. npm alone is not enough.
Recommended:
bun add -g @legionworks/facetOther distribution channels:
npm i -g @legionworks/facet
pnpm add -g @legionworks/facetFor a zero-install invocation:
bunx @legionworks/facet <verb>The first visual read-back requires the pinned browser. If it is missing,
facet doctor prints the install command. See the CLI reference for
the command contract and MCP reference for
structured-tool-only hosts.
Quickstart from an installed binary
This walkthrough uses the globally installed facet command, not a source
checkout. It creates disposable runtime and export directories.
export FACET_HOME="$(mktemp -d)"
facet status --start
facet doctor
bunx --bun puppeteer browsers install [email protected]
SOURCE='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 80"><rect width="240" height="80" rx="8" fill="#202638"/><text x="20" y="48" fill="white">Facet quickstart</text></svg>'
SOURCE_FILE="$(mktemp)"
printf '%s' "$SOURCE" > "$SOURCE_FILE"
ARTIFACT_ID="$(facet create --project-id demo --slug quickstart --title 'Quickstart' | bun -e 'const x=JSON.parse(await Bun.stdin.text()); if(!x.ok) throw new Error(x.error.code); console.log(x.data.artifact.id)')"
PUBLISH="$(facet publish --artifact-id "$ARTIFACT_ID" --type svg --file "$SOURCE_FILE")"
printf '%s\n' "$PUBLISH" | bun -e 'const x=JSON.parse(await Bun.stdin.text()); if(!x.ok || !x.data.verdict) throw new Error("publish failed"); console.log(JSON.stringify(x.data.verdict))'
REVISION_SHA="$(printf '%s\n' "$PUBLISH" | bun -e 'const x=JSON.parse(await Bun.stdin.text()); console.log(x.data.revision.sha256)')"
facet read-back --artifact-id "$ARTIFACT_ID" --revision-sha "$REVISION_SHA" --tier visual
EXPORT_DIR="$(mktemp -d)"
facet export "$ARTIFACT_ID" --format source --out "$EXPORT_DIR/artifact.svg"doctor checks the local runtime and browser setup and prints the browser
install command if the pinned shell is missing. Install it before visual
read-back. Inspect data.verdict in the publish envelope: an ok command
envelope only confirms transport, and the stored verdict can still have
status: "error".
Verdict language
The gallery and CLI use the same wire enum, glyph, hue, and treatment.
| enum (wire, verbatim) | glyph | hue | treatment |
| ---------------------------- | ----- | --------- | ----------------------------------- |
| ok | ✓ | #c3e88d | outline — proof, not celebration |
| error | ✗ | #ff757f | outline |
| partial:layout_unverified | ◐ | #ffc777 | outline; screenshot required |
| partial:opaque_content | ◐ | #ffc777 | outline; screenshot required |
| partial:external_resources | ◐ | #ffc777 | outline; screenshot required |
| partial:unstable | ◐ | #ffc777 | outline; screenshot required |
| partial:empty_render | ◐ | #ffc777 | empty TSX root; screenshot required |
| tampered | ⊘ | #ff757f | filled alarm badge |
| timeout | ◌ | #737aa2 | dim outline |
| shim_only | ◇ | #737aa2 | dim outline |
| probe_only | ◈ | #737aa2 | dim outline |
partial:external_resources means an artifact references external HTTPS images the no-egress verifier could not load. An ok verdict can still carry Verdict.screenshotError.code: "screenshot_unavailable" when whole-artifact capture cannot be produced; it records the limit without changing the validation result. insecure:unvalidated means level 3 intentionally skipped validation. A missing verdict is UNVERIFIED with no tier.
Why not just screenshot it?
| Approach | What it can establish | Trust boundary |
| --------------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Page-shim claim | Counts reported by JavaScript in the artifact page | Untrusted; a forged report becomes shim_only when the other channels are absent and tampered when it diverges |
| Tier 1 protocol observation | DOM and renderer observations from the verifier, compared with an isolated-world probe and the page shim | Independent of the artifact's own claim; disagreement is tampered |
| Tier 2 display-only view | What a human sees in the user's browser | No validation verdict; useful for inspection, not proof |
A screenshot is evidence of pixels. It is not evidence that the page's own report was honest.
What's in the box
- CLI contract —
create,publish,list,read-back,status,open,promote,instantiate,pin, andexport(source/render with a mandatory sidecar). - Artifact types — Markdown, Mermaid, SVG, Vega-Lite chart specs, static HTML, and static or interactive TSX. Chart data must be inline; external
data.urlforms are rejected. HTML is script-free, has no<style>block orstyle=attribute, and styles from a vendored Tailwind/daisyUI vocabulary (reference). - Gallery — an offline-built shell with a direct-frame render API, a restrictive per-artifact CSP, zoom controls, and revision SSE.
- Validation — a 5 MiB source cap, up to 64 Mermaid blocks and 10,000 Mermaid nodes, and a 1 MiB SVG cap with 16 roots. Tier 1 uses pinned
chrome-headless-shell151.0.7922.77; v9 evidence-format metadata is documented in the Export reference. - Templates — eleven checked-in starting points in
templates/, documented intemplates/README.md.
From source
For contributors working in a checkout, requires Bun 1.4.0 or newer. Bun 1.4.0 fixes oven-sh/bun#37230, the fd-reuse bug that affected CDP-pipe browser runs.
bun install
export FACET_HOME="$(mktemp -d)"
SOURCE='# Facet quickstart'
bun ./src/cli/main.ts status --start
ARTIFACT_ID="$(bun ./src/cli/main.ts create --project-id demo --slug chart --title "Chart" | bun -e 'const x=JSON.parse(await Bun.stdin.text()); if(!x.ok) throw new Error(x.error.code); console.log(x.data.artifact.id)')"
PUBLISH="$(printf '%s' "$SOURCE" | bun ./src/cli/main.ts publish --artifact-id "$ARTIFACT_ID" --type markdown)"
printf '%s\n' "$PUBLISH" | bun -e 'const x=JSON.parse(await Bun.stdin.text()); if(!x.ok || !x.data.verdict) throw new Error("publish failed"); console.log(JSON.stringify(x.data.verdict))'
REVISION_SHA="$(printf '%s\n' "$PUBLISH" | bun -e 'const x=JSON.parse(await Bun.stdin.text()); console.log(x.data.revision.sha256)')"
bun ./src/cli/main.ts read-back --artifact-id "$ARTIFACT_ID" --tier 0
bun ./src/cli/main.ts read-back --artifact-id "$ARTIFACT_ID" --revision-sha "$REVISION_SHA" --tier visual
EXPORT_DIR="$(mktemp -d)"
bun ./src/cli/main.ts export "$ARTIFACT_ID" --format source --out "$EXPORT_DIR/artifact.md"This checkout invocation needs no global link. It uses disposable runtime and export directories. The read-back without --revision-sha uses the latest revision; pass the SHA returned by publish when reproducibility matters. Tier 0 is browser-free; visual read-back is explicit Tier 1 escalation. Each verb writes one JSON envelope to stdout; ok confirms command transport, so inspect data.verdict in the publish envelope, including a stored status: "error".
Facet's insecure mode is an explicit, boot-only opt-in (FACET_INSECURE=1|2|3) and is never the default. It weakens or skips validation by level, marks every affected verdict, and speaks loudly at startup, in envelopes, in the CLI, and in the gallery. FACET_INSECURE_AUTO=1 permits startup probe fallback but never selects level 3. Restart after changing either environment variable.
MCP
On harnesses with shell access, the CLI is the integration; the MCP adapter is for structured-tool-only environments. The npm package includes six CLI-backed tools: facet_create, facet_publish, facet_read_back, facet_status, facet_export, and facet_open_url. It requires Bun 1.4.0. Register it with bun add -g @legionworks/facet followed by the bare facet-mcp command; npx -p @legionworks/facet facet-mcp is the no-install alternative. See the MCP reference for OpenCode, Claude Code, and Codex registration.
Documentation
- Agents — the CLI workflow and adapter boundary.
- CLI reference · MCP reference · Export · HTML reference · TSX reference · HTTP surface · Storage reference · Validation reference · Security
- Architecture · Structure · v1 ship gate · Roadmap
- Contributing
Status
Facet is a young, single-maintainer project. It targets local and single-operator workflows: the service binds to loopback, has no user-account authentication by design, and still protects routes with install/operator bearer capabilities. Interfaces may shift before 1.0. Bun 1.4.0 includes the verified fd-reuse fix for CDP-pipe browser runs. Current TSX compiler and runtime evidence lives in TSX measurements.
License
MIT OR Apache-2.0, at your option. See LICENSE-MIT or LICENSE-APACHE.
