Usage
facets.json for a newer release, then installs the ones that move. Applying an update is an ordinary install, so it reports the same outcomes and honors the same approvals.
facet upgrade is an alias of this command, with the same flags, output, and exit codes. Both act on the facets your project declares. To update the facet binary, use facet self-update.
facet update takes no positional arguments. To update some facets but not others, use --interactive.
Examples
Flags
boolean
Take each facet’s latest release, ignoring the range in
facets.json. Short form -L.This is the only mode that rewrites a specifier. See Crossing the declared range.It has no effect alongside --interactive, which already opens every row on its latest release.boolean
Choose which facets move and which version each takes. Short form
-i.Requires a terminal. Without one the command fails before contacting the registry.boolean
Print the plan and modify nothing. Exits
0 whether or not updates are available.boolean
Show detailed step output on stderr. Declaration contents never appear here. See where declarations appear.
boolean
Approve the MCP configuration this update would write, without prompting.
boolean
Print one JSON document to stdout instead of the plan view, and turn off the live progress indicator so its frames can’t land in front of the document. Cannot be combined with
--interactive: the picker writes to the same stream the document would. See JSON output.--frozen-lockfile here. Frozen mode reproduces what the lockfile records, which is the opposite of what this command does, and it belongs to facet install.
Exit codes
Output
facet install.
JSON output
--json prints one document to stdout and nothing else, with no plan view or progress indicator. The exit code and the document answer two different questions: the exit code says whether the command worked (0 for every successful run, whether or not anything moved, including a no-op or a completed --dry-run; 1 when preparation, selection, or the install itself failed; see Exit codes), and the document’s applied field and each facet’s outcome say whether anything moved. Check the exit code first; read the document for what happened.
A project that pins cowsay at 1.0.0, with 1.0.1 published to the registry, run through facet update --latest --json --accept-mcp:
1.0.1, so facets.lock records 1.0.1 afterward, yet current in the document still reads 1.0.0. That is deliberate: current is the version that was locked when this run started, the “from”; resolved is what the run’s flags took, the “to”. This holds on an applied run too, not only on a preview.
boolean
Whether this run wrote changes to the project.
false for a dry run or a no-op. An applied update does not necessarily change every file; for example, a range-respecting update preserves the specifier in facets.json.array
One entry per declared facet, each with
declared (the specifier exactly as written in facets.json), current (the version locked when this run started), target (the newest release the declared range permits), latest (the registry’s newest release), resolved (the version this run’s flags actually take, or null), and outcome.outcome is one of four values:
outcome
This run’s flags resolve to a newer release for it:
target under a plain update, latest under --latest. resolved carries that version; it is never null here.outcome says what the flags resolve to, not whether anything was written. Read applied for that: a preview (--dry-run, or facet outdated) reports applied: false while still marking movable facets updated, because that is what the same flags would do for real.outcome
Already at the newest release that exists. Nothing to take;
resolved is null.outcome
A newer release exists, but this run’s flags don’t resolve to it;
resolved is null. The common case is a facet pinned to an exact version; taking anything newer needs --latest. A floating range can also land here, but not merely because it falls short of the newest release: if the range’s own ceiling still advances past what’s locked, that’s updated, not held (see Current, Target, and Latest). --latest can report held too, when the registry’s newest answer has moved backwards relative to what’s locked while an advancing range target still exists.outcome
A git or local source. Not checked for updates;
current, target, latest, and resolved are all null.counts tallies how many facets landed in each outcome, always summing to facets.length.
Every refusal or anticipated failure under --json writes a single document to stdout, nothing to stderr, and exits 1. Recovery details, including affected files and MCP servers, appear in error.detail. Captured from a real facet update --json --interactive run:
facets.json declaring a source facets.lock doesn’t recognize:
Details
Current, Target, and Latest
Every checkable facet has three versions, and the plan shows all three:installed
The exact version
facets.lock records when the run starts. It does not move even on an applied run: facets.lock is rewritten, but this value keeps describing the “from”, not the “to”. Read resolved in the JSON output for what a run under --json actually took.range-respecting
The newest release the specifier in
facets.json already permits. Taking it never changes what the project declares. Plain facet update takes Target.newest published
The registry’s newest release, whatever the specifier says.
--latest takes Latest instead of Target.viper-plans moves to 1.4.0 under a plain update. cowsay does not move, and reaching 3.0.0 needs --latest.
Plain facet update takes every Target that advances. --latest takes every Latest that advances.
Crossing the declared range
--latest can select a version the declared specifier forbids, so it rewrites that specifier with the smallest edit that admits the new version:
Choosing interactively
Space takes the newest release of that facet and a facet you never touch is left alone. Move a row to target first to take the version your declared range already permits.
Only the digit that moves is coloured, and the column a row would install is also named in words, so the screen works without colour. A row already at the installed version cannot be selected, and confirming needs at least one selection.
Selection happens before adapter discovery and before anything is written, so cancelling costs nothing.
Previewing
--dry-run prints the plan and writes nothing: no manifest, no lockfile, no assets, no cache, and no adapter installation. That also means a preview works on a machine with no adapter connected.
A preview always prints the plan, including when nothing would move, followed by the reason. A real run prints the reason alone. Combined with --interactive it previews the selection you confirmed.
A preview shows versions, not their effects. Which assets a release materializes, whether it collides, and what it declares are determined during the install a real run performs.
When nothing moves
A run that applies nothing still succeeded and exits0.
Sources that cannot be checked
Only registry facets have releases to compare. Git and local facets are named in the printed plan rather than counted as current:--dry-run to see these rows. Move one by editing its source in facets.json and running facet install.
Troubleshooting
this project cannot be checked for updates yet
this project cannot be checked for updates yet
Cause:
facets.json and facets.lock disagree. A declared registry facet is missing from the lockfile, locked to a different source kind, locked at something that is not an exact release, or locked at a version its specifier no longer permits. The message names every affected facet.Fix: repair the drift first, then update:facets.json declares a source that cannot be parsed
facets.json declares a source that cannot be parsed
Cause: an entry’s specifier is malformed, so update cannot tell what it is meant to resolve to.Fix: correct that entry in
facets.json by hand. facet install cannot repair a malformed specifier.the project changed while the update ran
the project changed while the update ran
Cause:
facets.json or facets.lock changed while the plan was being built, or between reading the plan and applying it. A concurrent command, a pull, or an editor save will do it.Fix: re-run facet update. Nothing was written in either case, so nothing needs repairing.facet update --interactive needs an interactive terminal
facet update --interactive needs an interactive terminal
Cause: the picker needs a TTY. The check runs before any registry lookup.Fix: use a non-interactive mode instead:
the update selection screen could not be shown
the update selection screen could not be shown
Cause: the terminal check passed, but the picker could not render, for example because stdin was taken away mid-run.Fix: re-run in a normal terminal, or pick a non-interactive mode. Nothing was applied.
facet update does not accept positional arguments
facet update does not accept positional arguments
Cause: update considers everything declared in
facets.json.Fix: choose per facet with facet update --interactive.the registry returned an unusable version
the registry returned an unusable version
Cause: the registry answered with something that is not an exact release, or resolved a facet outside its declared range. Your project cannot cause this.Fix: retry shortly. If it persists, report it with the facet name from the message.