Usage
facets.json for a newer release and installs the ones that move. facet upgrade is an alias of the same command — same flags, same output, same exit codes.
This updates the facets your project declares. To update the facet binary itself, use facet self-update.
facet update does not accept positional arguments. To update some facets but not others, use --interactive.
Examples
Flags
boolean
Update to each facet’s latest release, ignoring the range in
facets.json. Short form: -L.Because the selected version may fall outside what the project declares, this is the only mode that rewrites the specifier — see Crossing the declared range.It has no effect alongside --interactive, which already opens every row on its latest release. The combination is accepted, not rejected.boolean
Choose which facets to update, and which version each one takes. Short form:
-i.Every row opens on its latest release with nothing selected, so Space takes the newest version of that facet — see Choosing interactively.Requires a terminal that can prompt. Without one, the command fails before contacting the registry rather than after.boolean
Print the plan; do not modify any files. Exits
0 whether or not updates are available.boolean
Show detailed step output on stderr. Verbose output never reproduces an MCP server declaration — see Where declarations appear.
boolean
Approve the MCP server configuration this update would write, without prompting.An update is an install, so it collects the same approval on the same terms as
facet install. It does not authorize an asset takeover or resolve an asset name collision.There is no
--frozen-lockfile. Frozen mode reproduces what the lockfile already records, which is the opposite of what this command does. It belongs to facet install.Exit codes
Current, Target, and Latest
Every checkable facet has three versions, and the plan shows all three:installed
The exact version
facets.lock records as installed.range-respecting
The newest release the specifier in
facets.json already permits. Selecting it never changes what the project declares.newest published
The registry’s newest release, regardless of the declared specifier.
viper-plans moves to 1.4.0 under a plain facet update. cowsay does not move at all: its range allows nothing newer than what is installed, and reaching 3.0.0 requires --latest.
Plain facet update takes every Target that advances. --latest takes every Latest that advances.
Crossing the declared range
--latest may select a version the declared specifier forbids, so it rewrites that specifier — by the smallest edit that admits the new version while preserving how you chose to express intent:
Choosing interactively
--interactive lists every facet with an advancing version and lets you pick both which facets move and which version each one takes:
Space on the facets you want; each one takes its newest release. Nothing is chosen for you, so Space always means “yes, this one” — and a facet you never touch is simply left alone. --latest changes none of this; it is how the non-interactive run says what this screen already offers.
↑↓ moves between facets, wrapping at the ends. ◀ ▶ moves between the two columns and clamps instead, so holding one down settles on a column rather than oscillating. l also flips the focused row. Move a row to target before selecting it to take the version your declared range already permits instead.
A row showing a version that is already installed cannot be selected — pressing Space there says so rather than silently doing nothing. That is what you see on a pinned facet after moving it to target. Confirming requires at least one selected facet.
Selection happens before adapter selection and before anything is written, so Esc or Ctrl-C costs nothing: the command reports that nothing was applied and exits 1.
Without a terminal that can prompt — CI, piped output — --interactive fails immediately, before any registry lookup, and points at plain facet update or facet update --latest.
Previewing
--dry-run prints the plan and modifies nothing: no manifest, no lockfile, no receipt, no assets, no cache, and no adapter installation. Combined with --interactive, it previews the selection you confirmed and stops there.
A preview always prints the plan, including when nothing would move — with no row marked and no facets.json edit shown, followed by the reason. The plan is what the reason was read off: which facet is pinned, which is already current, which range is holding one back. A run without --dry-run prints the reason alone.
A preview shows versions, not their downstream effects. Which assets a new release materializes, whether it collides with another facet, and what MCP configuration it declares are all determined during the install that a real run performs.
What it does
1
Discover, without taking the project lock
Reads
facets.json and facets.lock, then asks the registry for each facet’s range Target and its Latest. This phase writes nothing and takes no lock, so reading a plan never blocks another facet operation on the machine. A single failed lookup rejects the whole discovery rather than presenting a partial answer.2
Select
Applies the mode’s default choices, or the ones you confirmed in the picker. A
--dry-run stops here.3
Apply through the install pipeline
Confirms the project has not changed since the plan was reviewed, then runs the ordinary install pipeline for the selected facets — the same verification, collision detection, MCP approval, materialization, rollback, and atomic tri-write as every other install. The reviewed version is installed as chosen; a release published between review and application does not change it.
facet install.
When nothing moves
A run that applies nothing still succeeded, and exits0. Which kind of nothing it is decides whether you have anything to do about it. Under --dry-run the plan is printed first, so you can check the reason against the rows it came from:
Sources that cannot be checked
Only registry facets have releases to compare. Git and local facets are named in the plan rather than counted as current:facets.json and running facet install.
When the project cannot be checked
If a declared registry facet is missing fromfacets.lock, locked to a different kind of source, locked at something that is not an exact release, or locked at a version its specifier no longer permits, update refuses to guess and exits 1. It names every affected facet, because one facet install covers all of them:
facet install’s job, not this command’s.
When the project moves mid-run
Iffacets.json or facets.lock changes between reading the plan and applying it, the update is withdrawn before anything is written and exits 1. Nothing is broken and nothing needs repairing — the plan simply described a project that no longer exists. Re-run facet update to plan against the current state.
Project facets vs. the CLI binary
Four command names sit one keystroke apart. Theself- prefix is what separates them:
Neither touches the other.
See also
facet install— the pipeline an update runs, and every outcome it reports.facet add— add a facet, or pin an existing one to an exact version.facet self-update— update the CLI binary.facet upgrade— the alias.- Commit specification — how a reviewed version is resolved and written.