@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,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI argv parser.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the verb set + per-verb flag surface.
|
|
5
|
+
* The parser is intentionally a tiny recursive-descent walker — the
|
|
6
|
+
* CLI surface is small (10 verbs, ~25 flags) and a hand-rolled parser
|
|
7
|
+
* is easier to audit than a library. The parser NEVER throws for
|
|
8
|
+
* bad input; it returns a discriminated result so the caller can
|
|
9
|
+
* produce a typed usage-error envelope.
|
|
10
|
+
*
|
|
11
|
+
* Help text generation lives here too (`renderHelp`); the same code
|
|
12
|
+
* path is used by the test that asserts the help output contains the
|
|
13
|
+
* full verb list, so the help surface can never silently shrink.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
IMPLEMENTED_COMMANDS,
|
|
18
|
+
RESERVED_COMMANDS,
|
|
19
|
+
type CommandName,
|
|
20
|
+
} from "../shared/contracts/commands/names";
|
|
21
|
+
import { RENDERERS } from "../shared/contracts/renderers";
|
|
22
|
+
import { TSX_EXECUTION_MODES } from "../shared/tsx/execution";
|
|
23
|
+
import { EXIT_CODES } from "./output";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parsed CLI invocation. `verb === null` for the meta-commands
|
|
27
|
+
* (`--help`, `--version`); `verb !== null` for every v1 verb.
|
|
28
|
+
*/
|
|
29
|
+
export type ParsedCommand =
|
|
30
|
+
| { readonly kind: "help"; readonly format: "text" | "json"; readonly verb?: CliVerb }
|
|
31
|
+
| { readonly kind: "version"; readonly format: "text" | "json" }
|
|
32
|
+
| {
|
|
33
|
+
readonly kind: "verb";
|
|
34
|
+
readonly verb: CliVerb;
|
|
35
|
+
readonly args: Readonly<Record<string, string | boolean>>;
|
|
36
|
+
readonly format: "text" | "json";
|
|
37
|
+
readonly jsonFlag: boolean;
|
|
38
|
+
}
|
|
39
|
+
| {
|
|
40
|
+
readonly kind: "usage";
|
|
41
|
+
readonly message: string;
|
|
42
|
+
readonly details?: Readonly<Record<string, string>>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type CliVerb = CommandName | "doctor";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Per-verb flag surface. Each entry is the list of accepted flags
|
|
49
|
+
* (`--flag` or `--flag <value>`) for that verb. The parser rejects
|
|
50
|
+
* unknown flags with a typed usage error.
|
|
51
|
+
*/
|
|
52
|
+
interface FlagDefinition {
|
|
53
|
+
readonly flag: string;
|
|
54
|
+
readonly takesValue: boolean;
|
|
55
|
+
readonly values?: readonly string[];
|
|
56
|
+
readonly required?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const VERB_FLAGS: Readonly<Record<CommandName, readonly FlagDefinition[]>> = {
|
|
60
|
+
create: [
|
|
61
|
+
{ flag: "--project-id", takesValue: true, required: true },
|
|
62
|
+
{ flag: "--slug", takesValue: true, required: true },
|
|
63
|
+
{ flag: "--title", takesValue: true, required: true },
|
|
64
|
+
],
|
|
65
|
+
publish: [
|
|
66
|
+
{ flag: "--artifact-id", takesValue: true, required: true },
|
|
67
|
+
{ flag: "--type", takesValue: true, required: true },
|
|
68
|
+
{ flag: "--renderer", takesValue: true, values: [...RENDERERS] },
|
|
69
|
+
{ flag: "--execution", takesValue: true, values: [...TSX_EXECUTION_MODES] },
|
|
70
|
+
{ flag: "--file", takesValue: true },
|
|
71
|
+
{ flag: "--watch", takesValue: false },
|
|
72
|
+
{ flag: "--note", takesValue: true },
|
|
73
|
+
{ flag: "--parent-revision-id", takesValue: true },
|
|
74
|
+
],
|
|
75
|
+
list: [
|
|
76
|
+
{ flag: "--project-id", takesValue: true, required: true },
|
|
77
|
+
{ flag: "--slug-prefix", takesValue: true },
|
|
78
|
+
{ flag: "--limit", takesValue: true },
|
|
79
|
+
],
|
|
80
|
+
readBack: [
|
|
81
|
+
{ flag: "--artifact-id", takesValue: true, required: true },
|
|
82
|
+
{ flag: "--revision-sha", takesValue: true },
|
|
83
|
+
{ flag: "--tier", takesValue: true },
|
|
84
|
+
],
|
|
85
|
+
status: [
|
|
86
|
+
{ flag: "--artifact-id", takesValue: true },
|
|
87
|
+
{ flag: "--start", takesValue: false },
|
|
88
|
+
],
|
|
89
|
+
open: [
|
|
90
|
+
{ flag: "--artifact-id", takesValue: true, required: true },
|
|
91
|
+
{ flag: "--revision-sha", takesValue: true },
|
|
92
|
+
{ flag: "--no-launch", takesValue: false },
|
|
93
|
+
],
|
|
94
|
+
promote: [
|
|
95
|
+
{ flag: "--artifact-id", takesValue: true },
|
|
96
|
+
{ flag: "--revision-id", takesValue: true, required: true },
|
|
97
|
+
{ flag: "--name", takesValue: true, required: true },
|
|
98
|
+
{ flag: "--description", takesValue: true },
|
|
99
|
+
{ flag: "--promoted-by", takesValue: true, required: true },
|
|
100
|
+
],
|
|
101
|
+
instantiate: [
|
|
102
|
+
{ flag: "--name", takesValue: true, required: true },
|
|
103
|
+
{ flag: "--new-slug", takesValue: true, required: true },
|
|
104
|
+
{ flag: "--project-id", takesValue: true },
|
|
105
|
+
],
|
|
106
|
+
pin: [
|
|
107
|
+
{ flag: "--revision-id", takesValue: true, required: true },
|
|
108
|
+
{ flag: "--pinned", takesValue: true, required: true },
|
|
109
|
+
],
|
|
110
|
+
export: [
|
|
111
|
+
{ flag: "--revision", takesValue: true },
|
|
112
|
+
{ flag: "--format", takesValue: true, values: ["source", "render"] },
|
|
113
|
+
{ flag: "--out", takesValue: true },
|
|
114
|
+
{ flag: "--force", takesValue: false },
|
|
115
|
+
{ flag: "--include-bytes", takesValue: false },
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/** Map CLI verb names to wire `command` names. Only `read-back` differs. */
|
|
120
|
+
const VERB_TO_COMMAND: Readonly<Record<string, CommandName>> = {
|
|
121
|
+
create: "create",
|
|
122
|
+
publish: "publish",
|
|
123
|
+
list: "list",
|
|
124
|
+
"read-back": "readBack",
|
|
125
|
+
status: "status",
|
|
126
|
+
open: "open",
|
|
127
|
+
promote: "promote",
|
|
128
|
+
instantiate: "instantiate",
|
|
129
|
+
pin: "pin",
|
|
130
|
+
export: "export",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const COMMAND_TO_VERB: Readonly<Record<CommandName, string>> = {
|
|
134
|
+
create: "create",
|
|
135
|
+
publish: "publish",
|
|
136
|
+
list: "list",
|
|
137
|
+
readBack: "read-back",
|
|
138
|
+
status: "status",
|
|
139
|
+
open: "open",
|
|
140
|
+
promote: "promote",
|
|
141
|
+
instantiate: "instantiate",
|
|
142
|
+
pin: "pin",
|
|
143
|
+
export: "export",
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Read `--format text|json` from the argv, defaulting to `text`.
|
|
148
|
+
* Only `--help` and `--version` honor the flag — verb calls always
|
|
149
|
+
* print a JSON envelope on stdout. The flag is recognized in any
|
|
150
|
+
* position so adapters can pass it either first or last.
|
|
151
|
+
*/
|
|
152
|
+
function readFormat(argv: readonly string[]): "text" | "json" | null {
|
|
153
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
154
|
+
const arg = argv[i];
|
|
155
|
+
if (arg === "--format") {
|
|
156
|
+
const value = argv[i + 1];
|
|
157
|
+
if (value === "text" || value === "json") return value;
|
|
158
|
+
return null;
|
|
159
|
+
} else if (arg === "--json") {
|
|
160
|
+
return "json";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return "text";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Strip format metadata for non-export verbs; export owns its format flag. */
|
|
167
|
+
function withoutFormatFlags(argv: readonly string[], stripVerbFormat: boolean): string[] {
|
|
168
|
+
const out: string[] = [];
|
|
169
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
170
|
+
const arg = argv[i];
|
|
171
|
+
if (arg === undefined) continue;
|
|
172
|
+
if (arg === "--json") continue;
|
|
173
|
+
if (stripVerbFormat && arg === "--format") {
|
|
174
|
+
i += 1;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
out.push(arg);
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function parseArgs(argv: readonly string[]): ParsedCommand {
|
|
183
|
+
if (argv.length === 0) return { kind: "help", format: "text" };
|
|
184
|
+
const firstRaw = argv[0];
|
|
185
|
+
const isMeta =
|
|
186
|
+
firstRaw === "--help" || firstRaw === "-h" || firstRaw === "--version" || firstRaw === "-V";
|
|
187
|
+
const jsonFlag = argv.includes("--json");
|
|
188
|
+
const localVerb = firstRaw === "doctor" ? ("doctor" as const) : undefined;
|
|
189
|
+
const firstCommand = typeof firstRaw === "string" ? VERB_TO_COMMAND[firstRaw] : undefined;
|
|
190
|
+
const watchRequested = firstCommand === "publish" && argv.includes("--watch");
|
|
191
|
+
const requestedFormat = isMeta || watchRequested ? readFormat(argv) : "text";
|
|
192
|
+
if (requestedFormat === null) {
|
|
193
|
+
return {
|
|
194
|
+
kind: "usage",
|
|
195
|
+
message: "Flag '--format' must be one of: text, json",
|
|
196
|
+
details: { flag: "--format", allowedValues: "text, json" },
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const format = requestedFormat;
|
|
200
|
+
// `--format` is scoped to `--help`/`--version` metadata and to export's
|
|
201
|
+
// source|render argument; every other verb used to have `--format` (and
|
|
202
|
+
// its value) silently dropped by `withoutFormatFlags` BEFORE flag
|
|
203
|
+
// validation ran, so an unsupported or mistyped `--format` on e.g.
|
|
204
|
+
// `publish` ran the verb as if the flag were absent instead of failing.
|
|
205
|
+
if (
|
|
206
|
+
!isMeta &&
|
|
207
|
+
firstCommand !== undefined &&
|
|
208
|
+
firstCommand !== "export" &&
|
|
209
|
+
argv.includes("--format") &&
|
|
210
|
+
!watchRequested
|
|
211
|
+
) {
|
|
212
|
+
return { kind: "usage", message: `Flag '--format' is not supported for '${firstRaw}'` };
|
|
213
|
+
}
|
|
214
|
+
const stripped = withoutFormatFlags(
|
|
215
|
+
argv,
|
|
216
|
+
isMeta || (firstCommand !== undefined && firstCommand !== "export"),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const first = stripped[0];
|
|
220
|
+
if (first === "--help" || first === "-h") return { kind: "help", format };
|
|
221
|
+
if (first === "--version" || first === "-V") return { kind: "version", format };
|
|
222
|
+
|
|
223
|
+
const verb = first;
|
|
224
|
+
if (verb === undefined) return { kind: "help", format };
|
|
225
|
+
const command = localVerb ?? VERB_TO_COMMAND[verb];
|
|
226
|
+
if (command === undefined) {
|
|
227
|
+
return { kind: "usage", message: `Unknown verb: '${verb}'` };
|
|
228
|
+
}
|
|
229
|
+
if (stripped.slice(1).includes("--help") || stripped.slice(1).includes("-h")) {
|
|
230
|
+
return { kind: "help", format, verb: command };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (command === "doctor") {
|
|
234
|
+
if (stripped.slice(1).some((flag) => flag !== "--json")) {
|
|
235
|
+
return { kind: "usage", message: "doctor accepts only --json" };
|
|
236
|
+
}
|
|
237
|
+
return { kind: "verb", verb: "doctor", args: {}, format, jsonFlag };
|
|
238
|
+
}
|
|
239
|
+
const flags = VERB_FLAGS[command];
|
|
240
|
+
const args: Record<string, string | boolean> = {};
|
|
241
|
+
for (let i = 1; i < stripped.length; i += 1) {
|
|
242
|
+
const flag = stripped[i];
|
|
243
|
+
if (flag === undefined) continue;
|
|
244
|
+
if (!flag.startsWith("--")) {
|
|
245
|
+
if (command !== "export" || args["artifact-id"] !== undefined) {
|
|
246
|
+
return { kind: "usage", message: `Unexpected positional argument: '${flag}'` };
|
|
247
|
+
}
|
|
248
|
+
args["artifact-id"] = flag;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const def = flags.find((f) => f.flag === flag);
|
|
252
|
+
if (def === undefined) {
|
|
253
|
+
return { kind: "usage", message: `Unknown flag for '${verb}': '${flag}'` };
|
|
254
|
+
}
|
|
255
|
+
if (def.takesValue) {
|
|
256
|
+
const value = stripped[i + 1];
|
|
257
|
+
if (value === undefined) {
|
|
258
|
+
return { kind: "usage", message: `Flag '${flag}' requires a value` };
|
|
259
|
+
}
|
|
260
|
+
if (def.values !== undefined && !def.values.includes(value)) {
|
|
261
|
+
return {
|
|
262
|
+
kind: "usage",
|
|
263
|
+
message: `Flag '${flag}' must be one of: ${def.values.join(", ")} (got '${value}')`,
|
|
264
|
+
details: { flag, allowedValues: def.values.join(", ") },
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
args[flag.slice(2)] = value;
|
|
268
|
+
i += 1;
|
|
269
|
+
} else {
|
|
270
|
+
args[flag.slice(2)] = true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const missing = flags
|
|
274
|
+
.filter((flag) => flag.required === true && args[flag.flag.slice(2)] === undefined)
|
|
275
|
+
.map((flag) => flag.flag);
|
|
276
|
+
if (command === "export" && args["artifact-id"] === undefined) {
|
|
277
|
+
missing.push("<artifactId>");
|
|
278
|
+
}
|
|
279
|
+
if (missing.length > 0) {
|
|
280
|
+
return {
|
|
281
|
+
kind: "usage",
|
|
282
|
+
message: `Missing required input(s) for ${verb}: ${missing.join(", ")}`,
|
|
283
|
+
details: { missing: missing.join(", ") },
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
if (command === "publish" && args.watch === true) {
|
|
287
|
+
if (typeof args.file !== "string" || args.file === "-") {
|
|
288
|
+
return {
|
|
289
|
+
kind: "usage",
|
|
290
|
+
message: "publish --watch requires --file <path> and cannot read stdin",
|
|
291
|
+
details: { reason: "watch_requires_file" },
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return { kind: "verb", verb: command, args, format, jsonFlag };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Render the --help text. Single source of truth so the test that
|
|
300
|
+
* asserts the verb list and the "stdout is JSON" line can never
|
|
301
|
+
* silently fail when a new verb is added.
|
|
302
|
+
*/
|
|
303
|
+
export function renderHelp(verb?: CliVerb): string {
|
|
304
|
+
if (verb === "doctor") {
|
|
305
|
+
return [
|
|
306
|
+
"Usage: facet doctor [--json]",
|
|
307
|
+
"",
|
|
308
|
+
"Read-only environment diagnostics for Bun, browser, isolation, storage, permissions, and service state.",
|
|
309
|
+
"",
|
|
310
|
+
"Exit codes:",
|
|
311
|
+
" 0 all probes pass",
|
|
312
|
+
" 1 one or more probes fail",
|
|
313
|
+
].join("\n");
|
|
314
|
+
}
|
|
315
|
+
if (verb !== undefined) {
|
|
316
|
+
const commandVerb = COMMAND_TO_VERB[verb];
|
|
317
|
+
const positional = verb === "export" ? " <artifactId>" : "";
|
|
318
|
+
const flags = VERB_FLAGS[verb]
|
|
319
|
+
.map((flag) => {
|
|
320
|
+
const value = flag.takesValue ? " <value>" : "";
|
|
321
|
+
return ` ${flag.flag}${value}${flag.required === true ? " required" : ""}`;
|
|
322
|
+
})
|
|
323
|
+
.join("\n");
|
|
324
|
+
return [
|
|
325
|
+
`Usage: facet ${commandVerb}${positional} [--flag value]...`,
|
|
326
|
+
"",
|
|
327
|
+
positional.length > 0 ? "Positionals:" : "",
|
|
328
|
+
...(positional.length > 0 ? [" <artifactId> artifact identifier to export"] : []),
|
|
329
|
+
...(positional.length > 0 ? [""] : []),
|
|
330
|
+
"Flags:",
|
|
331
|
+
flags,
|
|
332
|
+
"",
|
|
333
|
+
verb === "publish" && VERB_FLAGS[verb].some((flag) => flag.flag === "--watch")
|
|
334
|
+
? "stdout is a versioned JSON envelope for verb calls; publish --watch streams one envelope per attempt (or presenter lines on a TTY)."
|
|
335
|
+
: "stdout is always a versioned JSON envelope for verb calls.",
|
|
336
|
+
].join("\n");
|
|
337
|
+
}
|
|
338
|
+
const verbs = [...IMPLEMENTED_COMMANDS, ...RESERVED_COMMANDS]
|
|
339
|
+
.map((c) => COMMAND_TO_VERB[c])
|
|
340
|
+
.concat("doctor")
|
|
341
|
+
.join(", ");
|
|
342
|
+
const lines: string[] = [
|
|
343
|
+
"facet — Facet v1 command line interface",
|
|
344
|
+
"",
|
|
345
|
+
"Usage:",
|
|
346
|
+
" facet [--help | --version] [--format text|json]",
|
|
347
|
+
" facet <verb> [--flag value]...",
|
|
348
|
+
"",
|
|
349
|
+
`Verbs: ${verbs}`,
|
|
350
|
+
"",
|
|
351
|
+
"Source input:",
|
|
352
|
+
" facet publish --artifact-id <id> --type <t> [--renderer <svg|canvas>] --file <path> read bytes from a file",
|
|
353
|
+
" facet publish --artifact-id <id> --type <t> [--renderer <svg|canvas>] --file - read bytes from stdin",
|
|
354
|
+
" cat src.md | facet publish --artifact-id <id> --type <t> [--renderer <svg|canvas>] read bytes from stdin (piped)",
|
|
355
|
+
"",
|
|
356
|
+
"verb stdout is always a versioned JSON envelope; --format applies to meta commands and export only.",
|
|
357
|
+
"diagnostics are stderr.",
|
|
358
|
+
"",
|
|
359
|
+
"Exit codes:",
|
|
360
|
+
];
|
|
361
|
+
// Defined inline to avoid a circular import (output.ts does not
|
|
362
|
+
// import from parser.ts; this is the only direction). The numbers
|
|
363
|
+
// must stay in sync with src/cli/output.ts.
|
|
364
|
+
const codes: readonly { code: number; meaning: string }[] = [
|
|
365
|
+
{ code: 0, meaning: "ok (any well-formed envelope on stdout, incl. typed error)" },
|
|
366
|
+
{ code: EXIT_CODES.DOCTOR_FAILED, meaning: "doctor completed with one or more failed probes" },
|
|
367
|
+
{ code: 64, meaning: "usage error (pre-parse: unknown verb, bad flag)" },
|
|
368
|
+
{ code: 70, meaning: "internal (unhandled non-FacetError throw)" },
|
|
369
|
+
];
|
|
370
|
+
for (const c of codes) lines.push(` ${c.code} ${c.meaning}`);
|
|
371
|
+
lines.push("");
|
|
372
|
+
lines.push("Environment:");
|
|
373
|
+
lines.push(" FACET=off kill switch — exit 0, no service spawned");
|
|
374
|
+
lines.push(" FACET_HOME override the runtime home (db / lock / tokens live here)");
|
|
375
|
+
return lines.join("\n");
|
|
376
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI presenter — pretty output for interactive terminals.
|
|
3
|
+
*
|
|
4
|
+
* Reference implementation for the output design; wiring is one
|
|
5
|
+
* branch at the `printEnvelope` call site in main.ts:
|
|
6
|
+
*
|
|
7
|
+
* if (shouldPresentPretty({ isTTY: process.stdout.isTTY === true,
|
|
8
|
+
* jsonFlag: flags.json === true,
|
|
9
|
+
* env: process.env })) {
|
|
10
|
+
* for (const line of presentEnvelope(envelope, caps)) writer.write(line + "\n");
|
|
11
|
+
* } else {
|
|
12
|
+
* printEnvelope(writer, envelope); // unchanged contract path
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* Contract safety:
|
|
16
|
+
* - A pipe NEVER sees pretty output — adapters keep byte-identical
|
|
17
|
+
* envelopes. `--json` forces the envelope even on a TTY.
|
|
18
|
+
* - Exit codes are untouched (0 / 64 / 70).
|
|
19
|
+
* - Wire strings stay verbatim except the verdict word, which drops
|
|
20
|
+
* its `partial:` detail into a labeled row.
|
|
21
|
+
*
|
|
22
|
+
* Color policy: ANSI-16 only, Legion Works roles — green 32, red 31,
|
|
23
|
+
* yellow 33, cyan 36, blue 34, bright-black 90, inverse 7 (tampered
|
|
24
|
+
* only). NO_COLOR or a non-TTY zeroes every escape; the glyph column
|
|
25
|
+
* carries the verdict alone.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type { FacetEnvelope } from "../shared/contracts/envelope";
|
|
29
|
+
import type { RenderStatus, Verdict } from "../shared/contracts/validation";
|
|
30
|
+
|
|
31
|
+
export interface PresenterCaps {
|
|
32
|
+
readonly color: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PresentRouting {
|
|
36
|
+
readonly isTTY: boolean;
|
|
37
|
+
readonly jsonFlag: boolean;
|
|
38
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** TTY → pretty; pipe or --json → envelope. */
|
|
42
|
+
export function shouldPresentPretty(routing: PresentRouting): boolean {
|
|
43
|
+
return routing.isTTY && !routing.jsonFlag;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function presenterCaps(routing: PresentRouting): PresenterCaps {
|
|
47
|
+
return { color: routing.isTTY && routing.env["NO_COLOR"] === undefined };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** One glyph per RenderStatus — identical to verdict.css. */
|
|
51
|
+
export const VERDICT_GLYPH: Record<RenderStatus, string> = {
|
|
52
|
+
ok: "✓",
|
|
53
|
+
error: "✗",
|
|
54
|
+
"partial:layout_unverified": "◐",
|
|
55
|
+
"partial:opaque_content": "◐",
|
|
56
|
+
"partial:external_resources": "◐",
|
|
57
|
+
"partial:unstable": "◐",
|
|
58
|
+
tampered: "⊘",
|
|
59
|
+
timeout: "◌",
|
|
60
|
+
shim_only: "◇",
|
|
61
|
+
probe_only: "◈",
|
|
62
|
+
"insecure:unvalidated": "⊘",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const SGR = {
|
|
66
|
+
green: "32",
|
|
67
|
+
red: "31",
|
|
68
|
+
yellow: "33",
|
|
69
|
+
cyan: "36",
|
|
70
|
+
blue: "34",
|
|
71
|
+
dim: "90",
|
|
72
|
+
bold: "1",
|
|
73
|
+
redInverse: "31;7",
|
|
74
|
+
} as const;
|
|
75
|
+
|
|
76
|
+
type Tone = keyof typeof SGR;
|
|
77
|
+
|
|
78
|
+
const STATUS_TONE: Record<RenderStatus, Tone> = {
|
|
79
|
+
ok: "green",
|
|
80
|
+
error: "red",
|
|
81
|
+
"partial:layout_unverified": "yellow",
|
|
82
|
+
"partial:opaque_content": "yellow",
|
|
83
|
+
"partial:external_resources": "yellow",
|
|
84
|
+
"partial:unstable": "yellow",
|
|
85
|
+
tampered: "redInverse",
|
|
86
|
+
timeout: "dim",
|
|
87
|
+
shim_only: "dim",
|
|
88
|
+
probe_only: "dim",
|
|
89
|
+
"insecure:unvalidated": "redInverse",
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
function makePaint(caps: PresenterCaps) {
|
|
93
|
+
return (tone: Tone, text: string): string =>
|
|
94
|
+
caps.color ? `\u001b[${SGR[tone]}m${text}\u001b[0m` : text;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const sha8 = (sha: string): string => sha.slice(0, 8);
|
|
98
|
+
|
|
99
|
+
/** ` label<pad to 10>value` detail row. */
|
|
100
|
+
function row(label: string, value: string): string {
|
|
101
|
+
return ` ${label.padEnd(10, " ")}${value}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function verdictLines(verdict: Verdict, caps: PresenterCaps): string[] {
|
|
105
|
+
const paint = makePaint(caps);
|
|
106
|
+
const tone = STATUS_TONE[verdict.status];
|
|
107
|
+
const [word, detail] = verdict.status.split(":") as [string, string | undefined];
|
|
108
|
+
const head = [
|
|
109
|
+
paint(tone, `${VERDICT_GLYPH[verdict.status]} ${word}`),
|
|
110
|
+
...(detail === undefined ? [] : [paint("dim", detail)]),
|
|
111
|
+
paint("dim", `tier ${verdict.tier}`),
|
|
112
|
+
`${verdict.artifactId} ${paint("dim", "@")} ${paint("cyan", sha8(verdict.revisionSha))}`,
|
|
113
|
+
].join(paint("dim", " · "));
|
|
114
|
+
const o = verdict.observed;
|
|
115
|
+
const lines = [
|
|
116
|
+
...(verdict.insecure === undefined
|
|
117
|
+
? []
|
|
118
|
+
: [`INSECURE L${verdict.insecure.level} — ${verdict.insecure.reason}`]),
|
|
119
|
+
head,
|
|
120
|
+
row(
|
|
121
|
+
"observed",
|
|
122
|
+
`svg ${o.rendererRootSvgCount} · graphs ${o.graphCount} · nodes ${o.mermaidNodeCount} · errors ${o.errorCount}`,
|
|
123
|
+
),
|
|
124
|
+
];
|
|
125
|
+
const first = o.discriminativeErrors?.[0];
|
|
126
|
+
if (first !== undefined)
|
|
127
|
+
lines.push(row("detail", paint("red", `${first.code} — ${first.message}`)));
|
|
128
|
+
const screenshot = (verdict as { screenshotPath?: string | null }).screenshotPath;
|
|
129
|
+
if (typeof screenshot === "string") lines.push(row("evidence", paint("blue", screenshot)));
|
|
130
|
+
return lines;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Envelope → pretty lines. Knows readBack / publish / status closely;
|
|
135
|
+
* every other command falls back to a one-line acknowledgement so a
|
|
136
|
+
* new verb never renders as nothing.
|
|
137
|
+
*/
|
|
138
|
+
export function presentEnvelope(envelope: FacetEnvelope<unknown>, caps: PresenterCaps): string[] {
|
|
139
|
+
const paint = makePaint(caps);
|
|
140
|
+
if (!envelope.ok) {
|
|
141
|
+
const err = envelope.error;
|
|
142
|
+
return [
|
|
143
|
+
`${paint("red", `✗ ${err.code}`)}${paint("dim", ` · retryable ${err.retryable ? "yes" : "no"}`)}`,
|
|
144
|
+
row("", err.message),
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
const data = envelope.data as Record<string, unknown>;
|
|
148
|
+
const command = typeof data?.["command"] === "string" ? (data["command"] as string) : null;
|
|
149
|
+
|
|
150
|
+
if (command === "readBack" && typeof data["verdict"] === "object" && data["verdict"] !== null) {
|
|
151
|
+
return verdictLines(data["verdict"] as Verdict, caps);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (command === "publish") {
|
|
155
|
+
const revision = data["revision"] as { sha256?: string } | undefined;
|
|
156
|
+
const lines = [
|
|
157
|
+
`${paint("cyan", "● published")}${
|
|
158
|
+
revision?.sha256 === undefined
|
|
159
|
+
? ""
|
|
160
|
+
: `${paint("dim", " · rev ")}${paint("cyan", sha8(revision.sha256))}`
|
|
161
|
+
}`,
|
|
162
|
+
];
|
|
163
|
+
if (revision?.sha256 !== undefined) {
|
|
164
|
+
lines.push(row("read-back", `facet read-back --revision-sha ${revision.sha256}`));
|
|
165
|
+
}
|
|
166
|
+
const tier1 = data["tier1Verdict"];
|
|
167
|
+
if (typeof tier1 === "object" && tier1 !== null) {
|
|
168
|
+
lines.push(...verdictLines(tier1 as Verdict, caps));
|
|
169
|
+
}
|
|
170
|
+
return lines;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (command === "status") {
|
|
174
|
+
const state = data["state"];
|
|
175
|
+
if (state === "dormant") {
|
|
176
|
+
return [
|
|
177
|
+
`${paint("dim", "◌ dormant")}${paint("dim", " · zero processes · zero ports — healthy")}`,
|
|
178
|
+
];
|
|
179
|
+
}
|
|
180
|
+
if (state === "active") {
|
|
181
|
+
const jobs = data["activeJobs"];
|
|
182
|
+
return [
|
|
183
|
+
`${paint("green", "● active")}${paint(
|
|
184
|
+
"dim",
|
|
185
|
+
typeof jobs === "number" ? ` · jobs ${jobs}` : "",
|
|
186
|
+
)}`,
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (command === "doctor") {
|
|
192
|
+
const probes = Array.isArray(data["probes"]) ? data["probes"] : [];
|
|
193
|
+
const lines: string[] = [];
|
|
194
|
+
for (const item of probes) {
|
|
195
|
+
if (typeof item !== "object" || item === null) continue;
|
|
196
|
+
const probe = item as {
|
|
197
|
+
name?: unknown;
|
|
198
|
+
status?: unknown;
|
|
199
|
+
summary?: unknown;
|
|
200
|
+
fixCommand?: unknown;
|
|
201
|
+
};
|
|
202
|
+
const passed = probe.status === "pass";
|
|
203
|
+
lines.push(
|
|
204
|
+
`${paint(passed ? "green" : "red", `${passed ? "✓" : "✗"} ${String(probe.name)}`)} · ${String(probe.summary)}`,
|
|
205
|
+
);
|
|
206
|
+
if (!passed && typeof probe.fixCommand === "string") lines.push(row("fix", probe.fixCommand));
|
|
207
|
+
}
|
|
208
|
+
return lines;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return [`${paint("green", "✓ ok")}${command === null ? "" : paint("dim", ` · ${command}`)}`];
|
|
212
|
+
}
|