Usage
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.
What it does
- Validate the project.
facets.jsonmust exist; at least one adapter must be installed (the picker auto-launches on a TTY if none are). - Acquire an install lock at
$FACET_DIR/locks/<basename>-<hash>.lockso two installs can’t race. The lock lives under the facet directory tree (not in your project root), keyed bysha256(realpath(projectRoot))so each project gets its own slot. - Load the existing lockfile, or use an empty skeleton if
facets.lockis absent —facet installbootstraps the lockfile on first run, the same waybun installcreatesbun.lock. - For each facet in
facets.json:- If the lockfile pins a version that satisfies the manifest specifier, that version is honored; ranges in the manifest are not re-resolved.
- If the locked version no longer satisfies the manifest specifier — because you edited
facets.jsonor pulled a teammate’s change — the entry is stale: the manifest specifier is re-resolved and the lockfile entry is replaced. If the new specifier resolves to nothing (e.g. a version that doesn’t exist), install fails and the project is left unchanged. - If the lockfile has no entry yet (newly-added or freshly-bootstrapped), the manifest specifier is resolved fresh.
- Fetch (from cache, or via git clone / registry / local path), verify integrity, build, and materialize the assets into every adapter.
- Drift removal. Any facet in the prior lockfile but no longer in
facets.jsonhas its assets cleaned up. - Skip-if-identical. Each asset’s on-disk content + metadata is compared to what we would write; identical assets are skipped without a journal entry.
- Write the new lockfile.
Lockfile semantics
facets.json is the source of truth; facets.lock records the resolved state so that an unchanged manifest installs reproducibly across machines. A pinned entry in facets.lock is honored as long as it satisfies its manifest specifier — a wildcard like 1.* keeps using the locked 1.2.3 and won’t drift to a newer 1.2.4.
When the manifest and lockfile disagree — you bumped an exact version, widened a wildcard the lock no longer falls within, or pulled a manifest change — the lockfile entry is stale. facet install re-resolves the manifest specifier and updates the lockfile to match. If the requested version doesn’t exist in the registry, install fails rather than silently keeping the old one.
To change the locked version of a facet, you can also run facet add <facet>@<new-version> — that updates both the manifest and the lockfile in one step. (A dedicated facet update command is on the roadmap.)
To enforce that the lockfile is already in sync — never re-resolving — use --frozen-lockfile.
Outcomes
The summary line classifies each facet by what happened on disk:| Outcome | Meaning |
|---|---|
installed | Facet was not in the previous lockfile. |
updated | Facet was in the lockfile at a different version — including when a stale entry was re-resolved to match the manifest. Summary shows (was X → Y). |
repaired | Same lockfile entry, but at least one adapter file was missing or had drifted from the lockfile content. Restored. |
unchanged | Same lockfile entry, every asset already in its desired state. Nothing was written. |
removed | Facet was in the lockfile but is no longer declared in facets.json. Assets cleaned up. |
facet install, the affected facet shows up as repaired.
Integrity protocol
Every facet is verified before any asset is written. See the Integrity Model for the full hash contract.| Source | Verification |
|---|---|
| Registry (cache hit) | → or |
| Registry (download) | Transport hash check → three-check protocol (metadata vs. archive vs. computed) |
| Git | Computed content vs. lockfile integrity (tag-move defense) |
| Local | Trust-by-path (normal mode); lockfile-verified (frozen mode) |
facets.json). A git clone that cannot be resolved to a commit fails the install rather than recording an unreproducible entry.
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. A cache hit is never trusted at face value: the content is re-hashed against its integrity sidecar on every materialization. Tampered content is evicted and re-fetched automatically.
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 declaresservers: (MCP server dependencies) emits a warning during install — the server names are listed but not materialized. Server materialization is open-beta scope.
Composition
A facet that declaresfacets: [...] (cherry-picking from other facets) is hard-rejected during install. Composition is open-beta scope.
Flags
| Flag | Description |
|---|---|
--verbose | Show detailed step output on stderr. |
--frozen-lockfile | Treat the lockfile as the source of truth; fail on any manifest/lockfile drift. See below. |
Frozen lockfile
facet install --frozen-lockfile makes the lockfile authoritative. Install reproduces exactly what is locked — no re-resolution, no lockfile writes.
Before installing, frozen mode verifies bidirectional consistency. It fails — changing nothing — if any of these hold:
Pre-flight checks
Pre-flight checks
- No
facets.lockexists - A manifest facet has no lockfile entry
- A locked version no longer satisfies its manifest specifier
- The lockfile pins a facet the manifest no longer declares
- A git/local source string changed since the entry was locked
Content verification
Content verification
Every facet — including local sources — must reproduce its locked integrity. Editing a local facet’s files fails a frozen install instead of silently overwriting.
Exit codes
| Code | Meaning |
|---|---|
0 | Install succeeded (including no-op when nothing has changed). |
1 | Install failed. The view renders the structured failure inline; stderr carries an install failed code=... line for log-grepping. |
See also
facet add— adds a new facet tofacets.jsonand installs it in one step.facet adapter install— install adapters thatfacet installmaterializes facets into.