@legionworks/facet 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +23 -0
- package/README.md +158 -0
- package/dist/gallery/chunk-4g8rem85.css +656 -0
- package/dist/gallery/chunk-c6s9k9ty.js +5970 -0
- package/dist/gallery/frame/artifact.css +8424 -0
- package/dist/gallery/frame/chunks/abnfDiagram-N423BO3Z-jj74szbm.js +155 -0
- package/dist/gallery/frame/chunks/architecture-TIHT7OUA-cvdjn7dt.js +10 -0
- package/dist/gallery/frame/chunks/architectureDiagram-T3A2C74G-n4z77eg6.js +8675 -0
- package/dist/gallery/frame/chunks/blockDiagram-VBNYF7ZC-7xsqzrd1.js +3529 -0
- package/dist/gallery/frame/chunks/c4Diagram-5PPSVZJV-z2zjs7wx.js +2413 -0
- package/dist/gallery/frame/chunks/classDiagram-JCYQIIEL-ve8rngqp.js +47 -0
- package/dist/gallery/frame/chunks/classDiagram-v2-OCEON4UE-8cfaxb8z.js +47 -0
- package/dist/gallery/frame/chunks/cose-bilkent-JH36ORCC-sn92qexx.js +4822 -0
- package/dist/gallery/frame/chunks/cynefin-VYW2F7L2-tqjv913d.js +10 -0
- package/dist/gallery/frame/chunks/cynefinDiagram-MW4NZA55-c1c0dj12.js +500 -0
- package/dist/gallery/frame/chunks/dagre-VZM6K2ZE-7tyg4yt2.js +476 -0
- package/dist/gallery/frame/chunks/diagram-7IWD3JNH-y4eng9sc.js +526 -0
- package/dist/gallery/frame/chunks/diagram-B4RE2ZJO-rn3p7j5h.js +673 -0
- package/dist/gallery/frame/chunks/diagram-LBJQPF4R-w4a2cm9q.js +255 -0
- package/dist/gallery/frame/chunks/diagram-Q27KOJAE-aqc2v7wy.js +575 -0
- package/dist/gallery/frame/chunks/diagram-UB23O5K3-xncy8nyd.js +355 -0
- package/dist/gallery/frame/chunks/ebnfDiagram-BXEA7PRR-acwgxz16.js +175 -0
- package/dist/gallery/frame/chunks/erDiagram-JOGREHBK-mzp5nswt.js +1332 -0
- package/dist/gallery/frame/chunks/eventmodeling-45OFAUF4-sg07qr0c.js +10 -0
- package/dist/gallery/frame/chunks/flowDiagram-UKHOOZJN-2e5ygs44.js +29 -0
- package/dist/gallery/frame/chunks/ganttDiagram-PKOTCBZU-p2hhh7hr.js +2620 -0
- package/dist/gallery/frame/chunks/gitGraph-TEB2WS4Q-j6kw0naa.js +10 -0
- package/dist/gallery/frame/chunks/gitGraphDiagram-DS77QQ5N-4z9z8wjm.js +1347 -0
- package/dist/gallery/frame/chunks/info-DKCQHKI2-mpt80jd3.js +10 -0
- package/dist/gallery/frame/chunks/infoDiagram-6WML65LV-5rqm3mgg.js +65 -0
- package/dist/gallery/frame/chunks/ishikawaDiagram-WSZJBQD7-b51z96jy.js +975 -0
- package/dist/gallery/frame/chunks/journeyDiagram-NVQOT4AX-9jbfp0pk.js +1272 -0
- package/dist/gallery/frame/chunks/kanban-definition-27J2QSJJ-4x0vx4se.js +1116 -0
- package/dist/gallery/frame/chunks/katex-xfgk95tv.js +14034 -0
- package/dist/gallery/frame/chunks/markdown-1s5vwkf2.js +9 -0
- package/dist/gallery/frame/chunks/markdown-2h6bwsms.js +3239 -0
- package/dist/gallery/frame/chunks/markdown-2x113yh8.js +107 -0
- package/dist/gallery/frame/chunks/markdown-30b7rwmp.js +1943 -0
- package/dist/gallery/frame/chunks/markdown-356bfj4g.js +2031 -0
- package/dist/gallery/frame/chunks/markdown-44n9ea69.js +2406 -0
- package/dist/gallery/frame/chunks/markdown-56enrf2r.js +39 -0
- package/dist/gallery/frame/chunks/markdown-5avymm8t.js +725 -0
- package/dist/gallery/frame/chunks/markdown-6r4svars.js +33 -0
- package/dist/gallery/frame/chunks/markdown-6zz8efx2.js +24 -0
- package/dist/gallery/frame/chunks/markdown-7jp6prnb.js +19 -0
- package/dist/gallery/frame/chunks/markdown-809k7q41.js +55 -0
- package/dist/gallery/frame/chunks/markdown-82xmzhzd.js +1076 -0
- package/dist/gallery/frame/chunks/markdown-92n37gd4.js +7627 -0
- package/dist/gallery/frame/chunks/markdown-ag2sssf8.js +36 -0
- package/dist/gallery/frame/chunks/markdown-b4s7ntq2.js +19 -0
- package/dist/gallery/frame/chunks/markdown-c9k0fvp1.js +100 -0
- package/dist/gallery/frame/chunks/markdown-ceefymn8.js +125 -0
- package/dist/gallery/frame/chunks/markdown-d1wfbs70.js +243 -0
- package/dist/gallery/frame/chunks/markdown-dy5sgjhc.js +11022 -0
- package/dist/gallery/frame/chunks/markdown-dz3z5w30.js +30868 -0
- package/dist/gallery/frame/chunks/markdown-eg6ngz7y.js +30340 -0
- package/dist/gallery/frame/chunks/markdown-ff5wvb43.js +1149 -0
- package/dist/gallery/frame/chunks/markdown-ffg06nfz.js +86 -0
- package/dist/gallery/frame/chunks/markdown-fvfncadf.js +472 -0
- package/dist/gallery/frame/chunks/markdown-gvyszqpv.js +19 -0
- package/dist/gallery/frame/chunks/markdown-gx1p0ew0.js +88 -0
- package/dist/gallery/frame/chunks/markdown-gxye5frz.js +65 -0
- package/dist/gallery/frame/chunks/markdown-h7pzv34e.js +105 -0
- package/dist/gallery/frame/chunks/markdown-hab789t9.js +47 -0
- package/dist/gallery/frame/chunks/markdown-hjp6sj8t.js +125 -0
- package/dist/gallery/frame/chunks/markdown-jka64qgp.js +1993 -0
- package/dist/gallery/frame/chunks/markdown-k8xkn8mw.js +56 -0
- package/dist/gallery/frame/chunks/markdown-m7frq8x8.js +112 -0
- package/dist/gallery/frame/chunks/markdown-p740v4qm.js +36 -0
- package/dist/gallery/frame/chunks/markdown-pxfeh231.js +57 -0
- package/dist/gallery/frame/chunks/markdown-pzgd8z3s.js +87 -0
- package/dist/gallery/frame/chunks/markdown-qx3cngz2.js +1673 -0
- package/dist/gallery/frame/chunks/markdown-rawhfvvc.js +85 -0
- package/dist/gallery/frame/chunks/markdown-rd8w0ng9.js +49 -0
- package/dist/gallery/frame/chunks/markdown-rm2yxfjj.js +2773 -0
- package/dist/gallery/frame/chunks/markdown-s1jatrnk.js +993 -0
- package/dist/gallery/frame/chunks/markdown-sgfg5vvq.js +59 -0
- package/dist/gallery/frame/chunks/markdown-t4ethv9d.js +409 -0
- package/dist/gallery/frame/chunks/markdown-tnmabqvz.js +36 -0
- package/dist/gallery/frame/chunks/markdown-tvdpppw8.js +5292 -0
- package/dist/gallery/frame/chunks/markdown-v0x27twr.js +22 -0
- package/dist/gallery/frame/chunks/markdown-v14td68x.js +36 -0
- package/dist/gallery/frame/chunks/markdown-w6sgd54z.js +36 -0
- package/dist/gallery/frame/chunks/markdown-xw8dsk8b.js +2125 -0
- package/dist/gallery/frame/chunks/markdown-y18mve73.js +85 -0
- package/dist/gallery/frame/chunks/markdown-yengsms5.js +427 -0
- package/dist/gallery/frame/chunks/markdown-z1wc0v0d.js +1826 -0
- package/dist/gallery/frame/chunks/markdown-zazx9h4n.js +329 -0
- package/dist/gallery/frame/chunks/mermaid-de7p4v0x.js +30 -0
- package/dist/gallery/frame/chunks/mindmap-definition-FAOFIHXS-ra44cbem.js +1216 -0
- package/dist/gallery/frame/chunks/packet-7NZHBO7P-xppm5913.js +10 -0
- package/dist/gallery/frame/chunks/pegDiagram-VL7TDLO6-a2a4wg03.js +163 -0
- package/dist/gallery/frame/chunks/pie-RZYD4A2V-hnbxk152.js +10 -0
- package/dist/gallery/frame/chunks/pieDiagram-7S7Q4E2Y-f1vxjtmx.js +311 -0
- package/dist/gallery/frame/chunks/quadrantDiagram-CIZ2JOQS-ywafa6kh.js +1390 -0
- package/dist/gallery/frame/chunks/radar-I7S5WNFK-ewqd27se.js +10 -0
- package/dist/gallery/frame/chunks/railroad-3IZDKUUU-5hy4bdx3.js +10 -0
- package/dist/gallery/frame/chunks/railroad-abnf-AHOZXSZD-bh5sxz0d.js +10 -0
- package/dist/gallery/frame/chunks/railroad-ebnf-EBAXGLYW-6b9c3zeh.js +10 -0
- package/dist/gallery/frame/chunks/railroad-peg-LSFZ7HO6-avc8v39s.js +10 -0
- package/dist/gallery/frame/chunks/railroadDiagram-AXF67PYL-qc61wax4.js +131 -0
- package/dist/gallery/frame/chunks/requirementDiagram-LRYGKXZP-9bs4786c.js +1295 -0
- package/dist/gallery/frame/chunks/sankeyDiagram-W5VNT64P-awt1em8b.js +1339 -0
- package/dist/gallery/frame/chunks/sequenceDiagram-SI44F4Z6-qaf9mz3t.js +4324 -0
- package/dist/gallery/frame/chunks/sizeCapture-X5ZJPWSS-9fq685xd.js +64 -0
- package/dist/gallery/frame/chunks/stateDiagram-OKZ733FA-vx7a7hh0.js +456 -0
- package/dist/gallery/frame/chunks/stateDiagram-v2-UEYNNEHI-x4m6ws5t.js +46 -0
- package/dist/gallery/frame/chunks/swimlanes-SLNWSIFB-e4mxm1gh.js +8287 -0
- package/dist/gallery/frame/chunks/swimlanesDiagram-ULZ7WXOC-0h93e2qq.js +42 -0
- package/dist/gallery/frame/chunks/timeline-definition-Z64GVDOM-wp0bhbzg.js +1549 -0
- package/dist/gallery/frame/chunks/treeView-QDETBFTQ-hcx7n0vt.js +10 -0
- package/dist/gallery/frame/chunks/treemap-6X3UGDF4-av02skdw.js +10 -0
- package/dist/gallery/frame/chunks/vennDiagram-T6HMQDX7-pmvk1p7n.js +2566 -0
- package/dist/gallery/frame/chunks/wardley-OPB4EBWU-njsp6e40.js +10 -0
- package/dist/gallery/frame/chunks/wardleyDiagram-T6FBY63Y-eggmnv4z.js +979 -0
- package/dist/gallery/frame/chunks/xychartDiagram-ELKLHX3M-q40tnpe5.js +1954 -0
- package/dist/gallery/frame/frame.css +183 -0
- package/dist/gallery/frame/runtime/chart.js +42744 -0
- package/dist/gallery/frame/runtime/html.js +14 -0
- package/dist/gallery/frame/runtime/markdown.js +1403 -0
- package/dist/gallery/frame/runtime/mermaid.js +32 -0
- package/dist/gallery/frame/runtime/svg.js +14 -0
- package/dist/gallery/frame/runtime/tsx.js +52 -0
- package/dist/gallery/index.html +104 -0
- package/docs/reference/cli.md +163 -0
- package/docs/reference/export.md +106 -0
- package/docs/reference/html.md +215 -0
- package/docs/reference/http.md +85 -0
- package/docs/reference/mcp.md +78 -0
- package/docs/reference/security.md +119 -0
- package/docs/reference/storage.md +93 -0
- package/docs/reference/tsx.md +101 -0
- package/docs/reference/validation.md +212 -0
- package/package.json +89 -0
- package/scripts/build-gallery.ts +127 -0
- package/scripts/launch-netns.sh +67 -0
- package/skills/facet/SKILL.md +100 -0
- package/src/cli/client.ts +311 -0
- package/src/cli/commands/create.ts +49 -0
- package/src/cli/commands/doctor.ts +249 -0
- package/src/cli/commands/export.ts +179 -0
- package/src/cli/commands/instantiate.ts +35 -0
- package/src/cli/commands/list.ts +49 -0
- package/src/cli/commands/open.ts +75 -0
- package/src/cli/commands/pin.ts +39 -0
- package/src/cli/commands/promote.ts +49 -0
- package/src/cli/commands/publish.ts +122 -0
- package/src/cli/commands/read-back.ts +56 -0
- package/src/cli/commands/status.ts +114 -0
- package/src/cli/commands/watch.ts +146 -0
- package/src/cli/main.ts +522 -0
- package/src/cli/output.ts +106 -0
- package/src/cli/parser.ts +376 -0
- package/src/cli/presenter.ts +212 -0
- package/src/cli/service-metadata.ts +134 -0
- package/src/cli/spawn-service.ts +326 -0
- package/src/css.d.ts +4 -0
- package/src/gallery-web/app.ts +1581 -0
- package/src/gallery-web/export-menu.ts +121 -0
- package/src/gallery-web/export.ts +112 -0
- package/src/gallery-web/favicon.ts +54 -0
- package/src/gallery-web/frame/entries/chart.ts +6 -0
- package/src/gallery-web/frame/entries/html.ts +6 -0
- package/src/gallery-web/frame/entries/markdown.ts +6 -0
- package/src/gallery-web/frame/entries/mermaid.ts +6 -0
- package/src/gallery-web/frame/entries/svg.ts +6 -0
- package/src/gallery-web/frame/entries/tsx.ts +6 -0
- package/src/gallery-web/frame/frame-payload.ts +91 -0
- package/src/gallery-web/frame/renderer-validation.ts +13 -0
- package/src/gallery-web/frame/renderers/chart.ts +182 -0
- package/src/gallery-web/frame/renderers/dompurify-shim.ts +112 -0
- package/src/gallery-web/frame/renderers/html.ts +48 -0
- package/src/gallery-web/frame/renderers/markdown.ts +81 -0
- package/src/gallery-web/frame/renderers/mermaid.ts +120 -0
- package/src/gallery-web/frame/renderers/registry.ts +260 -0
- package/src/gallery-web/frame/renderers/svg.ts +393 -0
- package/src/gallery-web/frame/renderers/tsx.ts +61 -0
- package/src/gallery-web/frame/runtime.ts +501 -0
- package/src/gallery-web/frame/styles/artifact.css +8424 -0
- package/src/gallery-web/frame/styles/frame.css +183 -0
- package/src/gallery-web/frame/styles/html-source.css +23 -0
- package/src/gallery-web/frame/view-box.ts +33 -0
- package/src/gallery-web/frame-html.ts +72 -0
- package/src/gallery-web/index.html +104 -0
- package/src/gallery-web/session.ts +107 -0
- package/src/gallery-web/sse-client.ts +212 -0
- package/src/gallery-web/styles/app.css +420 -0
- package/src/gallery-web/styles/tokens.css +112 -0
- package/src/gallery-web/styles/verdict.css +186 -0
- package/src/gallery-web/swap.ts +64 -0
- package/src/gallery-web/theme.ts +40 -0
- package/src/gallery-web/view-state.ts +93 -0
- package/src/harness-adapters/claude-code/README.md +10 -0
- package/src/harness-adapters/claude-code/facet.sh +4 -0
- package/src/harness-adapters/codex/README.md +10 -0
- package/src/harness-adapters/codex/facet.sh +4 -0
- package/src/harness-adapters/mcp/README.md +13 -0
- package/src/harness-adapters/mcp/cli-bridge.ts +216 -0
- package/src/harness-adapters/mcp/main.ts +15 -0
- package/src/harness-adapters/mcp/server.ts +164 -0
- package/src/harness-adapters/mcp/tool-schemas.ts +53 -0
- package/src/harness-adapters/opencode/README.md +10 -0
- package/src/harness-adapters/opencode/facet.sh +4 -0
- package/src/runtime/compiled-entrypoints.ts +69 -0
- package/src/service/dispatcher.ts +717 -0
- package/src/service/export.ts +218 -0
- package/src/service/http-utils.ts +55 -0
- package/src/service/lexical/expectations.ts +210 -0
- package/src/service/lifecycle/idle-controller.ts +125 -0
- package/src/service/lifecycle/orphan-cleanup.ts +128 -0
- package/src/service/lifecycle/process-lock.ts +235 -0
- package/src/service/main.ts +136 -0
- package/src/service/process.ts +240 -0
- package/src/service/router-guards.ts +239 -0
- package/src/service/router.ts +627 -0
- package/src/service/security/auth.ts +168 -0
- package/src/service/security/host-origin.ts +138 -0
- package/src/service/security/http-guards.ts +13 -0
- package/src/service/security/leases.ts +182 -0
- package/src/service/security/token-store.ts +138 -0
- package/src/service/server.ts +356 -0
- package/src/service/store/database.ts +61 -0
- package/src/service/store/evidence-retention.ts +234 -0
- package/src/service/store/migrations.ts +167 -0
- package/src/service/store/repository-lifecycle.ts +121 -0
- package/src/service/store/repository.ts +732 -0
- package/src/service/store/schema.ts +218 -0
- package/src/service/stored-verdict.ts +97 -0
- package/src/service/stream.ts +301 -0
- package/src/service/verdict-enrichment.ts +45 -0
- package/src/shared/build/frame-bundle-plugins.ts +61 -0
- package/src/shared/build-mode.ts +28 -0
- package/src/shared/config/evidence-read.ts +98 -0
- package/src/shared/config/limits.ts +56 -0
- package/src/shared/config/paths.ts +96 -0
- package/src/shared/contracts/artifact-types.ts +2 -0
- package/src/shared/contracts/artifact.ts +98 -0
- package/src/shared/contracts/commands/_shared.ts +59 -0
- package/src/shared/contracts/commands/guards.ts +86 -0
- package/src/shared/contracts/commands/index.ts +204 -0
- package/src/shared/contracts/commands/names.ts +35 -0
- package/src/shared/contracts/commands/requests.ts +149 -0
- package/src/shared/contracts/commands/results.ts +200 -0
- package/src/shared/contracts/envelope.ts +140 -0
- package/src/shared/contracts/events.ts +41 -0
- package/src/shared/contracts/observed-counts.ts +19 -0
- package/src/shared/contracts/renderers.ts +6 -0
- package/src/shared/contracts/validation.ts +296 -0
- package/src/shared/errors/facet-error.ts +127 -0
- package/src/shared/errors/store-error.ts +68 -0
- package/src/shared/evidence-image.ts +37 -0
- package/src/shared/export.ts +31 -0
- package/src/shared/html/policy.ts +108 -0
- package/src/shared/html/style-vocabulary.ts +352 -0
- package/src/shared/logging/logger.ts +108 -0
- package/src/shared/logging/redact.ts +49 -0
- package/src/shared/security/frozen-csp.ts +13 -0
- package/src/shared/storage-version.ts +2 -0
- package/src/shared/tsx/execution.ts +29 -0
- package/src/shared/tsx/import-policy.ts +94 -0
- package/src/shared/util/dir-permissions.ts +94 -0
- package/src/shared/util/mermaid-nodes.ts +230 -0
- package/src/shared/util/process.ts +67 -0
- package/src/shared/util/time.ts +21 -0
- package/src/shared/version.ts +3 -0
- package/src/validation/sandbox/limits.ts +41 -0
- package/src/validation/sandbox/netns.ts +177 -0
- package/src/validation/tier0/chart.ts +177 -0
- package/src/validation/tier0/dom-shim.ts +54 -0
- package/src/validation/tier0/html.ts +408 -0
- package/src/validation/tier0/markdown.ts +285 -0
- package/src/validation/tier0/mermaid.ts +98 -0
- package/src/validation/tier0/runner.ts +504 -0
- package/src/validation/tier0/svg.ts +321 -0
- package/src/validation/tier0/tsx/allowlist-resolver.ts +77 -0
- package/src/validation/tier0/tsx/ast-policy-capabilities.ts +238 -0
- package/src/validation/tier0/tsx/ast-policy-shared.ts +71 -0
- package/src/validation/tier0/tsx/ast-policy.ts +181 -0
- package/src/validation/tier0/tsx/compiler.ts +156 -0
- package/src/validation/tier0/worker-dispatch.ts +211 -0
- package/src/validation/tier0/worker-entry.ts +96 -0
- package/src/validation/tier0/worker-input.ts +75 -0
- package/src/validation/tier1/browser-process.ts +147 -0
- package/src/validation/tier1/cdp-pipe.ts +281 -0
- package/src/validation/tier1/entries/chart.ts +5 -0
- package/src/validation/tier1/entries/html.ts +6 -0
- package/src/validation/tier1/entries/markdown.ts +5 -0
- package/src/validation/tier1/entries/mermaid.ts +5 -0
- package/src/validation/tier1/entries/svg.ts +5 -0
- package/src/validation/tier1/entries/tsx.ts +5 -0
- package/src/validation/tier1/frame-target.ts +162 -0
- package/src/validation/tier1/harness-entry.ts +261 -0
- package/src/validation/tier1/harness.ts +215 -0
- package/src/validation/tier1/isolated-probe.ts +94 -0
- package/src/validation/tier1/launcher.ts +177 -0
- package/src/validation/tier1/limits.ts +80 -0
- package/src/validation/tier1/nonce.ts +17 -0
- package/src/validation/tier1/protocol-probe.ts +519 -0
- package/src/validation/tier1/runner.ts +1122 -0
- package/src/validation/tier1/verdict.ts +246 -0
- package/src/validation/tier1/webp.ts +69 -0
- package/templates/README.md +88 -0
- package/templates/bar-compare.vl.json +63 -0
- package/templates/capacity-report.tsx +102 -0
- package/templates/decision-record.md +24 -0
- package/templates/deployment-state.mmd +40 -0
- package/templates/exemplar.md +91 -0
- package/templates/facet-story.tsx +799 -0
- package/templates/fleet-dashboard.html +141 -0
- package/templates/html-release-ledger.html +91 -0
- package/templates/html-status-report.html +114 -0
- package/templates/incident-console.tsx +147 -0
- package/templates/legion-boundaries.mmd +23 -0
- package/templates/legion-flow.mmd +15 -0
- package/templates/legion-sequence.mmd +14 -0
- package/templates/legion-state.mmd +7 -0
- package/templates/metric-card.svg +29 -0
- package/templates/observability-map.svg +101 -0
- package/templates/pipeline-audit.md +108 -0
- package/templates/release-metrics.vl.json +98 -0
- package/templates/service-topology.mmd +47 -0
- package/templates/status-report.md +32 -0
- package/templates/system-map.svg +53 -0
- package/templates/timeseries.vl.json +71 -0
- package/templates/tsx-interactive-counter.tsx +19 -0
- package/templates/tsx-status-report.tsx +31 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: facet
|
|
3
|
+
description: Use when creating, publishing, validating, exporting, or inspecting Facet artifacts, diagrams, charts, SVG, Markdown, or gallery output; when choosing between Markdown, Mermaid, SVG, chart, HTML, and TSX; or when a Facet CLI envelope or typed error needs interpretation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Facet
|
|
7
|
+
|
|
8
|
+
Install `@legionworks/facet` with `bun add -g` (recommended), `npm i -g`, or
|
|
9
|
+
`pnpm add -g`; use `bunx @legionworks/facet <verb>` without a global install.
|
|
10
|
+
Bun `1.4.0` or newer is the required runtime. npm and pnpm are distribution
|
|
11
|
+
channels only. The pinned browser downloads on the first visual read-back.
|
|
12
|
+
|
|
13
|
+
Facet stores and verifies artifact bytes through a versioned CLI envelope. Keep
|
|
14
|
+
artifact bytes separate from host capabilities.
|
|
15
|
+
|
|
16
|
+
## Choose the artifact type
|
|
17
|
+
|
|
18
|
+
| Need | Type | Why |
|
|
19
|
+
| ------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------- |
|
|
20
|
+
| Prose or mixed documents | Markdown | Portable authored content. |
|
|
21
|
+
| One diagram | Mermaid | A single declarative diagram. |
|
|
22
|
+
| Authored vector output | SVG | Precise vector output under the SVG contract. |
|
|
23
|
+
| Data visualization | chart | Vega-Lite data visualization; `svg` is default, `canvas` only when chosen for the chart. |
|
|
24
|
+
| Static semantic report | HTML | Static semantic report with vendored styles. |
|
|
25
|
+
| Component composition | TSX: `static` \| `interactive` | Use `static` by default; use `interactive` only when runtime state or behavior is required. |
|
|
26
|
+
|
|
27
|
+
## Publish and check
|
|
28
|
+
|
|
29
|
+
1. Cold-start check: when environment, launch, or permission errors are present, run `facet doctor`; it is read-only and reports literal repair commands. Check status next and start only when needed: `facet status --start`.
|
|
30
|
+
2. Create or reuse an artifact: `facet create --project-id <id> --slug <slug> --title <title>`.
|
|
31
|
+
3. Publish source bytes with one of these forms:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
facet publish --artifact-id <id> --type markdown --file path/to/source.md
|
|
35
|
+
facet publish --artifact-id <id> --type markdown --file -
|
|
36
|
+
printf '%s' "$SOURCE" | facet publish --artifact-id <id> --type markdown
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Use `--watch --file <path>` for operator-led iterative authoring; it streams one publish envelope per changed attempt until Ctrl-C.
|
|
40
|
+
|
|
41
|
+
4. For every publish response, branch on top-level `ok`. If it is true, separately inspect `data.verdict.status`, `tier`, `artifactId`, and `revisionSha`. `ok: true` only confirms command transport; `status: "error"` is a stored validation result, not a transport refusal.
|
|
42
|
+
5. Read back Tier 0/latest by default: `facet read-back --artifact-id <id>`. Omit `--revision-sha` for the latest revision; pass its SHA only to pin reproducible read-back. Request `--tier 1` or `--tier visual` only when browser-backed evidence is needed.
|
|
43
|
+
|
|
44
|
+
## Execution and evidence
|
|
45
|
+
|
|
46
|
+
`--execution static|interactive` applies to TSX only. `static` is the default;
|
|
47
|
+
`interactive` is for client runtime state or behavior. Verdicts carry execution
|
|
48
|
+
only for TSX. Interactive TSX is animation-eligible without probing; static
|
|
49
|
+
artifacts become animation-eligible only after live CSS or Web Animations are
|
|
50
|
+
detected.
|
|
51
|
+
|
|
52
|
+
Gallery display defaults to `system`; users may select `dark` or `light`, and
|
|
53
|
+
the tab persists that choice. Tier 1 stays dark for deterministic parity.
|
|
54
|
+
|
|
55
|
+
## Export
|
|
56
|
+
|
|
57
|
+
Use `facet export <artifactId> --format source` for stored source, or
|
|
58
|
+
`--format render` for retained Tier 1 evidence. Exports write local artifact and
|
|
59
|
+
mandatory sidecar paths plus byte count by default. Use `--include-bytes` only
|
|
60
|
+
when an envelope consumer genuinely needs base64 bytes. Render export preserves
|
|
61
|
+
the detected evidence format:
|
|
62
|
+
|
|
63
|
+
- detected WebP for new captures;
|
|
64
|
+
- detected PNG for legacy captures.
|
|
65
|
+
|
|
66
|
+
## Promotion
|
|
67
|
+
|
|
68
|
+
Promotion is operator-only. The CLI discovers the token from
|
|
69
|
+
`FACET_PROMOTE_TOKEN`, then `FACET_HOME/secrets/promote.token`; never put a
|
|
70
|
+
token on argv.
|
|
71
|
+
|
|
72
|
+
## Error quick reference
|
|
73
|
+
|
|
74
|
+
| Error | Do next |
|
|
75
|
+
| ------------------------ | ---------------------------------------------------------------------------------------------------------------- |
|
|
76
|
+
| `invalid_request` | Correct the command flags, artifact type, or source bytes, then retry. |
|
|
77
|
+
| `artifact_not_found` | List or create the artifact, then use its returned ID. |
|
|
78
|
+
| `revision_not_found` | Omit the SHA for latest, or replace it with a returned revision SHA. |
|
|
79
|
+
| `duplicate_revision` | Use `error.details.revisionSha` as the existing revision SHA; do not republish identical bytes. |
|
|
80
|
+
| `evidence_unavailable` | Request Tier 1 evidence for that revision before a render export, or export source instead. |
|
|
81
|
+
| `output_unwritable` | Choose a writable `--out` path, resolve collisions, or use `--force` deliberately. |
|
|
82
|
+
| `tier0_*` | Repair the Tier 0 worker or its environment; a returned `status: "error"` is a verdict, not this envelope error. |
|
|
83
|
+
| `tier1_*` | Repair browser-backed validation availability, then retry the requested Tier 1 read-back. |
|
|
84
|
+
| `screenshot_unavailable` | Inspect `Verdict.screenshotError`; it is a degraded stored verdict field, not an envelope error. |
|
|
85
|
+
|
|
86
|
+
## Boundaries
|
|
87
|
+
|
|
88
|
+
- Promotion is operator-only. Secrets never enter artifacts, notes, or argv.
|
|
89
|
+
- The service is byte-dumb: do not call loopback routes, import renderers, or
|
|
90
|
+
parse artifact bytes outside the documented CLI workflow.
|
|
91
|
+
- Do not run `facet open` as an agent: it launches local `xdg-open` for human
|
|
92
|
+
display on the operator's desktop. Ensure the service is active with
|
|
93
|
+
`facet status --start`, then use the documented [Steel/browser workflow](../../docs/guides/agents.md)
|
|
94
|
+
or ask a human to inspect.
|
|
95
|
+
- Preserve stdout exactly so callers can parse the versioned envelope.
|
|
96
|
+
- With shell access, the CLI is the integration — MCP is for structured-tool-only hosts, even when the host owns an MCP client. On that surface, `facet_open_url` is safe because it always uses `--no-launch`.
|
|
97
|
+
|
|
98
|
+
## Deeper reference
|
|
99
|
+
|
|
100
|
+
→ [Agent workflow](../../docs/guides/agents.md) · [CLI](../../docs/reference/cli.md) · [MCP](../../docs/reference/mcp.md) · [Export](../../docs/reference/export.md) · [Validation](../../docs/reference/validation.md) · [HTML](../../docs/reference/html.md) · [TSX](../../docs/reference/tsx.md) · [Security](../../docs/reference/security.md)
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loopback HTTP client used by every CLI verb.
|
|
3
|
+
*
|
|
4
|
+
* The CLI talks to the service over HTTP only — never to the
|
|
5
|
+
* repository or any renderer module directly. `sendCommand` wraps
|
|
6
|
+
* the request in a strict `FacetEnvelope`, sends it with Bearer auth
|
|
7
|
+
* and the correct Host header, and parses the response through the
|
|
8
|
+
* shared `FacetEnvelopeSchema` so a forged or drift-ed response
|
|
9
|
+
* surfaces as a typed error envelope.
|
|
10
|
+
*
|
|
11
|
+
* Source bytes for `publish` are base64-encoded on the wire; the
|
|
12
|
+
* schema's `PublishBytesSchema` accepts only valid base64, so an
|
|
13
|
+
* invalid input is rejected at parse time (typed `invalid_request`).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { errEnvelope, parseEnvelope, type FacetEnvelope } from "../shared/contracts/envelope";
|
|
17
|
+
import {
|
|
18
|
+
CommandRequestSchema,
|
|
19
|
+
CommandResultSchema,
|
|
20
|
+
type CommandRequest,
|
|
21
|
+
type CommandResult,
|
|
22
|
+
} from "../shared/contracts/commands";
|
|
23
|
+
|
|
24
|
+
const TIER1_TRACE = process.env.FACET_TIER1_TRACE === "1";
|
|
25
|
+
export const FACET_CLIENT_COMMAND_TIMEOUT_MS = 75_000;
|
|
26
|
+
|
|
27
|
+
function traceTier1Transport(stage: string): void {
|
|
28
|
+
if (!TIER1_TRACE) return;
|
|
29
|
+
process.stderr.write(`[tier1-transport] ${stage}\n`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
import { FacetError } from "../shared/errors/facet-error";
|
|
33
|
+
import { generateRequestId } from "../shared/util/time";
|
|
34
|
+
import { isMutationMethod } from "../service/security/http-guards";
|
|
35
|
+
import type { ArtifactType } from "../shared/contracts/artifact-types";
|
|
36
|
+
import type { Renderer } from "../shared/contracts/renderers";
|
|
37
|
+
import type { ScreenshotError, TsxExecutionMode } from "../shared/contracts/validation";
|
|
38
|
+
|
|
39
|
+
export interface FacetClientOptions {
|
|
40
|
+
readonly baseUrl: string;
|
|
41
|
+
readonly installToken: string;
|
|
42
|
+
readonly promoteToken?: string;
|
|
43
|
+
readonly commandTimeoutMs?: number;
|
|
44
|
+
/** `fetch` indirection so tests can stub network. */
|
|
45
|
+
readonly fetchImpl?: typeof fetch;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SendCommandOptions {
|
|
49
|
+
/** Set true for any state-changing verb (default: derived from verb name). */
|
|
50
|
+
readonly isMutation?: boolean;
|
|
51
|
+
/** Extra headers to merge into the request (Content-Type is added automatically on mutations). */
|
|
52
|
+
readonly extraHeaders?: Readonly<Record<string, string>>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class FacetClient {
|
|
56
|
+
readonly #baseUrl: string;
|
|
57
|
+
readonly #installToken: string;
|
|
58
|
+
readonly #promoteToken: string | undefined;
|
|
59
|
+
readonly #commandTimeoutMs: number;
|
|
60
|
+
readonly #fetchImpl: typeof fetch;
|
|
61
|
+
|
|
62
|
+
constructor(options: FacetClientOptions) {
|
|
63
|
+
this.#baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
64
|
+
this.#installToken = options.installToken;
|
|
65
|
+
this.#promoteToken = options.promoteToken;
|
|
66
|
+
this.#commandTimeoutMs = options.commandTimeoutMs ?? FACET_CLIENT_COMMAND_TIMEOUT_MS;
|
|
67
|
+
this.#fetchImpl = options.fetchImpl ?? fetch;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Build a typed `CommandRequest` for the given verb + args, parse
|
|
72
|
+
* it through the shared schema (so a malformed CLI surface fails
|
|
73
|
+
* here, not in the service), wrap it in an envelope, POST to the
|
|
74
|
+
* loopback service, and return the parsed response envelope.
|
|
75
|
+
*
|
|
76
|
+
* On a non-2xx HTTP response the service still returns a valid
|
|
77
|
+
* envelope (the router wraps every error in one), so the only
|
|
78
|
+
* `throw` path is a transport-level failure (connection refused,
|
|
79
|
+
* DNS error, etc.). Those surface as `FacetError("invalid_envelope",
|
|
80
|
+
* ...)` so the caller can wrap them in a typed envelope for the
|
|
81
|
+
* adapter.
|
|
82
|
+
*/
|
|
83
|
+
async sendCommand(
|
|
84
|
+
command: CommandRequest,
|
|
85
|
+
options: SendCommandOptions = {},
|
|
86
|
+
): Promise<FacetEnvelope<CommandResult>> {
|
|
87
|
+
// Re-parse the command via the strict schema — a forged or
|
|
88
|
+
// drift-ed CLI surface that produced a malformed request fails
|
|
89
|
+
// here with a typed issue list, never reaching the wire.
|
|
90
|
+
const parsed = CommandRequestSchema.parse(command);
|
|
91
|
+
const innerRequestId = parsed.requestId;
|
|
92
|
+
const isMutation = options.isMutation ?? isMutationMethodFor(parsed.command);
|
|
93
|
+
const envelope: FacetEnvelope<CommandRequest> = {
|
|
94
|
+
schemaVersion: "facet.v1",
|
|
95
|
+
requestId: generateRequestId(),
|
|
96
|
+
ok: true,
|
|
97
|
+
data: parsed,
|
|
98
|
+
};
|
|
99
|
+
const headers: Record<string, string> = {
|
|
100
|
+
authorization: `Bearer ${parsed.command === "promote" && this.#promoteToken !== undefined ? this.#promoteToken : this.#installToken}`,
|
|
101
|
+
host: new URL(this.#baseUrl).host,
|
|
102
|
+
};
|
|
103
|
+
if (isMutation) {
|
|
104
|
+
headers["content-type"] = "application/json";
|
|
105
|
+
}
|
|
106
|
+
if (options.extraHeaders) {
|
|
107
|
+
Object.assign(headers, options.extraHeaders);
|
|
108
|
+
}
|
|
109
|
+
let res: Response;
|
|
110
|
+
let text: string;
|
|
111
|
+
const signal = AbortSignal.timeout(this.#commandTimeoutMs);
|
|
112
|
+
try {
|
|
113
|
+
traceTier1Transport(`client:fetch:start command=${parsed.command}`);
|
|
114
|
+
res = await this.#fetchImpl(`${this.#baseUrl}/api/v1/commands`, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers,
|
|
117
|
+
body: JSON.stringify(envelope),
|
|
118
|
+
signal,
|
|
119
|
+
});
|
|
120
|
+
traceTier1Transport(`client:fetch:complete command=${parsed.command} status=${res.status}`);
|
|
121
|
+
traceTier1Transport(`client:body:start command=${parsed.command}`);
|
|
122
|
+
text = await res.text();
|
|
123
|
+
traceTier1Transport(`client:body:complete command=${parsed.command} bytes=${text.length}`);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
const timedOut = signal.aborted;
|
|
126
|
+
throw new FacetError(
|
|
127
|
+
"invalid_envelope",
|
|
128
|
+
timedOut
|
|
129
|
+
? `Connection timed out after ${this.#commandTimeoutMs}ms`
|
|
130
|
+
: `Connection failed: ${(error as Error).message}`,
|
|
131
|
+
{
|
|
132
|
+
retryable: true,
|
|
133
|
+
cause: error,
|
|
134
|
+
details: timedOut
|
|
135
|
+
? {
|
|
136
|
+
reason: "connection_timeout",
|
|
137
|
+
host: this.#baseUrl,
|
|
138
|
+
timeoutMs: this.#commandTimeoutMs,
|
|
139
|
+
}
|
|
140
|
+
: { reason: "connection_failed", host: this.#baseUrl },
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
let body: unknown;
|
|
145
|
+
try {
|
|
146
|
+
body = JSON.parse(text);
|
|
147
|
+
} catch {
|
|
148
|
+
throw new FacetError(
|
|
149
|
+
"invalid_envelope",
|
|
150
|
+
`Service response is not valid JSON (status ${res.status})`,
|
|
151
|
+
{ retryable: false, details: { status: res.status } },
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const parsed2 = parseEnvelope(body);
|
|
155
|
+
if (!parsed2.ok) {
|
|
156
|
+
// The service's response was a parse failure; surface a typed
|
|
157
|
+
// error envelope with the same shape as the wire would.
|
|
158
|
+
return errEnvelope(innerRequestId, parsed2.body) as FacetEnvelope<CommandResult>;
|
|
159
|
+
}
|
|
160
|
+
return parsed2.envelope as FacetEnvelope<CommandResult>;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Map a wire `command` to whether it counts as a mutation. The CLI
|
|
166
|
+
* must set Content-Type for mutations; the service enforces the same
|
|
167
|
+
* check on the other side via the shared `isMutationMethod` helper.
|
|
168
|
+
*/
|
|
169
|
+
function isMutationMethodFor(_command: CommandRequest["command"]): boolean {
|
|
170
|
+
// The CLI only ever sends POST, so every wire command is a
|
|
171
|
+
// mutation from the HTTP-method standpoint; the service's
|
|
172
|
+
// mutation guard is always satisfied. This helper exists so the
|
|
173
|
+
// CLI can express intent in one place and the security
|
|
174
|
+
// classification stays single-sourced.
|
|
175
|
+
void isMutationMethod;
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Result of a successful publish. `artifactId` and `revisionSha` are
|
|
181
|
+
* the canonical IDs every downstream verb (read-back, status, open)
|
|
182
|
+
* carries. Publish is browser-free, so Tier 1 fields remain `null`;
|
|
183
|
+
* request a visual read-back for a Tier 1 verdict.
|
|
184
|
+
*/
|
|
185
|
+
export interface PublishArtifactResult {
|
|
186
|
+
readonly artifactId: string;
|
|
187
|
+
readonly revisionSha: string;
|
|
188
|
+
readonly tier1Status: string | null;
|
|
189
|
+
readonly tier1ScreenshotPath: string | null;
|
|
190
|
+
readonly tier1ScreenshotError: ScreenshotError | null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface PublishArtifactOptions {
|
|
194
|
+
readonly artifactType: ArtifactType;
|
|
195
|
+
readonly renderer?: Renderer;
|
|
196
|
+
readonly bytes: ArrayBuffer;
|
|
197
|
+
readonly slug?: string;
|
|
198
|
+
readonly note?: string;
|
|
199
|
+
readonly execution?: TsxExecutionMode;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Publish a new revision through an existing FacetClient. Resolves
|
|
204
|
+
* the artifact (create-then-publish) and returns the canonical IDs.
|
|
205
|
+
* Used by the acceptance test fixture helper, which needs to spawn
|
|
206
|
+
* a service via the existing CLI infrastructure.
|
|
207
|
+
*/
|
|
208
|
+
export async function publishArtifact(
|
|
209
|
+
client: FacetClient,
|
|
210
|
+
options: PublishArtifactOptions,
|
|
211
|
+
): Promise<PublishArtifactResult> {
|
|
212
|
+
const slug = options.slug ?? `acceptance-${crypto.randomUUID().slice(0, 8)}`;
|
|
213
|
+
const createRes = await client.sendCommand({
|
|
214
|
+
command: "create",
|
|
215
|
+
requestId: generateRequestId(),
|
|
216
|
+
projectId: "/facet",
|
|
217
|
+
slug,
|
|
218
|
+
title: slug,
|
|
219
|
+
});
|
|
220
|
+
if (!createRes.ok) {
|
|
221
|
+
throw FacetError.from(createRes.error);
|
|
222
|
+
}
|
|
223
|
+
const parsed = CommandResultSchema.parse(createRes.data);
|
|
224
|
+
if (parsed.command !== "create") {
|
|
225
|
+
throw new FacetError("invalid_envelope", `expected create result, got ${parsed.command}`);
|
|
226
|
+
}
|
|
227
|
+
const artifactId = parsed.artifact.id;
|
|
228
|
+
const base64 = btoa(String.fromCharCode(...new Uint8Array(options.bytes)));
|
|
229
|
+
const publishRes = await client.sendCommand({
|
|
230
|
+
command: "publish",
|
|
231
|
+
requestId: generateRequestId(),
|
|
232
|
+
artifactId,
|
|
233
|
+
artifactType: options.artifactType,
|
|
234
|
+
renderer: options.renderer ?? "svg",
|
|
235
|
+
bytes: base64,
|
|
236
|
+
...(options.note !== undefined ? { note: options.note } : {}),
|
|
237
|
+
...(options.execution === undefined ? {} : { execution: options.execution }),
|
|
238
|
+
});
|
|
239
|
+
if (!publishRes.ok) {
|
|
240
|
+
throw FacetError.from(publishRes.error);
|
|
241
|
+
}
|
|
242
|
+
const parsedPublish = CommandResultSchema.parse(publishRes.data);
|
|
243
|
+
if (parsedPublish.command !== "publish") {
|
|
244
|
+
throw new FacetError(
|
|
245
|
+
"invalid_envelope",
|
|
246
|
+
`expected publish result, got ${parsedPublish.command}`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
// Publish stays browser-free. Tier 1 is explicit through read-back.
|
|
250
|
+
const tier1Status = parsedPublish.tier1Verdict?.status ?? null;
|
|
251
|
+
return {
|
|
252
|
+
artifactId,
|
|
253
|
+
revisionSha: parsedPublish.revision.sha256,
|
|
254
|
+
tier1Status,
|
|
255
|
+
tier1ScreenshotPath: parsedPublish.tier1Verdict?.screenshotPath ?? null,
|
|
256
|
+
tier1ScreenshotError: parsedPublish.tier1Verdict?.screenshotError ?? null,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export interface ReadBackOptions {
|
|
261
|
+
readonly artifactId: string;
|
|
262
|
+
readonly revisionSha: string;
|
|
263
|
+
readonly tier: 0 | 1 | "visual";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Run `readBack` against an existing FacetClient and return the
|
|
268
|
+
* typed verdict. The service normalizes "visual" → 1 internally.
|
|
269
|
+
*
|
|
270
|
+
* The return shape is the canonical Verdict surface (plus the
|
|
271
|
+
* request-level `renderer`): the typed envelope is schema-validated
|
|
272
|
+
* by `CommandResultSchema.parse`, so the verdict that crosses the
|
|
273
|
+
* wire is already the canonical shape. Field-by-field copying here
|
|
274
|
+
* is the field-drop class (Must 2) — every field added since the
|
|
275
|
+
* helper was written was silently dropped. Pass the parsed verdict
|
|
276
|
+
* through so a new field on the schema is included by default.
|
|
277
|
+
*/
|
|
278
|
+
export async function readBack(
|
|
279
|
+
client: FacetClient,
|
|
280
|
+
options: ReadBackOptions,
|
|
281
|
+
): Promise<ReadBackResult> {
|
|
282
|
+
const res = await client.sendCommand({
|
|
283
|
+
command: "readBack",
|
|
284
|
+
requestId: generateRequestId(),
|
|
285
|
+
artifactId: options.artifactId,
|
|
286
|
+
revisionSha: options.revisionSha,
|
|
287
|
+
tier: options.tier,
|
|
288
|
+
});
|
|
289
|
+
if (!res.ok) {
|
|
290
|
+
throw FacetError.from(res.error);
|
|
291
|
+
}
|
|
292
|
+
const parsed = CommandResultSchema.parse(res.data);
|
|
293
|
+
if (parsed.command !== "readBack") {
|
|
294
|
+
throw new FacetError("invalid_envelope", `expected readBack result, got ${parsed.command}`);
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
renderer: parsed.renderer,
|
|
298
|
+
verdict: parsed.verdict,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* The canonical read-back result the client returns. The `verdict`
|
|
304
|
+
* is the canonical Verdict shape (already schema-validated by the
|
|
305
|
+
* envelope parse), and `renderer` is the request-level binding
|
|
306
|
+
* that accompanies it. Anything else lives on the verdict.
|
|
307
|
+
*/
|
|
308
|
+
export type ReadBackResult = {
|
|
309
|
+
readonly renderer: Renderer;
|
|
310
|
+
readonly verdict: import("../shared/contracts/validation").Verdict;
|
|
311
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `facet create` — register a new artifact.
|
|
3
|
+
*
|
|
4
|
+
* The CLI maps the public verb (`create`) to the wire command
|
|
5
|
+
* (`create`) and builds a strict `CreateRequest` from the parsed
|
|
6
|
+
* argv. Required flags: `--project-id`, `--slug`, `--title`. The
|
|
7
|
+
* request id is minted here so the response envelope carries the
|
|
8
|
+
* CLI-side correlation id (the service echoes the request id back
|
|
9
|
+
* in its own envelope).
|
|
10
|
+
*
|
|
11
|
+
* Input-validation errors throw `FacetError("invalid_request", ...)`
|
|
12
|
+
* so the envelope preserves the typed `invalid_request` code (the
|
|
13
|
+
* house pattern for argv-shape failures) — the main catch recognises
|
|
14
|
+
* a pre-existing FacetError and passes `toBody()` through unchanged.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { generateRequestId } from "../../shared/util/time";
|
|
18
|
+
import { FacetError } from "../../shared/errors/facet-error";
|
|
19
|
+
import type { CreateRequest } from "../../shared/contracts/commands/requests";
|
|
20
|
+
|
|
21
|
+
export function buildCreateRequest(
|
|
22
|
+
args: Readonly<Record<string, string | boolean>>,
|
|
23
|
+
): CreateRequest {
|
|
24
|
+
const projectId = args["project-id"];
|
|
25
|
+
const slug = args["slug"];
|
|
26
|
+
const title = args["title"];
|
|
27
|
+
if (typeof projectId !== "string" || projectId.length === 0) {
|
|
28
|
+
throw new FacetError("invalid_request", "--project-id is required for create", {
|
|
29
|
+
retryable: false,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (typeof slug !== "string" || slug.length === 0) {
|
|
33
|
+
throw new FacetError("invalid_request", "--slug is required for create", {
|
|
34
|
+
retryable: false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (typeof title !== "string" || title.length === 0) {
|
|
38
|
+
throw new FacetError("invalid_request", "--title is required for create", {
|
|
39
|
+
retryable: false,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
command: "create",
|
|
44
|
+
requestId: generateRequestId(),
|
|
45
|
+
projectId,
|
|
46
|
+
slug,
|
|
47
|
+
title,
|
|
48
|
+
};
|
|
49
|
+
}
|