Skip to main content

Usage

Reads facets.json, fetches and materializes every facet declared there, and writes a facets.lock recording the exact resolved versions and integrity hashes. Used after a fresh git clone, after pulling teammate changes that updated the manifest, or to reapply assets after manual edits to adapter directories. facet install does not accept positional arguments — to add a new facet, use facet add.

Flags

boolean
Show detailed step output on stderr.
boolean
Treat the lockfile as the source of truth; fail on any manifest/lockfile drift.Setting this flag makes the lockfile authoritative: no re-resolution, no lockfile writes, and every facet — including local sources — must reproduce its locked integrity. It also requires the lockfile’s recorded materialization intent to match facets.json exactly. The full preflight and behavior matrix are in the Frozen lockfile specification.Frozen mode never prompts, even in a fully interactive terminal. It reproduces recorded intent, so it must not collect a new decision. A name collision under --frozen-lockfile is reported and fails.A 0.2 lockfile has no field in which to record a materialization disposition. If facets.json declares any override against one, the frozen install fails rather than silently ignoring it — run one non-frozen facet install to migrate the lockfile.
Use this in CI to guarantee facets.json and facets.lock are in agreement. Mirrors --frozen-lockfile from npm and bun.

Exit codes

What it does

1

Validate the project

facets.json must exist, and at least one install-capable adapter must be installed. If none are, the picker launches when the terminal can prompt; otherwise the command exits pointing at facet adapter install. Installed-but-incompatible adapters fail here rather than falling through to the picker.
2

Run the install pipeline

Runs the install pipeline with an empty delta: acquire the install lock, resolve and verify every declared facet (honoring satisfying lockfile pins), compose one global plan checking for name collisions, then remove drift and materialize assets into every adapter, and finally write manifest + lockfile + receipt atomically.Deletion runs before any write, which is what lets an asset name move from one facet to another in a single install.
Every facet is verified, and the whole plan composed, before any asset is written. An integrity mismatch, a name collision, or a cancelled resolution all abort with the project byte-identical — not rolled back, but never touched. The full semantics — lockfile trust, staleness, the verification chain — live in the Commit specification and the Integrity Model.

Outcomes

The summary line classifies each facet by what happened on disk:
new
Facet was not in the previous lockfile.
state change
Facet was in the lockfile at a different version — including when a stale entry was re-resolved to match the manifest. The summary shows (was X → Y).Also reported when the version is unchanged but its materialization is not: aliasing or omitting an asset changes what is on disk and what the lockfile records, so it is an update rather than a no-op. It is not a repair either — nothing drifted, the project changed its mind.
self-heal
Same version and same materialization intent, but at least one adapter file was missing or had drifted from the lockfile content. Restored. A skill is repaired by replacing its whole bundle atomically, so a single drifted companion pulls the primary with it.
no-op
Same version, same materialization intent, every asset already in its desired state. Nothing was written — the on-disk bytes were compared and matched, which is enough to record the assets as tracked without rewriting them.
cleanup
Facet is no longer declared in facets.json, and this machine’s install receipt tracked it. Its ownership was reconciled: obsolete identities deleted, and any identity another declared facet now claims kept.
records only
Facet is no longer declared, and only the lockfile ever recorded it — you pulled a teammate’s facets.lock and never installed. The declaration is dropped from facets.json and facets.lock, but nothing on disk is deleted, because no receipt claim proves this machine wrote it. The summary names the facet and says the files were left in place.
The N removed count covers both: it counts declarations that went away. How many assets left disk is the separate asset count — zero, for an untracked removal.
If you delete a materialized asset by hand and re-run facet install, the affected facet shows up as repaired. Below the counts, the summary names every asset whose materialized name differs from its authored one — the one thing you cannot infer from the file tree:
Omitted assets are not counted as written.

Name collisions

Two facets cannot materialize an asset under the same name. Skills and commands share one namespace; agents have their own — see Namespaces. Collisions are detected globally, before anything is written, so the install stops with the project untouched. In an interactive terminal, the install pauses and opens a resolver. Each contested asset gets one of three outcomes: Move with ↑↓, pick with ←→, apply with Enter, and Esc to go back. Arrow keys only move the cursor — nothing is applied until you press Enter. Each claimant shows a status that pairs an icon with a word, so it stays readable without color: ✕ unresolved, ⚠ conflict (your own edit created this one), ✓ resolved. Confirmation unlocks only when every group is resolved. Your choices are written into facets.json as durable intent, so the next install runs without prompting:
facets.json
Esc or Ctrl-C cancels the whole install. Nothing was written, and re-running lets you make the choices again. Without a terminal — CI, piped output, or --frozen-lockfile — nothing is prompted. Every group and every claimant is printed to stderr with the exact facets.json location to edit and copy-pasteable alias and omit snippets. No winner is chosen and no alias is invented for you: the placeholder is literally choose-a-name. The report ends by stating that facets.json, facets.lock, the receipt, and your materialized assets were not changed.

Cache

Resolved facet content is stored at $FACET_DIR/cache/<name>@<version>/ (default ~/.facet/cache/) so subsequent installs of the same identity don’t hit the network. Cache hits are re-verified on every use — see cache self-audit. The cache root is part of the facet directory tree; set FACET_DIR to change it. See the environment variables reference.

Servers

A facet that declares servers: emits a warning during install — the server names are listed but not materialized. Server support is on the roadmap.

See also