Skip to content

CLI reference

CommandWhat it does
patchcli setupInstall the Swift→WebAssembly toolchain `patchcli release` needs (one-time).
patchcli initSet up this app for Patch: detect the project, register the app, add the SDK, insert startup code.
patchcli prepareMake this app's SwiftUI views patchable out of the box (dynamic-replacement thunks).
patchcli doctorPreflight: check (read-only) that this project is set up for OTA patches, with a fix hint per gap.
patchcli buildParse → classify → split → compile to a real .wasm module. Track F1.
patchcli pushUpload the built .wasm to the backend (gated on fingerprint compatibility). Track F2.
patchcli releaseRelease a patch to your users: build the .wasm then upload it in one shot (fingerprint-gated).
patchcli statusShow the current deployment: version, rollout %, adoption, failure rate.
patchcli rollbackRoll back to the previous module (or --to <version>).
patchcli fingerprintCompute / diff / register the native-shell fingerprint.
patchcli fingerprint diffShow what changed in the native shell since the last registered fingerprint.
patchcli fingerprint registerRegister the current native-shell fingerprint with the backend.
patchcli channelsList deployment channels for the configured app (+ active module per channel).
patchcli whoamiPrint the configured app_id / workspace_id / base URL / channel from .Patch.yml.
patchcli analyzeAnalysis only, for CI. Reports OTA coverage; exits 1 if the native shell changed.
patchcli compileGenerate _wasm.swift for OTA-eligible code and compile it to a real .wasm module.
patchcli overlayAuthor + package an OTA resource overlay (colors / strings / images).
patchcli overlay packageBundle an overlay spec (JSON) into the built module artifact.
patchcli overlay inspectPrint the resource overlay carried by a packaged module artifact.

patchcli setup

Install the Swift→WebAssembly toolchain `patchcli release` needs (one-time).

OVERVIEW: Install the Swift→WebAssembly toolchain `patchcli release` needs
(one-time).

USAGE: patchcli setup [--force]

OPTIONS:
  --force                 Reinstall even if a toolchain is already detected.
  --version               Show the version.
  -h, --help              Show help information.

patchcli init

Set up this app for Patch: detect the project, register the app, add the SDK, insert startup code.

OVERVIEW: Set up this app for Patch: detect the project, register the app, add
the SDK, insert startup code.

USAGE: patchcli init [<path>] [--target <target>] [--base-url <base-url>] [--force] [--manual] [--no-open] [--yes] [--skip-package] [--skip-code]

ARGUMENTS:
  <path>                  Project directory (default: current directory).

OPTIONS:
  --target <target>       Build target. Optional: inferred from Package.swift /
                          .xcodeproj / source layout when omitted.
  --base-url <base-url>   Backend base URL to record in .Patch.yml (root or
                          .../api/v1 — both work). Default: the live production
                          API.
  --force                 Overwrite an existing .Patch.yml and start fresh.
  --manual                Skip all automation (browser registration, package
                          add, code insertion); just write .Patch.yml and print
                          the manual steps.
  --no-open               Don't auto-open the browser for registration (the URL
                          is printed instead).
  --yes                   Apply proposed source-code changes without asking.
  --skip-package          Skip adding the PatchSDK Swift package to the Xcode
                          project.
  --skip-code             Skip inserting the Patch startup code into App.swift.
  --version               Show the version.
  -h, --help              Show help information.

patchcli prepare

Make this app's SwiftUI views patchable out of the box (dynamic-replacement thunks).

OVERVIEW: Make this app's SwiftUI views patchable out of the box
(dynamic-replacement thunks).

USAGE: patchcli prepare [<path>] [--yes] [--thunks-only] [--check] [--quiet]

ARGUMENTS:
  <path>                  Project directory (default: the directory of
                          .Patch.yml, else CWD).

OPTIONS:
  --yes                   Apply source changes without asking.
  --thunks-only           Only regenerate the thunk file; never edit existing
                          sources. (For a build phase.)
  --check                 Report views that aren't patch-ready (missing
                          `dynamic`) and exit non-zero if any. (For CI /
                          pre-push.)
  --quiet                 Less output.
  --version               Show the version.
  -h, --help              Show help information.

patchcli doctor

Preflight: check (read-only) that this project is set up for OTA patches, with a fix hint per gap.

OVERVIEW: Preflight: check (read-only) that this project is set up for OTA
patches, with a fix hint per gap.

USAGE: patchcli doctor [<path>] [--base-url <base-url>] [--offline] [--json]

ARGUMENTS:
  <path>                  Project directory (default: the directory of
                          .Patch.yml, else CWD).

OPTIONS:
  --base-url <base-url>   Backend base URL override (for the fingerprint check).
  --offline               Skip every network call (the fingerprint-registration
                          check degrades to a warning).
  --json                  Output JSON (machine-readable).
  --version               Show the version.
  -h, --help              Show help information.

patchcli build

Parse → classify → split → compile to a real .wasm module. Track F1.

OVERVIEW: Parse → classify → split → compile to a real .wasm module. Track F1.

USAGE: patchcli build [<path>] [--verbose] [--dry-run] [--report <report>] [--optimization <optimization>] [--output <output>] [--no-prepare]

ARGUMENTS:
  <path>                  Source directory to build (default: project root from
                          .Patch.yml, else CWD).

OPTIONS:
  --verbose               Verbose: print generated sources + per-function table.
  --dry-run               Generate sources + coverage report but skip the WASM
                          compile.
  --report <report>       Write the coverage report (JSON) to this path.
  --optimization <optimization>
                          Optimization: size | speed.
  --output <output>       Output .wasm path (default: .Patch/build/module.wasm).
  --no-prepare            Skip the automatic `prepare` step (don't add
                          `dynamic`/thunks to new views before building).
  --version               Show the version.
  -h, --help              Show help information.

patchcli push

Upload the built .wasm to the backend (gated on fingerprint compatibility). Track F2.

OVERVIEW: Upload the built .wasm to the backend (gated on fingerprint
compatibility). Track F2.

USAGE: patchcli push <options>

OPTIONS:
  --channel <channel>     Release channel (default: production). (default:
                          production)
  --rollout <rollout>     Rollout percentage 0-100 (default: 100). (default:
                          100)
  --message <message>     Release notes / changelog message.
  --version <version>     Module version (default: timestamp-derived).
  --module <module>       Path to the .wasm to push (default:
                          .Patch/build/module.wasm).
  --base-url <base-url>   Backend base URL override.
  --mandatory             Mark the update as mandatory.
  --min-app-version <min-app-version>
                          Only deliver to app versions ≥ this (e.g. 2.1.0).
                          Absent = all versions.
  --max-app-version <max-app-version>
                          Only deliver to app versions ≤ this (e.g. 3.0.0).
                          Absent = no upper bound.
  --min-os-version <min-os-version>
                          Only deliver to OS versions ≥ this (e.g. 16.0).
                          Absent = all OS versions.
  --target-cohort <target-cohort>
                          Restrict the rollout to a named cohort/segment.
                          Absent = all devices.
  --dry-run               Run all checks + show what would be sent, but do not
                          upload.
  --force                 Upload even if the native-shell fingerprint gate
                          reports a MISMATCH (dangerous; skips the
                          compatibility refusal).
  --allow-native-drift    Ship the patch when the native shell drifted ONLY in
                          ways the OTA patch can't see (a package / Info.plist
                          / entitlements / deployment-target change, with no
                          native-source change). Refused if any change is
                          patch-affecting. The non-interactive form of the
                          native-drift prompt.
  --no-prepare            Skip the automatic `prepare` step (don't add
                          `dynamic`/thunks to new views before pushing).
  --version               Show the version.
  -h, --help              Show help information.

patchcli release

Release a patch to your users: build the .wasm then upload it in one shot (fingerprint-gated).

OVERVIEW: Release a patch to your users: build the .wasm then upload it in one
shot (fingerprint-gated).

USAGE: patchcli release <options>

OPTIONS:
  --optimization <optimization>
                          Optimization: size | speed.
  --output <output>       Output .wasm path (default: .Patch/build/module.wasm).
  --channel <channel>     Release channel (default: production). (default:
                          production)
  --rollout <rollout>     Rollout percentage 0-100 (default: 100). (default:
                          100)
  -m, --message <message> Release notes / changelog message.
  --version <version>     Module version (default: timestamp-derived).
  --base-url <base-url>   Backend base URL override.
  --mandatory             Mark the update as mandatory.
  --min-app-version <min-app-version>
                          Only deliver to app versions ≥ this (e.g. 2.1.0).
                          Absent = all versions.
  --max-app-version <max-app-version>
                          Only deliver to app versions ≤ this (e.g. 3.0.0).
                          Absent = no upper bound.
  --min-os-version <min-os-version>
                          Only deliver to OS versions ≥ this (e.g. 16.0).
                          Absent = all OS versions.
  --target-cohort <target-cohort>
                          Restrict the rollout to a named cohort/segment.
                          Absent = all devices.
  --dry-run               Build, then run all push checks + show what would be
                          sent, but do not upload.
  --force                 Upload even if the native-shell fingerprint gate
                          reports a MISMATCH (dangerous; skips the
                          compatibility refusal).
  --allow-native-drift    Ship the patch when the native shell drifted ONLY in
                          ways the OTA patch can't see (a package / Info.plist
                          / entitlements / deployment-target change, with no
                          native-source change). Refused if any change is
                          patch-affecting. The non-interactive form of the
                          native-drift prompt.
  --allow-empty           Release even if the module has no real OTA coverage
                          (only a version probe / a degenerate near-empty
                          module). Off by default.
  --no-prepare            Skip the automatic `prepare` step (don't add
                          `dynamic`/thunks to new views before building).
  --version               Show the version.
  -h, --help              Show help information.

patchcli status

Show the current deployment: version, rollout %, adoption, failure rate.

OVERVIEW: Show the current deployment: version, rollout %, adoption, failure
rate.

USAGE: patchcli status [--channel <channel>] [--base-url <base-url>] [--json]

OPTIONS:
  --channel <channel>     Release channel (default: production). (default:
                          production)
  --base-url <base-url>   Backend base URL override.
  --json                  Output JSON.
  --version               Show the version.
  -h, --help              Show help information.

patchcli rollback

Roll back to the previous module (or --to <version>).

OVERVIEW: Roll back to the previous module (or --to <version>).

USAGE: patchcli rollback [--channel <channel>] [--to <to>] [--base-url <base-url>] [--dry-run] [--json]

OPTIONS:
  --channel <channel>     Release channel (default: production). (default:
                          production)
  --to <to>               Roll back to a specific version (rolls back
                          everything newer first).
  --base-url <base-url>   Backend base URL override.
  --dry-run               Show what would be rolled back without calling the
                          backend.
  --json                  Output JSON.
  --version               Show the version.
  -h, --help              Show help information.

patchcli fingerprint

Compute / diff / register the native-shell fingerprint.

OVERVIEW: Compute / diff / register the native-shell fingerprint.

USAGE: patchcli fingerprint <subcommand>

OPTIONS:
  --version               Show the version.
  -h, --help              Show help information.

SUBCOMMANDS:
  diff (default)          Show what changed in the native shell since the last
                          registered fingerprint.
  register                Register the current native-shell fingerprint with
                          the backend.

  See 'patchcli help fingerprint <subcommand>' for detailed help.

patchcli fingerprint diff

Show what changed in the native shell since the last registered fingerprint.

OVERVIEW: Show what changed in the native shell since the last registered
fingerprint.

USAGE: patchcli fingerprint diff [--base-url <base-url>] [--json] [--explain] [--no-build]

OPTIONS:
  --base-url <base-url>   Backend base URL override.
  --json                  Output JSON.
  --explain               Also list the native (not OTA-patchable) functions in
                          the shell and WHY each is native.
  --no-build              Skip the build-confirmation compile; compute the
                          STATIC estimate instead (faster, but the result is an
                          estimate — the authoritative check runs at
                          build/release).
  --version               Show the version.
  -h, --help              Show help information.

patchcli fingerprint register

Register the current native-shell fingerprint with the backend.

OVERVIEW: Register the current native-shell fingerprint with the backend.

USAGE: patchcli fingerprint register [--base-url <base-url>] [--app-version <app-version>] [--dry-run] [--json] [--no-prepare]

OPTIONS:
  --base-url <base-url>   Backend base URL override.
  --app-version <app-version>
                          App version string to record (e.g. 2.0.0).
  --dry-run               Compute + print the fingerprint but do not call the
                          backend.
  --json                  Output JSON.
  --no-prepare            Skip the automatic prepare step before hashing
                          (advanced — risks a post-upgrade fingerprint mismatch
                          on the next release).
  --version               Show the version.
  -h, --help              Show help information.

patchcli channels

List deployment channels for the configured app (+ active module per channel).

OVERVIEW: List deployment channels for the configured app (+ active module per
channel).

USAGE: patchcli channels [--base-url <base-url>] [--json]

OPTIONS:
  --base-url <base-url>   Backend base URL override.
  --json                  Output JSON.
  --version               Show the version.
  -h, --help              Show help information.

patchcli whoami

Print the configured app_id / workspace_id / base URL / channel from .Patch.yml.

OVERVIEW: Print the configured app_id / workspace_id / base URL / channel from
.Patch.yml.

USAGE: patchcli whoami [--base-url <base-url>] [--json]

OPTIONS:
  --base-url <base-url>   Backend base URL override (to preview what would be
                          used).
  --json                  Output JSON.
  --version               Show the version.
  -h, --help              Show help information.

patchcli analyze

Analysis only, for CI. Reports OTA coverage; exits 1 if the native shell changed.

OVERVIEW: Analysis only, for CI. Reports OTA coverage; exits 1 if the native
shell changed.

USAGE: patchcli analyze <path> [--format <format>] [--verbose] [--fast-no-split-check] [--split-diagnostics] [--check-fingerprint] [--fingerprint-baseline <fingerprint-baseline>] [--base-url <base-url>]

ARGUMENTS:
  <path>                  Path to a directory (or a single .swift file) to
                          analyze.

OPTIONS:
  --format <format>       Output format: text (default) or json. (default: text)
  --verbose               Print the per-function classification table.
  --fast-no-split-check   Skip the realized-split pass (faster; reports only
                          the optimistic mixed-inclusive OTA%).
  --split-diagnostics     Print the per-strategy realized-split outcome
                          breakdown (A / sub-expr / guard / unrealized reasons).
  --check-fingerprint     CI gate: exit 1 if the native-shell fingerprint
                          differs from the baseline (.Patch.yml + backend, or
                          --fingerprint-baseline).
  --fingerprint-baseline <fingerprint-baseline>
                          Expected native-shell fingerprint to compare against
                          (CI; avoids a backend call).
  --base-url <base-url>   Backend base URL override (for --check-fingerprint).
  --version               Show the version.
  -h, --help              Show help information.

patchcli compile

Generate _wasm.swift for OTA-eligible code and compile it to a real .wasm module.

OVERVIEW: Generate _wasm.swift for OTA-eligible code and compile it to a real
.wasm module.

USAGE: patchcli compile <path> [--output <output>] [--swift-sdk <swift-sdk>] [--exports <exports>]

ARGUMENTS:
  <path>                  Path to a directory of generated _wasm.swift files
                          (or a single .swift file) to compile.

OPTIONS:
  --output <output>       Output .wasm path. (default: patch.wasm)
  --swift-sdk <swift-sdk> Swift SDK identifier for the WASM target. (default:
                          swift-6.3.2-RELEASE_wasm)
  --exports <exports>     Comma-separated symbols to export from the module.
  --version               Show the version.
  -h, --help              Show help information.

patchcli overlay

Author + package an OTA resource overlay (colors / strings / images).

OVERVIEW: Author + package an OTA resource overlay (colors / strings / images).

USAGE: patchcli overlay <subcommand>

OPTIONS:
  --version               Show the version.
  -h, --help              Show help information.

SUBCOMMANDS:
  package                 Bundle an overlay spec (JSON) into the built module
                          artifact.
  inspect                 Print the resource overlay carried by a packaged
                          module artifact.

  See 'patchcli help overlay <subcommand>' for detailed help.

patchcli overlay package

Bundle an overlay spec (JSON) into the built module artifact.

OVERVIEW: Bundle an overlay spec (JSON) into the built module artifact.

USAGE: patchcli overlay package <spec> [--module <module>] [--output <output>]

ARGUMENTS:
  <spec>                  Path to the overlay spec JSON (colors / strings /
                          images).

OPTIONS:
  --module <module>       Module to wrap (default: .Patch/build/module.wasm).
  --output <output>       Where to write the wrapped artifact (default:
                          overwrite the module in place).
  --version               Show the version.
  -h, --help              Show help information.

patchcli overlay inspect

Print the resource overlay carried by a packaged module artifact.

OVERVIEW: Print the resource overlay carried by a packaged module artifact.

USAGE: patchcli overlay inspect <artifact>

ARGUMENTS:
  <artifact>              Path to the artifact (a POVR-wrapped module, or any
                          module).

OPTIONS:
  --version               Show the version.
  -h, --help              Show help information.

The full command surface of the patchcli CLI. build and compile drive the real engine and the real WASM toolchain; push, release, status, rollback, and fingerprint talk to the backend over HTTP. Run patchcli <command> --help for full details on any command.

Which command compiles vs. uploads? patchcli build compiles your Swift to a real .wasm at .Patch/build/module.wasm but never talks to the backend. patchcli push does not build — it only uploads an already-built .wasm (the one build wrote, or the path you pass with --module), gated on the fingerprint check. patchcli release is the everyday command: it runs the same build pipeline as build, then the same fingerprint-gated upload as push — i.e. release = build + push.

Command What it does Key flags
patchcli init [path] One-command app setup: detect the Xcode project/target/bundle id, register the app via the browser (app.patchrelease.com/cli-connect — the CLI automatically receives the app key and a publish token, writing app_key/publish_token/app_id/workspace_id into .Patch.yml), add the PatchSDK package to the project (backup at project.pbxproj.patch-backup), and propose the startup-code diff, applied after you confirm. Skips anything already done — safe to re-run. Anything it can’t do safely falls back to printed manual steps. --manual · --no-open · --yes · --skip-package · --skip-code · --force · --target <name> · --base-url <url>
patchcli login [path] (new) Get a publish token (ppt_…) for this project and save it to .Patch.yml. Opens your browser, you approve, the CLI receives the token — the same hand-off init uses, on its own so you can run it any time. Use it when you clone a repo that has .Patch.yml but no token, when a token is revoked, or to mint one for CI. The token is never printed. --base-url <url>
patchcli doctor [path] (new) Read-only setup/health preflight — answers “is my app set up correctly for OTA patches?” Runs five checks (each ✓/⚠/✗ with a one-line fix hint), never mutates the project, and exits non-zero on any blocking ✗ so it’s CI-usable. Run it after init or whenever a patch isn’t behaving. See the doctor section. --json · --offline · --base-url <url>
patchcli prepare [path] Make your SwiftUI views patchable: mark each var body: some View dynamic and generate the @_dynamicReplacement(for: body) thunks (the same mechanism Xcode Previews uses), so a future OTA patch re-renders your views with no PatchView wrapping. Idempotent. You normally never run this yourself — it runs automatically on every build / push / release. This standalone command stays for CI (--check) and debugging. --check
patchcli build [path] Parse → classify → split → compile to a real .wasm module at .Patch/build/module.wasm. Does not upload. Auto-runs prepare first (insert dynamic + thunks for any new views) unless --no-prepare. --verbose · --dry-run · --report <path> · --optimization size|speed · --output <path> · --no-prepare
patchcli push Upload an already-built .wasm to the backend, gated on a fingerprint-compatibility check. Does not build — run build first (or use release). --channel <name> · --rollout 0-100 · --message <text> · --version <ver> · --module <path> · --mandatory · --min-app-version <ver> · --max-app-version <ver> · --min-os-version <ver> · --target-cohort <name> · --allow-native-drift · --base-url <url> · --dry-run · --no-prepare
patchcli release (new) Build then push in one command — the everyday deploy. Runs the same build pipeline as build, then the same fingerprint-gated upload as push; accepts both flag sets (no --module, since it builds the module itself). Auto-runs prepare first so views you add are always patchable (skip with --no-prepare). --channel <name> · --rollout 0-100 · --message <text> · --version <ver> · --mandatory · --min-app-version <ver> · --max-app-version <ver> · --min-os-version <ver> · --target-cohort <name> · --allow-native-drift · --optimization size|speed · --output <path> · --base-url <url> · --dry-run · --no-prepare
patchcli status Show the current deployment: version, rollout %, adoption, failure rate. --channel <name> · --base-url <url> · --json
patchcli rollback Roll back to the previous module (or to a specific version). --channel <name> · --to <version> · --base-url <url> · --dry-run · --json
patchcli fingerprint <diff|register> Diff or register the native-shell fingerprint that gates OTA compatibility. diff is the default subcommand. --base-url <url> · --app-version <ver> (register) · --dry-run (register) · --json
patchcli analyze <path> Analysis only, for CI/code review. Reports OTA coverage; with --check-fingerprint it exits 1 if the native shell changed. --format text|json · --verbose · --check-fingerprint · --fingerprint-baseline <hash>
patchcli channels List the deployment channels seen for the configured app, with the active module per channel. Calls GET /apps/{app_id}/channels (falls back to deriving channels from the module list). --base-url <url> · --json
patchcli whoami Print the configured app_id / workspace_id / base URL / channel resolved from .Patch.yml (and env overrides). Handy for confirming which backend a command will hit. --json
patchcli compile <path> Lower-level: compile a directory of generated _wasm.swift to a real .wasm module. --output <path> · --swift-sdk <id> · --exports <symbols>

patchcli build parses, classifies, splits, and compiles, then prints a coverage report and writes the module to .Patch/build/module.wasm. Use --verbose for the per-function table and --dry-run to skip the WASM compile.

~/MyApp — zsh
$ patchcli build ./Sources
Coverage report:
WASM-eligible (pure) 7 (50.0%)
WASM-eligible (bridged) 3 (21.4%)
Mixed (auto-split) 0 (0.0%)
Native (stays in shell) 4 (28.6%)
OTA-updatable (realized): 71.4%
Packaging tier (start): T0 Embedded + bridges (tens of KB)
Module: .Patch/build/module.wasm (38.2 KB)
Build succeeded push with: patchcli push

patchcli release runs the build pipeline, confirms the module, then runs the same fingerprint-gated push as patchcli push — so it accepts both sets of flags. This is the command you’ll use day to day.

~/MyApp — zsh
# everyday: build + push to a 10% staged rollout on production
$ patchcli release --rollout 10 --message "Tighten refund eligibility"
# release to a named channel, optimizing the module for size
$ patchcli release --channel staging --optimization size
# mark a release mandatory (see Force updates)
$ patchcli release --rollout 100 --mandatory --message "Critical tax fix"
# preflight only — build + run all push checks, but do not upload
$ patchcli release --dry-run
~/MyApp — zsh
# current deployment + device telemetry on a channel
$ patchcli status --channel production
Active version: 2026.06.03.142210
Rollout: 10%
Adoption: 96.4% (activations / downloads)
Failure rate: 0.2% (errors / (activations+errors))
# one-click revert to the previous module (≈60s to propagate)
$ patchcli rollback --channel production
# roll all the way back to a specific version
$ patchcli rollback --to 2026.05.30.090112

patchcli doctor is the “is my setup correct?” command. It runs a read-only preflight — it never edits your project (unlike init / prepare), so it’s safe to run anytime, in CI, or on a teammate’s machine. Run it right after patchcli init, or whenever a patch isn’t behaving the way you expect. It reports five checks, each with a ✓ / ⚠ / ✗ and a one-line fix hint:

  1. .Patch.yml present + valid — has a publish token (ppt_…, or PATCH_API_KEY set); app_id recommended for backend commands. An app_key alone fails this check: it is a public identifier, not a publish credential.
  2. PatchSDK package added + linked — referenced in your .xcodeproj or Package.swift and linked into your app target.
  3. Patch.configure(...) + Patch.shared.start() in the @main App — plus import PatchSDK (a configure with no start() never fetches updates).
  4. Views are patch-ready — every SwiftUI view body is dynamic and the dynamic-replacement thunks exist. If some aren’t yet, this is just a ⚠ (not a blocker): build / push / release auto-run prepare, so it self-heals on your next release.
  5. Native-shell fingerprint registered + current — the registered fingerprint matches the current shell (needs network; degrades to a ⚠ with --offline).
~/MyApp — zsh
$ patchcli doctor
Patch doctor
============
.Patch.yml present + valid
publish token set; app_id 3f2b…-uuid.
PatchSDK package added to the project
MyApp.xcodeproj references patch-swift and links the PatchSDK product.
Patch.configure(...) + start() in the @main App entry
Patch.configure(...) + Patch.shared.start() + import PatchSDK found (MyAppApp.swift).
Views are patch-ready (bodies dynamic + thunks)
12 SwiftUI view(s) are `dynamic` + have generated thunks.
Native-shell fingerprint registered + current
Registered fingerprint matches the current native shell (3f2b9c1e7a4d…).
5/5 checks passed.
READY this project is set up for OTA patches.

It exits non-zero on any blocking ✗ (the ⚠ items are non-fatal but worth fixing), so you can drop it into CI as a setup gate. Add --json for a machine-readable report:

CI — setup gate
# fail the job if the project isn't set up for OTA patches
$ patchcli doctor --json

Sometimes your native shell changes but in ways the OTA patch can’t see — you added a Swift package, edited Info.plist, tweaked an entitlement, or bumped the deployment target, without touching the source the patch is built against. The plain fingerprint gate would block the push, even though the view patch is provably compatible. To handle this, patchcli fingerprint diff tags each changed component as either native-only (the patch can’t see it) or patch-affecting (it can change the surface the patch is built against), and --allow-native-drift lets you ship a view patch when every change is native-only.

Changed component Classified Why
Info.plist native-only Pure native-shell metadata (display name, permission strings, URL schemes). The WASM module never reads or links it.
Entitlements native-only Native binary capabilities (keychain groups, App Groups, push) — an OS/codesign concern the WASM ABI is blind to.
Linked frameworks native-only Adding/removing a framework the shell links. (If a patched view had started using it, the view’s source would have changed too — that shows up as a patch-affecting native-source change and blocks the skip.)
Deployment target native-only A native build setting — it changes which OS versions the binary supports, not the OS-version-independent WASM ABI.
Native .swift files patch-affecting A native function, signature, or file add/remove can change the surface the patch is built against — never skipped.
Bridge definitions patch-affecting Host bridges are capabilities the module calls; changing them shifts the shell ABI.
SDK / WasmKit / compiler version patch-affecting These govern how the module is marshalled and executed on-device — a rebuild + re-register is required.
~/MyApp — zsh
# see the per-component breakdown (native-only vs patch-affecting)
$ patchcli fingerprint diff
# the native shell drifted, but only natively — ship the view patch anyway
$ patchcli release --allow-native-drift --message "Restyle the paywall"

patchcli init writes .Patch.yml at your project root — including the app_key/publish_token/app_id/workspace_id it receives during browser registration. It’s a small, fixed-shape YAML file. Environment variables override the API fields at call time; app_id/workspace_id are only needed for backend commands (push/release/status/rollback).

.Patch.yml
version: 1
app_key: pak_live_… # PUBLIC app identifier (also baked into your binary)
publish_token: ppt_… # SECRET publish credential — env PATCH_API_KEY overrides
project: MyApp.xcodeproj
target: MyApp
app_id: 3f2b…-uuid # required for push/release/status/rollback
workspace_id: 9a01…-uuid
api_base_url: https://api.patchrelease.com
exclude: # paths/globs to skip during analysis
[]
bridges: # host bridges available to OTA code
networking: true
userDefaults: true
notifications: true
navigation: true
keychain: true
dateLocale: true
logging: true
build:
optimization: size # size | speed
stripDebugInfo: true
swiftui: true # lower SwiftUI views to WASM (default on)
Field Type Description
version int Config schema version. Currently 1.
app_key string PUBLIC per-app identifier (pak_…). Ships inside your binary and rides the device check-in body. Authenticates nothing — it is not accepted as X-API-Key.
publish_token string? SECRET publish credential (ppt_…), sent as X-API-Key. Written by patchcli init/login. Overridden by env PATCH_API_KEY.
project string Detected project file (e.g. MyApp.xcodeproj or Package.swift).
target string The app target whose sources Patch analyzes.
app_id uuid? Backend app UUID. Required for push/release/status/rollback; optional for offline build/analyze.
workspace_id uuid? Backend workspace UUID the app belongs to.
api_base_url url? Backend base URL. Overridden by env PATCH_API_URL / the –base-url flag.
api_key string? Legacy alias for publish_token, still read as a fallback. Overridden by env PATCH_API_KEY. An app key here is ignored.
exclude string[] Paths/globs to skip during analysis. Empty list = analyze everything.
bridges.* bool Toggle each host bridge: networking, userDefaults, notifications, navigation, keychain, dateLocale, logging, analytics, fileStorage, connectivity, biometrics, appReview, pasteboard, haptics, deviceInfo, shareSheet, openURL, location, calendar, contacts, appBadge, mailCompose, inAppPurchase, speechSynthesis, documentPicker, systemSound, photoPicker, secureRandom, appGroupStorage, screenControl, audioPlayback, audioRecording, motion, mapsDirections, networkImage, spotlightIndex, backgroundTask, fileDownload, accessibility, appShortcuts, handoff, watchConnectivity, nfcRead, processInfo, videoPlayback, speechRecognition, mediaInfo, imageFilter, pdfGenerate, qrGenerate, camera (51 in total). A disabled bridge means functions using it stay native.
build.optimization string size (default) or speed. Overridden by –optimization.
build.stripDebugInfo bool Strip debug info from the emitted module to shrink it. Default true.
build.swiftui bool Lower SwiftUI View.body to WASM so views ship over the air. Default true (on if omitted); set false to keep views native. Env PATCH_SWIFTUI=0 overrides per-run.