Skip to main content

Usage

facet install
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.

What it does

  1. Validate the project. facets.json must exist; at least one adapter must be installed (the picker auto-launches on a TTY if none are).
  2. Acquire an install lock at $FACET_DIR/locks/<basename>-<hash>.lock so two installs can’t race. The lock lives under the facet directory tree (not in your project root), keyed by sha256(realpath(projectRoot)) so each project gets its own slot.
  3. Load the existing lockfile, or use an empty skeleton if facets.lock is absent — facet install bootstraps the lockfile on first run, the same way bun install creates bun.lock.
  4. 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.json or 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.
  5. Drift removal. Any facet in the prior lockfile but no longer in facets.json has its assets cleaned up.
  6. 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.
  7. 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:
OutcomeMeaning
installedFacet was not in the previous lockfile.
updatedFacet 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).
repairedSame lockfile entry, but at least one adapter file was missing or had drifted from the lockfile content. Restored.
unchangedSame lockfile entry, every asset already in its desired state. Nothing was written.
removedFacet was in the lockfile but is no longer declared in facets.json. Assets cleaned up.
If you delete a materialized asset by hand and re-run 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.
SourceVerification
Registry (cache hit) or
Registry (download)Transport hash check → three-check protocol (metadata vs. archive vs. computed)
GitComputed content vs. lockfile integrity (tag-move defense)
LocalTrust-by-path (normal mode); lockfile-verified (frozen mode)
Any mismatch is a hard security error: install aborts before any asset is written.
Git sources are pinned by commit: the lockfile records the resolved commit SHA (not the requested ref, which stays in 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 declares servers: (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 declares facets: [...] (cherry-picking from other facets) is hard-rejected during install. Composition is open-beta scope.

Flags

FlagDescription
--verboseShow detailed step output on stderr.
--frozen-lockfileTreat 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.
Use this in CI to guarantee facets.json and facets.lock are in agreement. Mirrors --frozen-lockfile from npm and bun.
Before installing, frozen mode verifies bidirectional consistency. It fails — changing nothing — if any of these hold:

Pre-flight checks

  • No facets.lock exists
  • 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
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

CodeMeaning
0Install succeeded (including no-op when nothing has changed).
1Install 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 to facets.json and installs it in one step.
  • facet adapter install — install adapters that facet install materializes facets into.