> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentfacets.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> What's new in Agent Facets

<Update
  label="2026-07-27"
  description="Resolve name collisions between facets; manifest 0.1, lockfile 0.3, and asset aliasing"
  tags={["CLI", "Breaking", "New Feature"]}
  rss={{
title: "Asset aliasing, omission, and cross-facet collision resolution",
description: "Two facets can now publish an asset with the same name without one of them losing. facet add and facet install detect every cross-facet name collision before writing anything, and in a terminal they pause and let you keep, rename, or omit each contested asset. Your choices are saved into facets.json as a materialization block and replayed on every later install, so it happens once and your teammates get the same layout. Without a terminal -- CI, or piped output -- nothing is prompted: the install fails and prints every claimant with the exact facets.json edit to make, choosing no winner and inventing no name. Aliasing renames only the file on disk; archive paths, integrity hashes, and the lockfile keep the publisher's authored name, so an alias can never smuggle different content under a familiar name. Omitted assets are still resolved, verified, and recorded, they are simply not written. Breaking: facets.json now carries manifestVersion 0.1 and entries may be objects instead of strings, so any tool that reads it must handle both forms; facets.lock moves to 0.3 with a required materialization disposition on every asset. Both migrate automatically on the next successful install. A frozen install fails if the manifest declares overrides while facets.lock is still at 0.2, which has no field in which to record them -- run one normal install first. Commit is now three phases, Resolve-all then Compose then Apply, so nothing is written until the whole plan is known to be collision-free. Removing a facet is the exception: when your machine's install receipt already accounts for everything you are keeping, facet remove refines those lockfile entries in place instead of resolving them, so it still works with a cold cache and an unreachable registry. Breaking: only that receipt authorizes deletion now. facets.lock is shared state describing what should be installed, so it is never treated as proof that this machine wrote a file -- pull a teammate's lockfile onto a machine that never ran an install, remove the facet, and the declaration is dropped while the files stay exactly where they are. Run facet install first if you want the CLI to take ownership of them, and note that facet remove needs the network when a facet you are KEEPING is not covered by that receipt -- it materializes what stays before claiming it, and fails without deleting anything if the content is unreachable. Breaking: facet remove also decides what is declared under the project lock rather than from a read taken before it, so a request whose names all look undeclared still connects an adapter -- it opens the picker in a terminal and exits non-zero in CI instead of printing a no-op it never verified."
}}
>
  ## Two facets, one name

  Facets are published independently, so sooner or later two of them
  ship an asset with the same name. Until now the second one quietly
  overwrote the first. Now [`facet add`](/cli/add) and
  [`facet install`](/cli/install) detect every collision across the
  whole project **before writing anything**, and stop.

  In a terminal, the install pauses and hands you the decision:

  ```
  Installation is paused: two or more facets want the same name.
  1 group · 1 still to resolve · nothing has been written yet

    project skills and commands — "planning"
      ✕ unresolved   viper-plans  skill planning
      ✕ unresolved   team-tools   skill planning
  ```

  Give each contested asset one of three outcomes -- **Keep** it under
  the published name, **Alias** it to a name you choose, or **Omit** it
  entirely. Only one of them has to change; the names just have to
  differ from each other.

  What this means for you:

  * **Nothing is written until you decide.** A collision leaves
    `facets.json`, `facets.lock`, the receipt, and your materialized
    assets byte-identical. Cancelling with `Esc` ends the command
    without applying anything -- it reports the cancellation and exits
    non-zero, and your project is exactly as you left it.
  * **You decide once.** Your choices are saved into `facets.json`, so
    the next install -- and every teammate's, and CI's -- replays them
    without prompting.
  * **No tool picks a winner.** Not the CLI, not the registry, not the
    facet authors. A silent winner means a silently discarded asset.

  ## Alias or omit any asset

  The same mechanism works without a collision. A facet entry can carry
  a `materialization` block naming, per asset, what you want:

  ```json facets.json theme={"theme":{"light":"github-light","dark":"github-dark"}}
  "team-tools": {
    "source": "1.*",
    "materialization": {
      "skills": {
        "planning": { "kind": "aliased", "as": "team-planning" },
        "scratch": { "kind": "omitted" }
      }
    }
  }
  ```

  * **Aliasing renames the file, nothing else.** Archive paths,
    per-file integrity, and the lockfile all keep the publisher's
    **authored** name. That is what makes an alias safe: it can never
    be used to smuggle different content in under a familiar name.
  * **Omitted assets are still verified.** They are resolved, hashed,
    and recorded in the lockfile -- they are just never written to
    disk. Dropping them from the lockfile would make an omission
    indistinguishable from a facet that never published the asset.
  * **Skills and commands share one namespace**, agents have their
    own. A skill `deploy` and a command `deploy` collide; an agent
    `deploy` coexists with both.

  See [Materialization](/specification/materialization) for the full
  model.

  ## **Breaking:** `facets.json` entries may be objects

  `facets.json` now carries a `manifestVersion` (currently `0.1`), and
  a facet entry is **either** a source string **or** an object with
  `source` and `materialization`:

  ```json facets.json theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "manifestVersion": 0.1,
    "facets": {
      "cowsay": "1.*",
      "team-tools": { "source": "1.*", "materialization": { "skills": { "planning": { "kind": "aliased", "as": "team-planning" } } } }
    }
  }
  ```

  **If you have tooling that reads `facets.json`, it must handle both
  forms.** Code that assumes every value is a string will render an
  expanded entry as `[object Object]` -- or, worse, destroy a recorded
  alias when it rewrites the file.

  A manifest with no `manifestVersion` is still read as the legacy
  format and is migrated in place on the next successful install. There
  is no separate migration command and nothing to run.

  ## **Breaking:** lockfile `0.3` (and receipt `0.3`)

  Every asset entry in `facets.lock` now carries a required
  `materialization` disposition -- `authored`, `aliased`, or `omitted`.
  Version `0.2` remains readable and is migrated to `0.3` by any normal
  install. The withdrawn closed-alpha `1` is no longer read at all: if
  you still have one, delete `facets.lock` and run `facet install` to
  regenerate it. The number `1` is reserved for the eventual stable v1
  format.

  The machine-local install receipt moves to `0.3` alongside it, so it
  can record the disposition each asset was materialized under. It
  migrates itself on the next install and lives outside your repository,
  so there is nothing to commit and nothing to run.

  ```json facets.lock theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "scope": "project", "type": "skill", "name": "planning",
    "materialization": { "kind": "aliased", "as": "team-planning" },
    "files": [{ "path": "skills/planning/SKILL.md", "integrity": "sha256:…" }]
  }
  ```

  Note `name` and `files` keep the authored name even when aliased.
  Versions are matched exactly, never by ordering: a version number
  names a schema rather than a position in a sequence, so a reader
  recognizes the versions it supports and fails on anything else
  instead of comparing magnitudes.

  Each asset's `files` are also checked against the asset itself now,
  not just for path safety: an agent or command lists exactly its own
  primary file, and every path in a skill lives under that skill's
  directory. A lockfile that pointed an asset at an unrelated file no
  longer validates.

  **In CI:** a [`--frozen-lockfile`](/cli/install) install fails if
  `facets.json` declares any materialization override while
  `facets.lock` is still at `0.2`, which has no field in which to
  record it. Run one normal `facet install` locally and commit the
  migrated lockfile. Frozen mode also treats a changed alias as drift,
  exactly like a changed version, and never prompts.

  ## The install pipeline resolves everything before writing anything

  Commit used to be an interleaved loop -- resolve facet A, write it,
  then start facet B. Detecting that A and Z want the same name is
  impossible that way, because A is already on disk.

  It now runs in three phases: **Resolve-all**, **Compose**, then
  **Apply**. The first two are read-only, which is why a collision, an
  invalid alias, or a cancelled resolution leaves the project untouched
  rather than rolled back. Deletion also moved to the start of Apply,
  before any write, so an asset name can transfer from one facet to
  another in a single install without the new owner's file being
  deleted behind it.

  Removing a facet is the exception. When your machine's install
  receipt already accounts for everything you are keeping,
  [`facet remove`](/cli/remove) refines those lockfile entries in place
  instead of resolving them, so it still works with a cold cache and an
  unreachable registry.

  Two smaller consequences you may notice:

  * **Disposition changes report as `updated`.** Aliasing an asset at
    an unchanged version is a real change to what is on disk, not a
    no-op and not a repair.
  * **Stale choices are pruned and reported.** If you upgrade a facet
    and the publisher removed an asset you had aliased, the override is
    dropped on a successful install and the CLI tells you which one.

  ## **Breaking:** only your install receipt authorizes deletion

  `facets.lock` is shared, version-controlled state. It says what
  *should* be materialized -- not what your machine actually wrote --
  so it is no longer treated as proof of ownership. Deletion is driven
  by the machine-local install receipt alone.

  Two rules replace the old fallback:

  * **Your project's desired state authorizes writes.** An install
    still materializes every declared asset, overwriting an unmanaged
    file that happens to occupy the same name. (If what is already
    there matches byte for byte, it is adopted without a rewrite.)
    Either way the file is tracked from then on.
  * **The receipt authorizes deletions.** Files the CLI has no record
    of writing are never deleted.

  What you may notice:

  * **Pull a teammate's `facets.lock` onto a machine that never ran an
    install, then `facet remove` the facet:** `facets.json` and
    `facets.lock` drop it and the files stay exactly where they are.
    The summary says so instead of reporting a cleanup that did not
    happen, and those files are then yours to delete by hand.
  * **`facet remove` may need the network.** The offline guarantee is a
    property of *tracked* state, not of removal. If a facet you are
    **keeping** is not fully accounted for by your receipt, the removal
    runs the ordinary pipeline to materialize it before claiming it --
    which succeeds when the content is reachable and fails, deleting
    nothing, when it is not. Run `facet install` first to take
    ownership.
  * **An unusable receipt is reported.** Unreadable, or recorded
    against a different project: nothing on disk is tracked, so nothing
    is cleaned up, and the run records only what it materializes.

  ## **Breaking:** `facet remove` decides what is declared under the lock

  Whether a name is declared is now answered by the commit, holding the
  project lock, instead of by a read taken before it. A pre-lock read
  can be stale, and acting on one let a facet added by a concurrent
  process survive the removal that asked for it.

  So `facet remove ghost` in a project with no adapter opens the picker
  in a terminal and exits non-zero in CI, rather than printing a no-op
  summary it never verified. Names that really are absent under the
  lock are still ignored, and a removal whose names are all absent
  still succeeds. An unreadable `facets.json` is still reported as a
  manifest problem, before any adapter is discovered.

  ## Adapter authors

  The adapter contract is unchanged -- no rebuild, no version bump. But
  `request.name` has always been, and now visibly is, the **effective**
  name: the name the consuming project chose. Address files by
  `request.name` and never re-derive a path from a facet's manifest.
  See [Custom adapters](/guides/custom-adapters).
</Update>

<Update
  label="2026-07-23"
  description="Facets ship non-asset files; archive 0.2, per-file lockfiles, first-class README, and the 0.1 adapter contract"
  tags={["CLI", "Breaking", "New Feature"]}
  rss={{
title: "Supplementary files, archive 0.2, and first-class README authoring",
description: "Facets can now declare and ship non-asset files: skill companion files (references, scripts, templates beside SKILL.md) that install and remove atomically with their skill, and top-level archive-only files like README.md and LICENSE that ship in the archive but never materialize on disk. Declare them with exact paths in the manifest's top-level files array or a skill descriptor's files array. Builds emit the new 0.2 archive format with a complete per-entry hash map; lockfiles move to 0.2 with a per-file integrity record for every materialized file; install reports and repairs drift per file. facet create now writes and declares an editable README.md by default (skip with --no-readme), and facet edit has a dedicated README panel. Consumers still accept legacy 0.1 archives and legacy alpha lockfiles during the compatibility window; the two versions are dispatched by exact match. Asset names are single-segment Agent Skills names -- slash-namespaced names are no longer valid in new manifests -- and skills and commands now share one namespace. The adapter contract advances from positional 0.0 to the tagged 0.1 request/result shape: rebuild and reinstall any adapter that still declares 0.0. Rollout is consumer-first: protocol, then registry, then adapters, then the CLI."
}}
>
  ## Facets can ship non-asset files

  A facet is no longer just its assets. You can now declare **supplementary
  files** that travel inside the `.facet` archive and are integrity-protected
  like everything else:

  * **Skill companions** — files beside a `SKILL.md` (references, scripts,
    templates). They install and remove **atomically** with their skill, and
    files you add to a skill directory yourself are preserved on removal.
  * **Top-level archive-only files** — `README.md`, `LICENSE`, design notes.
    They ship in the archive but are **never** written to disk at install.

  Declare them with exact paths (no globs) in the manifest's top-level `files`
  array or a skill descriptor's own `files` array:

  ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "skills": {
      "code-review": {
        "description": "Code review guidelines",
        "files": ["references/style-guide.md"]
      }
    },
    "files": ["README.md", "LICENSE"]
  }
  ```

  See [Supplementary files](/specification/manifest#supplementary-files).

  ## First-class README authoring

  [`facet create`](/cli/authoring/create) now writes and declares an editable
  `README.md` by default (skip it with `--no-readme`), and
  [`facet edit`](/cli/authoring/edit) has a dedicated README panel for
  creating, adopting, editing, or removing both `README.md` and the
  extensionless `README`.

  ## **Breaking:** archive `0.2`, per-file lockfiles, and naming

  * **Archive format `0.2`.** Every build now emits `facetVersion: 0.2` with a
    complete per-entry hash map covering every file, asset or not. Consumers
    still accept legacy `0.1` archives during the compatibility window; the two
    are dispatched by **exact** version match, never numeric ordering. An
    archive whose version your CLI doesn't support fails with upgrade guidance —
    run [`facet self-update`](/cli/self-update).
  * **Lockfile `0.2`.** `facets.lock` records a per-file `{ path, integrity }`
    record for every materialized file inside each asset, and install
    reconciles them before writing, reporting and repairing drift by exact
    path. A verified legacy alpha lockfile migrates automatically on a normal
    install. See [Lockfile](/specification/lockfile).
  * **Asset names.** Skill, command, and agent names are now single-segment
    [Agent Skills names](/specification/manifest#asset-names) — slash-namespaced
    names like `viper-plans/planning` are no longer valid in new manifests (use
    `viper-planning`). Skills and commands now share one namespace and can't
    reuse a name.
  * **Primary files carry no front matter.** Asset metadata lives in the
    manifest; a `SKILL.md`, agent, or command file with YAML front matter is now
    a build error. Supplementary files still ship verbatim.

  ## **Breaking:** adapter API `0.0` → `0.1`

  The adapter contract advances from the positional single-file shape (`0.0`)
  to the tagged multi-file request/result contract (`0.1`) that carries skill
  companions. A `0.1` CLI treats a positional `0.0` adapter as unsupported and
  fails closed before any write. Rebuild against a `0.1` SDK and reinstall:

  ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
  facet adapter list                 # shows each adapter's declared API
  facet adapter install my-adapter   # reinstall the newest compatible release
  ```

  The archive `facetVersion`, the lockfile version, and the adapter API are
  three **independent** axes — a consumer classifies each separately. See
  [Custom adapters](/guides/custom-adapters).

  ## Rollout

  The change ships **consumer-first**: the protocol package publishes dual
  `0.1`/`0.2` verification, then the registry adopts it, then the adapter SDK
  and first-party adapters publish `0.1`, and finally the CLI release turns on
  `0.2` production. Existing `0.0` CLIs keep selecting compatible `0.0` adapters
  until you update.
</Update>

<Update
  label="2026-07-13"
  description="facet remove now prunes empty adapter directories"
  tags={["CLI", "Improvement"]}
  rss={{
title: "facet remove now prunes empty adapter directories",
description: "Removing a facet now cleans up empty parent directories left behind in each connected adapter. When facet remove (or facet rm) deletes the last asset inside a nested folder -- for example skills/<name>/SKILL.md under Claude Code or OpenCode -- the CLI walks up and removes empty parent directories up to (but not including) the adapter root. Uninstalls leave a tidy tree instead of hollow namespace folders. No flags to set; behavior is automatic."
}}
>
  ## `facet remove` prunes empty adapter directories

  Removing a facet now cleans up empty parent directories left behind
  in each connected adapter. When [`facet remove`](/cli/remove) (or
  `facet rm`) deletes the last asset inside a nested folder -- for
  example `skills/<name>/SKILL.md` under Claude Code or OpenCode -- the
  CLI walks up and removes empty parent directories, stopping at (but
  not including) the adapter's root.

  What this means for you:

  * **Tidier working tree.** Uninstalling a facet no longer leaves
    behind empty namespace folders that you'd otherwise have to delete
    by hand.
  * **No configuration.** The behavior is automatic for the
    Claude Code and OpenCode adapters -- no flags, no opt-in.
  * **Bounded to each adapter.** Pruning stops at the adapter's own
    root, so it can never walk into the rest of your project.

  Ships in `@agent-facets/adapter@0.26.0`,
  `@agent-facets/adapter-claude-code@0.7.0`, and
  `@agent-facets/adapter-opencode@0.8.0`. Run
  [`facet self-update`](/cli/self-update) to pick it up.
</Update>

<Update
  label="2026-07-12"
  description="Server-side search for facet search; asset names now follow the Agent Skills grammar"
  tags={["CLI", "Improvement", "Breaking"]}
  rss={{
title: "Server-side search for facet search; asset names now follow the Agent Skills grammar",
description: "facet search forwards your query term to the registry as the ?q= parameter and renders results exactly as the server returns them. The registry now owns relevance ranking, sorting, and paging -- so results reflect the same ordering you see in the web UI, and future improvements to registry search take effect immediately without a CLI upgrade. The old client-side case-insensitive substring filter has been removed. Separately: skill, command, and agent names in facet.json are now validated against the Agent Skills name grammar at build and install time. Each /-separated segment must be 1-64 characters of lowercase ASCII letters, digits, and hyphens, with no leading, trailing, or consecutive hyphens. Digit-start names like 2fa are now allowed; non-conforming names like MySkill or foo_bar are now rejected. Namespaced names like viper-plans/planning validate per segment. Error messages now cite the specific grammar rule violated. facet modify --update and --remove still accept legacy names so you can fix or remove assets created under the old, looser rules."
}}
>
  ## Server-side search for `facet search`

  `facet search` now forwards your query term to the registry as the
  `?q=` parameter on the facets endpoint and renders whatever the server
  returns, in order. The registry -- not the CLI -- owns relevance
  ranking, sorting, and paging.

  ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
  facet search plan        # sends ?q=plan; server ranks and pages the results
  facet search             # no term; returns the first page unfiltered
  ```

  What this means for you:

  * **Results match the web UI.** Ordering is consistent with what
    [agentfacets.io](https://agentfacets.io) shows, because both surfaces
    read the same server-ranked list.
  * **Better matching, no upgrade required.** Improvements to registry
    search (typo tolerance, ranking, new sort orders) take effect
    immediately -- the CLI just displays what the server sends.
  * **Old behavior removed.** The client-side case-insensitive name
    substring filter is gone. If a term returns no matches on the
    server, the CLI now shows an empty result instead of falling back to
    local filtering.

  See the [`facet search`](/cli/search) reference for usage.

  ## Asset names now follow the Agent Skills grammar

  **Breaking:** skill, command, and agent names declared in `facet.json`
  are now validated against the [Agent Skills name
  grammar](https://agentskills.io/specification#name-field) at both
  build and install time. Each `/`-separated segment must be:

  * 1–64 characters long
  * lowercase ASCII letters, digits, and hyphens only
  * no leading, trailing, or consecutive hyphens

  Digit-start names like `2fa` are now accepted; previously they were
  rejected. Non-conforming names like `MySkill` or `foo_bar` are now
  rejected  -- previously they slipped past the looser kebab-case check.
  Namespaced names like `viper-plans/planning` validate per segment.

  ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # Accepted (was rejected before):
  facet modify skill 2fa --add

  # Rejected (was accepted before):
  facet modify skill MySkill --add
  facet modify skill foo_bar --add
  facet modify skill really-long-name-that-goes-well-past-sixty-four-characters-in-length --add
  ```

  Error messages now cite the specific rule that failed (e.g. `"must be
      at most 64 characters"`) instead of a generic kebab-case hint.

  If you have a facet with a legacy non-kebab asset name, `facet modify   --update` and `facet modify --remove` still accept the old name so you
  can rename or drop the asset without a chicken-and-egg problem. See
  the [manifest schema](/specification/manifest) and [`facet
      modify`](/cli/authoring/modify) reference.
</Update>

<Update
  label="2026-07-09"
  description="Agent-friendly CLI: instructions, modify, headless create, build --verify; TypeScript 7 support; trailing newlines on generated JSON"
  tags={["CLI", "New Feature", "Improvement", "Fix"]}
  rss={{
title: "Agent-friendly CLI, TypeScript 7 support, and trailing newlines on generated JSON",
description: "The facet CLI is now built for AI agents to drive non-interactively: new facet instructions [topic] prints agent-oriented usage guidance (the manifest topic emits the facet.json JSON Schema generated live from the schema definition); new facet modify makes one scriptable, flag-driven edit to a facet; facet create gains headless flags; facet build gains --verify and --json. Also: the public @agent-facets/* packages now declare their typescript peerDependency as ^5 || ^6 || ^7, so they install cleanly on TypeScript 7 projects. And every JSON file the CLI writes -- facets.lock, install receipts, build manifests, scaffolded facet.json, and cache metadata -- now ends with exactly one trailing newline and uses consistent 2-space indentation."
}}
>
  ## TypeScript 7 support

  The public `@agent-facets/*` packages now declare their `typescript`
  peer dependency as `^5 || ^6 || ^7`, so they install cleanly on
  projects that have upgraded to TypeScript 7. Consumers still on
  TypeScript 5 or 6 are unaffected -- no action needed.

  This applies to:

  * `@agent-facets/protocol`
  * `@agent-facets/adapter`
  * `@agent-facets/brand`
  * `@agent-facets/adapter-claude-code`
  * `@agent-facets/adapter-codex`
  * `@agent-facets/adapter-opencode`

  ## Trailing newlines on generated JSON

  Every JSON file the CLI writes now ends with exactly one trailing
  newline and uses consistent 2-space indentation:

  * `facets.lock`
  * install receipts and parallel-install lock payloads
  * build manifests inside the `.facet` archive
  * scaffolded `facet.json` (from `facet create`)
  * cache integrity sidecars

  Previously these files ended without a trailing newline, so editors
  configured to auto-append one produced a one-character diff every
  time you opened a CLI-generated file. That's fixed -- open a file,
  save it, no diff.

  `facets.json` continues to be written through the comment-preserving
  serializer (so your hand-edited comments survive) and now upholds the
  same trailing-newline invariant.

  ## The CLI is now agent-friendly

  Every interactive authoring flow now has a non-interactive, scriptable
  path, and the CLI can describe itself to an AI agent on demand. Three
  themes: **built-in instructions**, **headless authoring**, and
  **machine-readable output**.

  ## New command: `facet instructions`

  `facet instructions [topic]` prints agent-oriented usage guidance — how to
  author and use facets, written for an autonomous agent to read before it
  acts. It is workflow guidance, not per-flag help.

  ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
  facet instructions            # overview (default): the two workflows + index
  facet instructions manifest   # facet.json structure + generated JSON Schema
  facet instructions authoring  # scaffold → modify → verify, incl. adapter config
  facet instructions usage      # add/update/remove facets; install adapter tooling
  ```

  The `manifest` topic appends the **facet.json JSON Schema generated live
  from the schema definition** — a single source of truth, never a
  hand-maintained copy. See [`facet instructions`](/cli/instructions).

  ## New command: `facet modify`

  `facet modify` is the scriptable counterpart to the interactive
  [`facet edit`](/cli/authoring/edit) wizard: one headless, flag-driven edit
  to a facet.

  ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # Add an asset (manifest entry + scaffolded file), with a description
  # and adapter config, in one call:
  facet modify skill greet --add --description "Greets the user" \
    --adapter-claude-code '{"permission":{"bash":"ask"}}'

  # Change a description, rename (moves the file too), or remove:
  facet modify agent helper --description "Reviews code"
  facet modify command run --rename start
  facet modify skill greet --remove

  # Set facet-level metadata:
  facet modify facet --version 1.0.0 --private
  ```

  Per-asset adapter config uses a name-embedded flag whose value is a JSON
  object: `--adapter-<name> '<json>'` sets a block (replacing it wholesale),
  and `--remove-adapter-<name>` deletes one. Add `--json` for a structured
  change summary. Exactly one lifecycle action (`--add`, `--remove`, or
  `--rename`) is allowed per call; illegal flag combinations are rejected up
  front. See [`facet modify`](/cli/authoring/modify).

  ## `facet create` gains headless flags

  Pass authoring flags to scaffold a facet without the interactive wizard:

  ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
  facet create ./my-facet \
    --name my-facet --description "What it does" --version 0.1.0 \
    --skill greet --agent helper --command run --json
  ```

  Repeat `--skill`/`--agent`/`--command` for multiple assets; `--private`
  marks the facet private, `--force` overwrites an existing `facet.json`
  (required in headless mode), and `--json` prints a structured result. See
  [`facet create`](/cli/authoring/create).

  ## `facet build --verify` and `--json`

  `facet build --verify` runs the full validation pipeline **without writing
  any output** — a no-op build that confirms `facet.json` and every asset are
  valid and buildable. `--json` emits a machine-readable result and works with
  or without `--verify`.

  ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
  facet build --verify          # validate only; nothing written to dist/
  facet build --verify --json   # machine-readable: { ok, errors, warnings, … }
  ```

  See [`facet build`](/cli/authoring/build).

  ## Fix: subcommand `--version` is no longer shadowed

  The global `--version` flag now applies only *before* the command name, so
  `facet modify facet --version 1.2.3` sets the facet's version instead of
  printing the CLI version.
</Update>

<Update
  label="2026-06-06"
  description="New facet remove command (aliased rm)"
  tags={["CLI", "New Feature"]}
  rss={{
title: "New command: facet remove",
description: "facet remove (aliased rm) takes one or more facets out of a project: it removes them from facets.json, deletes their assets from every connected adapter, and rewrites facets.lock without them, in a single command. It is the inverse of facet add and reuses the same install pipeline, so removal is transactional  -- any failure restores facets.json byte-for-byte. Removing multiple facets is all-or-nothing, and removing a facet that is not declared fails without changing anything."
}}
>
  ## New command: `facet remove`

  `facet remove <facet> [more facets...]` is the inverse of `facet add`: it
  takes facets back out of a project. In one command it removes the named
  facets from `facets.json`, deletes their assets from every connected
  adapter, and rewrites `facets.lock` without them.

  ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # Remove a single facet.
  facet remove viper-plans

  # rm is an alias.
  facet rm viper-plans

  # Remove several at once.
  facet remove viper-plans rezi
  ```

  Removal reuses the same install pipeline as `facet add`, so it is
  **transactional**: any failure restores `facets.json` byte-for-byte and
  leaves the project unchanged. Removing multiple facets is all-or-nothing  --
  if any name is not declared in `facets.json`, nothing is removed. Every
  facet you don't name is left untouched.

  See the [`facet remove`](/cli/remove) reference for details.
</Update>

<Update
  label="2026-06-05"
  description="Bearer-token auth for the registry; new login/whoami/logout commands; FACET_REGISTRY_API_KEY removed; install now re-resolves a stale lockfile; new --frozen-lockfile flag"
  tags={["CLI", "Breaking", "New Feature", "Fix"]}
  rss={{
title: "Registry auth moves to bearer tokens; new login/whoami/logout commands; install honors manifest edits",
description: "The facet CLI now authenticates to the registry with a personal access token sent as a bearer credential, replacing the old FACET_REGISTRY_API_KEY API key. FACET_REGISTRY_API_KEY has been removed with no shim. Provide a token via the FACET_TOKEN environment variable or by running the new `facet login` command, which verifies the token and saves it to ~/.facet/credentials. Two more new commands: `facet whoami` prints the signed-in identity, and `facet logout` clears the saved credential. `facet publish` now also accepts an optional directory argument. Registry errors are now rendered using the registry's own message and suggested fix. Bug fix: editing a facet's version in facets.json now takes effect  -- facet install re-resolves a lockfile entry that no longer satisfies the manifest, and fails if the requested version does not exist, instead of silently keeping the old version. New flag: facet install --frozen-lockfile treats the lockfile as the source of truth and fails on any manifest/lockfile drift, for reproducible CI installs."
}}
>
  ## Registry authentication is now bearer-token based

  **Breaking:** the `FACET_REGISTRY_API_KEY` environment variable has been
  **removed**  -- there is no fallback or deprecation shim. The CLI now sends an
  `Authorization: Bearer <token>` header, where the token is a <Tooltip headline="Personal access token (PAT)" tip={<span>A revocable credential you mint in the web UI and use to authenticate the CLI — via <code>facet login</code> or the <code>FACET_TOKEN</code> environment variable.</span>}>personal access
  token (PAT)</Tooltip> you mint in the web UI.

  Provide the token one of two ways:

  ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # 1. Environment variable (preferred for CI):
  export FACET_TOKEN=fct_pub_…
  facet publish

  # 2. Or sign in interactively  -- verifies the token and saves it to
  #    ~/.facet/credentials (mode 600):
  facet login
  ```

  When `FACET_TOKEN` is set it takes precedence over the saved file. Read-only
  commands like `facet search` and `facet add` send the token too when one is
  available (earning a higher rate-limit tier) and work anonymously otherwise.

  ## New commands: `login`, `whoami`, `logout`

  * **`facet login`**  -- guided sign-in. Paste a PAT; the CLI verifies it
    against the registry before saving it, so a typo or expired token fails
    fast instead of surfacing later at publish time. A browser sign-in option
    is shown as "coming soon".
  * **`facet whoami`**  -- prints the signed-in username, email, and tier, and
    notes when `FACET_TOKEN` is the active credential.
  * **`facet logout`**  -- removes the saved credentials file. It makes no
    server call; revoke PATs in the web UI. If `FACET_TOKEN` is still set, it
    tells you so.

  ## `facet publish` takes an optional directory

  `facet publish` now accepts a directory argument and defaults to the current
  directory, matching `facet build`, `facet edit`, and `facet create`:

  ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
  facet publish            # publish the facet in the current directory
  facet publish ./cowsay   # publish the facet in ./cowsay
  ```

  A first-time publish of a reserved or over-budget global facet may be queued
  for admin review  -- this is reported as a success, not an error.

  ## Registry errors render verbatim

  When the registry rejects a request, the CLI now shows the registry's own
  message and suggested fix rather than translating the error code through a
  local table. The registry is the single source of truth for what an error
  means  -- so error guidance stays accurate as the registry evolves, with no CLI
  release required.

  See the [Publish Flow](/specification/publish) spec for the full
  authentication model.

  ## Editing a version in `facets.json` now takes effect

  **Fix:** `facets.json` is the source of truth, but `facet install` was
  ignoring an edited version when the lockfile still pinned the old one. If you
  bumped a facet to a version that didn't exist, install "succeeded" and wrote a
  self-contradictory lockfile entry instead of failing.

  Now `facet install` compares each lockfile entry against its manifest
  specifier. A locked version that no longer satisfies the manifest is treated
  as stale and re-resolved:

  ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # facets.json edited: cowsay 0.1.1 → 0.1.2
  facet install          # fetches 0.1.2, updates the lockfile (was 0.1.1 → 0.1.2)

  # facets.json edited to a version that doesn't exist
  facet install          # fails: version not found; project left unchanged
  ```

  A wildcard the lock still satisfies (manifest `1.*`, lock `1.2.3`) is
  unaffected  -- it stays pinned and reproducible. See [resolution semantics](/specification/commit#resolve).

  ## New: `facet install --frozen-lockfile`

  For reproducible CI installs, `--frozen-lockfile` makes the lockfile the
  source of truth: install never re-resolves or writes the lockfile, and fails
  if the lockfile is missing, omits a manifest facet, or has drifted out of sync
  with `facets.json`.

  ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
  facet install --frozen-lockfile   # passes only if facets.json and facets.lock agree
  ```

  This mirrors `--frozen-lockfile` from `npm` and `bun`. See [frozen
  lockfile semantics](/specification/commit#frozen-lockfile).
</Update>

<Update
  label="2026-05-14"
  description="Consolidate everything under FACET_DIR; new FACET_BIN_OVERRIDE; lock moves out of project root"
  tags={["CLI", "Breaking"]}
  rss={{
title: "Consolidate everything under FACET_DIR and move the install lock out of the project root",
description: "One environment variable now controls everything the CLI writes to disk: FACET_DIR (default ~/.facet). The cache, installed adapters, install advisory locks, and the curl-installed binary all live under it. FACET_BIN_OVERRIDE replaces FACET_BIN_PATH as the launcher's binary override (and continues to refuse self-update when set, because overriding means you've taken control). The install advisory lock moves from <projectRoot>/.facets/.install.lock to $FACET_DIR/locks/<basename>-<hash>.lock  -- facet install no longer writes anything to your project root. Removed env vars (no aliases, hard rename): FACETS_CACHE_DIR, FACETS_ADAPTERS_DIR, FACET_CACHE_DIR, FACET_ADAPTERS_DIR, FACET_INSTALL_DIR, FACET_BIN_PATH. No automatic migration of existing ~/.facets/ data."
}}
>
  ## One directory, one override

  The facet CLI used to spread its state across four near-identical
  locations and five separate environment variables. State for the
  cache, installed adapters, the curl-installed binary, and parallel-
  install coordination all lived in different places with different
  overrides. This release collapses everything into a single root: one
  directory, one environment variable.

  ### The new shape

  Everything the CLI writes to disk now lives under `$FACET_DIR`:

  ```
  $FACET_DIR/
    ├── bin/        # curl-installed binary
    ├── cache/      # content-addressed cache for fetched facets
    ├── adapters/   # installed adapter bundles
    └── locks/      # install advisory locks (one file per project)
  ```

  The default for `$FACET_DIR` is `~/.facet/`. Setting `FACET_DIR` IS
  the override  -- there is no separate variable per subsystem. Curl
  installs put the binary at `$FACET_DIR/bin/facet`; `facet add` and
  `facet install` resolve cache and adapters relative to the same root.

  ### What changed

  | Before                                | After                                           |
  | ------------------------------------- | ----------------------------------------------- |
  | `FACETS_CACHE_DIR`                    | `FACET_DIR` (cache is `$FACET_DIR/cache`)       |
  | `FACETS_ADAPTERS_DIR`                 | `FACET_DIR` (adapters at `$FACET_DIR/adapters`) |
  | `FACET_INSTALL_DIR`                   | `FACET_DIR` (curl bin at `$FACET_DIR/bin`)      |
  | `FACET_BIN_PATH`                      | `FACET_BIN_OVERRIDE`                            |
  | `<projectRoot>/.facets/.install.lock` | `$FACET_DIR/locks/<basename>-<hash>.lock`       |
  | `~/.facets/cache/<name>@<version>/`   | `$FACET_DIR/cache/<name>@<version>/`            |
  | `~/.facets/adapters/<name>/`          | `$FACET_DIR/adapters/<name>/`                   |

  `FACET_CLI_REGISTRY` (npm registry URL override) and `FACET_VERSION`
  (used by `install.sh`) are unchanged.

  ### `FACET_BIN_OVERRIDE`, by name

  The launcher's binary override gets a new name that carries its own
  semantics: `FACET_BIN_OVERRIDE`. Setting it means you've taken control
  of which binary the launcher executes. `facet self-update` continues
  to refuse while it's set, because if you've overridden the binary
  path, self-update has no business writing over whatever you pointed
  it at. The refusal is coupled to the override on purpose.

  Unset `FACET_BIN_OVERRIDE` to re-enable self-update for a real install.

  ### Project root: clean

  The install advisory lock no longer touches your project root.
  Previously, `facet install` materialized `.facets/.install.lock` next
  to `facets.json`  -- a project-local directory that wasn't tracked
  anywhere and could be left behind on crashes. The lock now lives at
  `$FACET_DIR/locks/<basename>-<sha256(realpath)[:16]>.lock`, keyed by
  the project's canonical path so two checkouts of the same repo at
  different paths (git worktrees, Conductor workspaces) get distinct
  locks.

  No `.facet.lock` file, no `.facets/` directory, no untracked entry
  next to your `facets.json`. The project root stays as clean as
  `facets.json` itself.

  **Breaking:** No automatic migration. The new code reads `$FACET_DIR`
  only  -- existing cached payloads and adapters at `~/.facets/` are not
  detected, copied, or warned about. Old env vars (`FACETS_CACHE_DIR`,
  `FACETS_ADAPTERS_DIR`, `FACET_CACHE_DIR`, `FACET_ADAPTERS_DIR`,
  `FACET_INSTALL_DIR`, `FACET_BIN_PATH`) are silently ignored  -- anyone
  who had them set in shell rc files or CI configs must rename to
  `FACET_DIR` / `FACET_BIN_OVERRIDE` or the values stop taking effect.
  Existing `~/.facets/` data can be deleted at any time; the new code
  will rebuild cache and adapters on first use.

  See the [environment variables](/cli/env) reference and the
  [`facet install`](/cli/install) page for the updated paths.
</Update>

<Update
  label="2026-05-03"
  description="@agent-facets/core split into protocol (public, Node-native) and engine (private, Bun-native)"
  tags={["CLI", "Breaking", "Improvement"]}
  rss={{
title: "Package split: @agent-facets/core → @agent-facets/protocol + @agent-facets/engine",
description: "@agent-facets/core has been split into two packages. @agent-facets/protocol is the new public, Node-native package containing the facet artifact specification: schemas, validators, integrity verification, deterministic archive format, hash algorithm, and version-spec grammar. @agent-facets/engine is the Bun-native CLI machinery, now private to the monorepo. The legacy @agent-facets/core package is no longer published; existing pins to v0.9.1 continue to resolve. CLI behavior is unchanged."
}}
>
  ## Three layers, honestly named

  `@agent-facets/core` was always two things: the **facet artifact specification** (schemas, integrity rules, deterministic archive format, hash algorithm) and the **Bun-native CLI implementation** of that specification (subprocess-driven adapter bundling, registry HTTP client, install pipeline, scaffold, edit, self-update). The first set is portable Node-runnable data + cryptography that any third party  -- a registry server, a future alternative CLI, an offline `.facet` linter  -- needs to honor. The second is intrinsically Bun-native and runs only on a developer's machine.

  Splitting them produces three honest layers:

  * **`@agent-facets/protocol`** (NEW, public, Node-native, Node 22+)  -- the TypeScript reference implementation of the facet artifact specification. Schemas, bytes-validators, integrity verification, content hashing, deterministic tar layout, version-spec grammar, front-matter encoding, build validators. Pure data + cryptography. No subprocesses, no network, no developer-machine state.
  * **`@agent-facets/engine`** (RENAMED from `@agent-facets/core`, made private)  -- the Bun-native CLI machinery. Install pipeline, registry HTTP client, adapter machinery, source resolvers, manifest mutations, cache, scaffold, edit, self-update, build pipeline orchestrator, gzip compression, path-based loaders. Internal to the monorepo; never published.
  * **`agent-facets`** (the CLI binary, unchanged)  -- argv parsing, Ink TUI, error formatting, exit codes.

  See the [specification](/specification) for the full layer description and the design rationale for keeping the registry HTTP API outside the protocol.

  **Breaking:** `@agent-facets/core` is no longer published. The package is frozen at v0.9.1 on npm; existing pins continue to resolve, but there will be no further versions. New consumers (registry servers, third-party tooling) MUST use `@agent-facets/protocol`. There is no deprecation message on the legacy package  -- closed-alpha, no known external consumers.

  **No CLI behavior change.** Every `@agent-facets/core` import in the CLI was redirected to either `@agent-facets/protocol` (data primitives) or `@agent-facets/engine` (orchestrators). User-visible commands, flags, and output are unchanged.
</Update>

<Update
  label="2026-05-01"
  description="facet add now installs in one step; new source grammar; lockfile-driven install"
  tags={["CLI", "New Feature", "Breaking"]}
  rss={{
title: "facet add now installs in one step; new source grammar; lockfile-driven install",
description: "facet add now resolves, fetches, verifies, and installs a facet in a single command  -- no separate facet install step needed. New source grammar accepts registry names, github:owner/repo shorthand, plain https://...git URLs, SCP-style git@host:owner/repo, and local paths. Breaking: git+https:// and git+ssh:// prefixes are rejected (drop the git+); caret/tilde/comparator version ranges are rejected (use 1.* or 1.2.3); facet install no longer accepts --dry-run or positional arguments. Adds: lockfile bootstrap on first install, lockfile-driven reproducibility, three-check integrity protocol for registry sources, ~/.facets/cache/ with FACETS_CACHE_DIR override, repaired outcome when adapter files have drifted, server warnings, and adapter picker auto-launch when a project has no adapters."
}}
>
  ## facet add and facet install converge

  `facet add` now does everything end-to-end. Resolve, fetch, verify integrity, materialize into adapters, write the lockfile  -- all in a single command. There is no separate `facet install` step after `facet add`.

  ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # The old two-step flow:
  facet add github:owner/repo
  facet install            # <-- no longer needed

  # The new one-step flow:
  facet add github:owner/repo
  ```

  `facet install` is still there, and it's the right command after a fresh `git clone` or after pulling teammate changes that updated `facets.json`. It honors any pinned versions in `facets.lock` verbatim and only resolves entries that don't have a lockfile entry yet  -- making installs reproducible across machines without a separate `facet update` command.

  If a project has no adapters installed, both `facet add` and `facet install` now auto-launch the adapter picker on a TTY, so first-run experience is a single command from a cold start.

  ## New source grammar

  `facet add` accepts a richer set of sources, aligned with what npm and bun users already expect:

  ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
  facet add viper-plans              # registry name (resolved version pinned)
  facet add viper-plans@1.2.3        # exact version
  facet add viper-plans@1.*          # major-pinned wildcard
  facet add viper-plans@latest       # alias for the bare-name form
  facet add github:owner/repo#main   # GitHub shorthand with a ref
  facet add https://example.com/repo.git#v1.0.0
  facet add git@github.com:owner/repo.git#main
  facet add ./local-facets/my-plans  # local path inside the project
  facet add a b c                    # multi-source: install several at once
  ```

  Bare names default to the resolved exact version when written back to `facets.json`  -- so `facet add viper-plans` produces `viper-plans@1.2.3` in the manifest, the same way `npm install` and `bun add` pin lockable defaults.

  ## Lockfile-driven, with bootstrap

  `facets.lock` is now the single source of truth for what gets installed:

  * When a lockfile entry exists, that exact version is fetched. The manifest's range is not re-resolved.
  * When a lockfile entry doesn't exist (first run, or a freshly-added manifest entry), the manifest specifier is resolved fresh.
  * When `facets.lock` doesn't exist yet, `facet install` bootstraps it  -- the same way `bun install` creates `bun.lock`.

  ## Three-check integrity protocol

  Every fetched facet is verified before any asset is written:

  * **Registry sources** run three independent checks: cache vs. registry metadata, archive manifest vs. registry metadata, computed content vs. archive manifest. Each defends against a distinct adversary.
  * **Git sources** run a single check: computed content vs. lockfile integrity. Defends against tag-move attacks.
  * **Local sources** are trust-by-path.

  Any mismatch is a hard security error. The install aborts before any asset is written; the project is exactly as it was before.

  ## Cache

  Resolved facet content is cached at `~/.facets/cache/<name>@<version>/`. Subsequent installs of the same identity hit the cache instead of the network. Override with the `FACETS_CACHE_DIR` environment variable.

  ## Repaired outcome

  If you delete a materialized asset by hand and re-run `facet install`, the affected facet now reports as `repaired` in the summary  -- the adapter file is restored without bumping the version. This makes self-heal explicit instead of silent.

  ## Breaking changes

  * **`git+` prefix is removed.** Use plain `https://...git` or `git@host:owner/repo` instead. The new grammar accepts everything `git+` did, just without the prefix.
  * **Caret, tilde, and comparator version ranges are rejected.** Use `1.*` for major-pinned, `1.2.*` for minor-pinned, `*` or `latest` for unpinned, or `1.2.3` for exact. The `@latest` alias and bare-name form both produce the same result as `*`.
  * **`facet install --dry-run` is gone.** No replacement; `facet install` always commits.
  * **`facet install` rejects positional arguments.** To add a new facet to the project, use `facet add`.

  See the [facet add](/cli/add) and [facet install](/cli/install) CLI reference for full details.
</Update>

<Update
  label="2026-04-28"
  description="Publish pipeline fixed across all packages"
  tags={["CLI", "Fix"]}
  rss={{
title: "Publish pipeline fixed across all packages",
description: "Releases of agent-facets, @agent-facets/core, @agent-facets/adapter, and the first-party adapters (Claude Code, OpenCode, Codex) had been failing intermittently. The publish pipeline is now fixed and all packages have been republished. Reinstall with: npm install -g agent-facets, or curl -fsSL https://agentfacets.io/install | bash."
}}
>
  ## Publish pipeline fixed across all packages

  Recent releases of `agent-facets` and the supporting packages had been failing or shipping inconsistently due to issues in the publish pipeline. All affected packages have been republished from a known-good state:

  * `agent-facets`  -- the CLI
  * `@agent-facets/core` and `@agent-facets/adapter`  -- authoring and adapter SDKs
  * `@agent-facets/adapter-claude-code`, `@agent-facets/adapter-opencode`, and `@agent-facets/adapter-codex`  -- first-party adapters

  If you installed or upgraded the CLI in the last week and ran into install or runtime errors, reinstall:

  ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -fsSL https://agentfacets.io/install | bash
  ```

  Or, on any platform with Node.js:

  ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
  npm install -g agent-facets
  ```

  No usage changes  -- `facet add`, `facet install`, and the [adapter commands](/cli/adapters/install) all behave the same as before.
</Update>

<Update
  label="2026-04-22"
  description="Mobile-responsive landing page"
  tags={["Improvement"]}
  rss={{
title: "Mobile-responsive landing page",
description: "The Agent Facets landing page at agentfacets.io is now fully responsive on phones and tablets. Navigation collapses into a slide-down mobile menu, the CLI demo adapts for smaller screens, and all sections stack cleanly on narrow viewports."
}}
>
  ## Mobile-responsive landing page

  The [agentfacets.io](https://agentfacets.io) landing page is now fully responsive. If you previously visited on a phone or tablet, the experience was broken  -- the scroll-linked demo, navigation, and layout all assumed a desktop viewport. That's fixed.

  Here's what changed:

  * **Mobile navigation**  -- the nav bar collapses into a slide-down menu on screens ≤ 1024 px wide, with all links accessible from a single tap.
  * **Adapted CLI demo**  -- the interactive terminal demo skips the widest step on small screens so it fits without horizontal scrolling.
  * **Stacked sections**  -- the explainer, hero, and footer all reflow into a clean single-column layout on narrow viewports.
  * **Registry CTA**  -- on mobile, the install command is replaced with a link to [agentfacets.io](https://agentfacets.io) so you can browse facets without needing a terminal.

  The desktop layout is unchanged.
</Update>

<Update
  label="2026-04-21"
  description="Landing page and new docs URL"
  tags={["Improvement"]}
  rss={{
title: "Landing page and new docs URL",
description: "Agent Facets has a new landing page at https://agentfacets.io with a live CLI demo. Install the CLI with: curl -fsSL https://agentfacets.io/install | bash. Documentation has moved to https://docs.agentfacets.io  -- update any bookmarks."
}}
>
  ## agentfacets.io landing page

  The Agent Facets website now has a proper landing page at [agentfacets.io](https://agentfacets.io). It walks you through what facets are, shows a live CLI demo, and makes it easy to get started with a single install command:

  ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -fsSL https://agentfacets.io/install | bash
  ```

  ## New docs URL

  Documentation has moved to its own subdomain at [docs.agentfacets.io](https://docs.agentfacets.io). The main domain at `agentfacets.io` now serves the landing page, and the CLI installer lives at `agentfacets.io/install`. Existing docs links have been preserved  -- you'll just land on the new URL. Update any bookmarks accordingly.
</Update>

<Update
  label="2026-04-20"
  description="Install pipeline and new install URL"
  tags={["CLI", "New Feature"]}
  rss={{
title: "Install pipeline and new install URL",
description: "facet add and facet install are now available. Use 'facet add github:owner/repo' (or an https:// git URL, or a local path) to register a facet in facets.json, then 'facet install' to materialize assets into every connected adapter. Supports lockfile diffing, rollback on failure, atomic concurrent-install locking, and --verbose. The CLI installer has moved to https://agentfacets.io/install  -- install on macOS and Linux with 'curl -fsSL https://agentfacets.io/install | bash', or on any platform with Node.js via 'npm install -g agent-facets'."
}}
>
  ## facet add and facet install

  You can now add facets from external sources and install them into your AI coding tools end-to-end.

  **`facet add`** resolves a facet from GitHub, a Git URL, or a local path and writes it to your project's `facets.json`, preserving any hand-edited comments:

  ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
  facet add github:owner/repo
  facet add https://github.com/owner/repo.git
  facet add ./local-facet
  ```

  **`facet install`** reads `facets.json`, builds each facet, and materializes its assets into every adapter you've connected. The pipeline is built for iteration:

  * **Lockfile diffing**  -- only changed assets are written on each run.
  * **Rollback on failure**  -- if something goes wrong mid-install, changes are reversed automatically.
  * **Concurrent safety**  -- an atomic install lock prevents two `facet install` runs from interfering with each other.
  * **`--verbose`**  -- full pipeline trace for debugging.

  See the [facet add](/cli/add) and [facet install](/cli/install) CLI reference for details.

  ## New install URL

  The CLI installer has moved to its own home at `agentfacets.io/install`. If you previously bookmarked the install URL, update it to:

  ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -fsSL https://agentfacets.io/install | bash
  ```

  This works on macOS and Linux. For Windows or any platform with Node.js, you can install via npm instead:

  ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
  npm install -g agent-facets
  ```

  The [download link](/) on the docs site has been updated automatically.
</Update>

<Update label="2026-04-18" description="Faster adapter installs" tags={["CLI", "Improvement"]}>
  ## Self-contained adapter bundles

  Adapters now ship as fully self-contained bundles with all dependencies inlined. When you run `facet adapter install`, the CLI uses a prebuilt fast path that skips the build step entirely  -- falling back to a full rebuild only if the prebuilt bundle is missing or incompatible.

  This also means adapter installs no longer leave build artifacts in your source tree.

  See the [environment variables](/cli/env) reference for configuring the adapter install location with `FACETS_ADAPTERS_DIR`.
</Update>

<Update
  label="2026-04-17"
  description="Adapter system with Claude Code, OpenCode, and Codex"
  tags={["CLI", "New Feature"]}
  rss={{
title: "Adapter system with Claude Code, OpenCode, and Codex",
description: "Adapters are the bridge between facets and your AI coding tool. First-party adapters ship for Claude Code, OpenCode, and Codex (early access  -- installable but asset materialization is coming soon). Manage adapters with 'facet adapter install', 'facet adapter list', and 'facet adapter remove <name>'. Third-party adapters install via npm, Git URL, or local path. See /cli/adapters/install for built-in adapter names."
}}
>
  ## Adapter SDK and first-party adapters

  Adapters are the bridge between facets and your AI coding tool  -- each adapter knows where and how to write assets for a specific tool. The first three first-party adapters ship today:

  * **Claude Code**
  * **OpenCode**
  * **Codex** (early access  -- installable, but asset materialization via `facet install` is coming soon)

  When you run `facet install`, the CLI writes your facet assets into the correct locations for every adapter you've installed. Third-party adapters use the same installation and loading mechanism as first-party ones  -- install via npm, Git URL, or local path.

  Manage adapters with:

  ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
  facet adapter install
  facet adapter list
  facet adapter remove <name>
  ```

  If no adapters are installed when you run `facet install`, an interactive picker appears so you can select which AI tools to connect.

  See the [adapter CLI reference](/cli/adapters/install) for full usage, including all built-in adapter names.
</Update>

<Update label="2026-04-10" description="Self-contained archives" tags={["CLI", "Improvement"]}>
  ## Self-contained .facet archives

  The `.facet` build output is now a single self-contained archive. The build manifest is embedded inside the archive rather than shipped as a separate file, making distribution simpler. You can extract the manifest for debugging with `--emit-manifest` during build.

  See the [facet build](/cli/authoring/build) CLI reference.
</Update>
