capgo-update-lite-cli
v0.6.0
Published
CLI for publishing Capacitor bundles to a capgo-update-lite OTA server.
Maintainers
Readme
capgo-update-lite-cli
CLI for publishing Capacitor OTA bundles to a capgo-update-lite server, plus admin commands to inspect apps, bundles, and client telemetry.
It runs a full set of preflight checks before touching the server: semver sanity, package.json alignment, capacitor.config match, server reachability, duplicate/downgrade detection, notifyAppReady() source scan, dist-dir validation, and zip size/integrity.
Install-free usage
pnpx capgo-update-lite-cli <subcommand> [...args]
# or
npx capgo-update-lite-cli <subcommand> [...args]The package name on npm is capgo-update-lite-cli. If you install it globally, the installed binary is capgo-update-lite.
Quick start
# 1. scaffold a config file in your Capacitor project root
pnpx capgo-update-lite-cli init
# 2. set the admin token once per shell
export CAPGO_ADMIN_TOKEN=<bearer-token>
# 3. register the app (one-time)
pnpx capgo-update-lite-cli apps add com.example.app --name "Example"
# 4. publish a bundle (version sourced from package.json)
pnpx capgo-update-lite-cli publishWith a capgo-update.config.json in place, the only thing the publish flow needs at runtime is the admin token (env). The bundle version is read from package.json; if it matches the active bundle on the channel, the CLI prompts for a patch/minor/major bump and writes the result back.
Shell completions
The CLI ships a complete <shell> subcommand that emits a tab-completion script for bash, zsh, fish, and powershell. The init wizard offers to install completions automatically; to install or re-install manually:
# bash
echo 'eval "$(capgo-update-lite complete bash)"' >> ~/.bashrc
# zsh
echo 'eval "$(capgo-update-lite complete zsh)"' >> ~/.zshrc
# fish
capgo-update-lite complete fish > ~/.config/fish/completions/capgo-update-lite.fish
# powershell
capgo-update-lite complete powershell >> $PROFILEWhat gets completed:
- Static enums —
--platforms,--platform,--state,--disable-auto-update. - App IDs —
--app/--app-id(and the positional onapps get/apps set-policy) hitGET /admin/apps. - Channels —
--channelenumerates distinct channels seen on the resolved app's bundles. - Bundle versions — the positional on
bundles promote <version>listsstate=activerows for the resolved(appId, channel)scope.
Dynamic completions need CAPGO_SERVER_URL + CAPGO_ADMIN_TOKEN available at TAB time (env or in capgo-update.config.json in the cwd). They use a 5-minute on-disk cache at ${XDG_CACHE_HOME:-~/.cache}/capgo-update-lite/; CLI write commands (apps add / set-policy, bundles delete / edit / promote, publish) invalidate the matching entries automatically. Each completion fetch has a 1.5 s timeout and falls back silently to "no completions" on any error — TAB never hangs the shell prompt.
Subcommands
Every subcommand inherits the three global flags (--server-url, --admin-token, --config). They also honour the matching CAPGO_* env vars and the keys in the config file.
publish
Zip a dist directory and upload it as a new bundle.
pnpx capgo-update-lite-cli publish [options]The bundle version defaults to package.json's version field. If it matches the active bundle on the target channel, the CLI prompts for a patch/minor/major bump and writes the new value back to package.json before publishing. Pass --bundle-version (or set CAPGO_VERSION) to override.
Options specific to publish:
| Flag | Purpose |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| --app-id <id> | Reverse-domain app identifier (or set in config / CAPGO_APP_ID) |
| --bundle-version <semver> | Bundle version (defaults to package.json version) |
| --dist-dir <path> | Built web bundle directory (or set in config / CAPGO_DIST_DIR) |
| -c, --channel <name> | Release channel (default production) |
| -p, --platforms <list> | Comma-separated list of ios,android,electron |
| --link <url> | Release notes / changelog URL |
| --comment <text> | Operator-authored note |
| --activate / --no-activate | Activate on commit (default: activate) |
| --dry-run | Run preflight and build the zip, skip all server writes |
| --skip-preflight | Bypass preflight checks (escape hatch) |
| --no-code-check | Skip the notifyAppReady() source scan |
| --version-exists-ok | Exit 0 if the version is already published (CI-friendly) |
| --package-json <path> | Override auto-detection of package.json |
| --capacitor-config <path> | Override auto-detection of capacitor.config.(ts\|js\|json) |
| --min-android-build <semver> | Minimum native Android versionName required by this bundle |
| --min-ios-build <semver> | Minimum native iOS CFBundleShortVersionString required by this bundle |
| --auto-min-update-build | Inherit min builds from the previous bundle when native deps are unchanged; bump to detected native versions when they changed |
| --android-project <path> | Path to Android project root (default ./android) |
| --ios-project <path> | Path to iOS project root (default ./ios) |
init
Interactive wizard that scaffolds capgo-update.config.json in the current directory.
pnpx capgo-update-lite-cli init [--force] [--path <path>] \
[--app-id <id>] [--server-url <url>] [--channel <name>] [--dist-dir <path>] \
[--no-validate]Prompts (each is skipped when the matching value is already provided via flag, env, or an existing config file):
- App ID — reverse-domain identifier (
com.example.app). - Server URL — public URL of your deployed worker.
- Channel — defaults to
production. - Dist directory — auto-detects
./build,./dist,./www,./out,./publicin the cwd; each option's hint says whether it containsindex.html. Falls back to a free-form path prompt if no candidates exist.
Then verifies the configuration against the server:
- Asks for an admin token at a hidden password prompt. Leave empty to fall back to a public health check only —
GET /health, no admin endpoints touched. - With a token: pings
/health, thenGET /admin/appswith the bearer token to verify auth. If yourappIdisn't registered yet, the wizard offers to register it inline (same path asapps add) — confirm + display-name prompt +POST /admin/apps. The display-name default is derived from the appId's last segment (com.example.member_app→Member App), editable. - The token is used once and discarded; it is never written to
capgo-update.config.json. IfCAPGO_ADMIN_TOKEN(or--admin-token) is already set, the wizard reuses that and skips the password prompt. - Pass
--no-validateto skip the verification step entirely (e.g. when scripting init in a context with no network access).
The template includes appId, serverUrl, channel, distDir, platforms (default ["ios", "android"]), and autoMinUpdateBuild: true. The admin token is intentionally absent — keep it in CAPGO_ADMIN_TOKEN.
apps list
List apps registered on the server.
pnpx capgo-update-lite-cli apps list [--json]apps add
Register a new app.
pnpx capgo-update-lite-cli apps add <app-id> --name <display-name>apps set-policy
Update an app's compatibility policy. Only the flags you pass are changed; omit a flag to leave that field untouched.
pnpx capgo-update-lite-cli apps set-policy <app-id> \
[--name <display-name>] \
[--disable-auto-update none|patch|minor|major] \
[--disable-auto-update-under-native | --no-disable-auto-update-under-native] \
[--min-plugin-version <semver>|null] \
[--fail-min-devices <n>|null] \
[--fail-warn-rate <0..1>|null] \
[--fail-risk-rate <0..1>|null] \
[--fail-rate-threshold <0..1>|null]--min-plugin-version null clears the floor. Without --no-disable-auto-update-under-native the under-native guard stays at whatever value the server holds. The --fail-* flags override the broken-bundle severity ladder per-app — pass null to clear an override and fall back to env / server default. Order is enforced server-side: warn ≤ risk ≤ disable. Set --fail-rate-threshold 0 (or --fail-min-devices 0) to disable auto-disable for this app while keeping severity classification for the dashboard / bundles health.
bundles list
List bundles, optionally filtered.
pnpx capgo-update-lite-cli bundles list [--app <id>] [-c <channel>] [-s <state>] [--active | --no-active] [--json]bundles delete
Delete a bundle. Soft-delete by default (row stays, marked deleted). Pass --purge to also remove the R2 object. Purge is unrecoverable.
pnpx capgo-update-lite-cli bundles delete <bundle-id> [--purge]bundles promote
Activate an existing bundle for an (app, channel) pair, with no re-upload.
pnpx capgo-update-lite-cli bundles promote <version> --app <id> [-c <channel>]Errors if zero or multiple bundles match the version on the channel.
bundles health
Show the broken-bundle severity ladder. Without an app-id, summarises every app that has at least one non-healthy bundle (most-urgent first). With an app-id, lists each bundle of that app classified into healthy | noisy | warning | at_risk | auto_disabled | manually_disabled, alongside the resolved per-app thresholds.
pnpx capgo-update-lite-cli bundles health [app-id] [--json]bundles reactivate
Restore an auto-disabled (or manually-disabled) bundle: flips state=active + active=true, atomically deactivates siblings on the same channel, and stamps blacklist_reset_at = now() so previously-failed devices get another chance via the /updates blacklist gate.
pnpx capgo-update-lite-cli bundles reactivate <bundle-id>stats
List recent client telemetry events.
pnpx capgo-update-lite-cli stats [--app <id>] [--action <name>] [--since <iso>] [--until <iso>] [--limit <n>] [--offset <n>] [--json]Actions include update, set, and other events the plugin reports. --limit accepts 1..1000 (default 100).
probe
Send a synthetic POST /updates to smoke-test the server end-to-end from the client perspective.
pnpx capgo-update-lite-cli probe [--app <id>] [--platform ios|android|electron] [--current-version <semver>] [--device-id <uuid>] [-c <channel>]Defaults to platform ios and current version 0.0.0 (which forces an update-available response if a bundle exists). When --app is absent, the command falls back to the config or reads appId out of capacitor.config.*.
Configuration layers
Every value resolves via four routes. Precedence, highest wins:
- CLI flag
- Environment variable (
CAPGO_*) - JSON config file (
./capgo-update.config.jsonauto-loaded, or--config <path>) - Built-in defaults (
channel=production,activate=true,codeCheck=true)
For publish specifically, when --bundle-version / CAPGO_VERSION / version are all absent, the bundle version is sourced from package.json's version field. If that version matches the bundle currently active on the channel, the CLI prompts (Cancel / Increase patch version / Increase minor version / Increase major version) and writes the bumped value back to package.json before publishing. In non-interactive contexts (no TTY, e.g. CI), the prompt is replaced by a hard fail — bump package.json ahead of time or pass --bundle-version / --version-exists-ok explicitly.
The admin token follows the same three-route rule. Prefer the environment variable:
--admin-tokenon the command line is visible inpsoutput."adminToken"in the config file is fine if the file is gitignored.CAPGO_ADMIN_TOKENis the safest default.
Config file example
Drop capgo-update.config.json in the directory where you run the CLI (typically your mobile app's project root). The CLI auto-loads it; pass --config <path> to point at a different file.
{
"appId": "com.example.app",
"serverUrl": "https://ota.example.com",
"distDir": "./build",
"channel": "production",
"platforms": ["ios", "android"],
"activate": true
}With the config file present and CAPGO_ADMIN_TOKEN exported, publish shortens to:
pnpx capgo-update-lite-cli publishThe bundle version is sourced from package.json automatically; the bump prompt handles the case where it matches the active bundle.
Full option reference
Rows marked "publish-only" are only read by the publish subcommand. Everything else is global.
| CLI flag | Env var | Config key | Scope | Notes |
| ------------------------------ | ----------------------------- | -------------------- | ------------ | -------------------------------------------------------------------------------------------------------------- |
| --server-url <url> | CAPGO_SERVER_URL | serverUrl | global | OTA server base URL |
| --admin-token <token> | CAPGO_ADMIN_TOKEN | adminToken | global | Bearer token for /admin/* |
| --config <path> | CAPGO_CONFIG | n/a | global | JSON config path |
| --app-id <id> | CAPGO_APP_ID | appId | publish-only | Reverse-domain app identifier |
| --bundle-version <semver> | CAPGO_VERSION | version | publish-only | Bundle version. Defaults to package.json version. Named to avoid commander's reserved root --version flag. |
| --dist-dir <path> | CAPGO_DIST_DIR | distDir | publish-only | Built web bundle directory (must contain index.html) |
| -c, --channel <name> | CAPGO_CHANNEL | channel | most | Default production |
| -p, --platforms <list> | CAPGO_PLATFORMS | platforms | publish-only | ios,android,electron (comma-separated on CLI and env) |
| --link <url> | CAPGO_LINK | link | publish-only | Release notes / changelog URL |
| --comment <text> | CAPGO_COMMENT | comment | publish-only | Operator-authored note |
| --activate / --no-activate | CAPGO_ACTIVATE | activate | publish-only | Default true; env accepts true/false/1/0/yes/no/on/off |
| --dry-run | CAPGO_DRY_RUN | dryRun | publish-only | Run preflight and zip, skip all server writes |
| --skip-preflight | CAPGO_SKIP_PREFLIGHT | skipPreflight | publish-only | Bypass preflight checks |
| --no-code-check | n/a | codeCheck | publish-only | Skip notifyAppReady() source scan (config key defaults to true) |
| --version-exists-ok | n/a | n/a | publish-only | Exit 0 if the version is already published |
| --package-json <path> | CAPGO_PACKAGE_JSON | packageJson | publish-only | Override auto-detection of package.json |
| --capacitor-config <path> | CAPGO_CAPACITOR_CONFIG | capacitorConfig | publish-only | Override auto-detection of capacitor.config.(ts\|js\|json) |
| --min-android-build <semver> | CAPGO_MIN_ANDROID_BUILD | minAndroidBuild | publish-only | Minimum native Android versionName required by this bundle |
| --min-ios-build <semver> | CAPGO_MIN_IOS_BUILD | minIosBuild | publish-only | Minimum native iOS CFBundleShortVersionString required by this bundle |
| --auto-min-update-build | CAPGO_AUTO_MIN_UPDATE_BUILD | autoMinUpdateBuild | publish-only | Inherit min builds from prev bundle if native deps unchanged; bump otherwise |
| --android-project <path> | CAPGO_ANDROID_PROJECT | androidProject | publish-only | Native Android project root (default ./android) |
| --ios-project <path> | CAPGO_IOS_PROJECT | iosProject | publish-only | Native iOS project root (default ./ios) |
| -V, --version | n/a | n/a | global | Print CLI version |
| -h, --help | n/a | n/a | global | Show help (supported on every subcommand) |
Preflight checks (publish only)
All of these can be bypassed with --skip-preflight. Checks that rely on local files auto-detect from the current working directory and silently skip if the file is absent.
- Version autoresolve. When
--bundle-versionis absent, sources the version frompackage.json. Compares against the active bundle on the channel: newer ⇒ continue; older ⇒ fail (downgrade); equal + sourced ⇒ prompt forpatch/minor/majorbump and write back topackage.json; equal + explicit ⇒ fail unless--version-exists-ok. - Version validity. The resolved version must parse as
MAJOR[.MINOR[.PATCH]](Apple'sCFBundleShortVersionStringrules —110,110.0,1.2.3are all accepted; missing segments default to0). package.jsonalignment. Bundle version must be>=thepackage.jsonversion. Major bumps emit a warning so you can confirm the native shell supports the new bundle.capacitor.config.(ts|js|json)alignment. ItsappIdmust match the configuredappId. TheCapacitorUpdater.updateUrl, if set, should start with the server URL (warning only).- Native build resolution. Reads
versionNamefromandroid/app/build.gradle(.kts)to defaultmin_android_build. For iOS, walks Info.plist →project.pbxproj→.xcconfig→xcodebuild(macOS only) to resolveCFBundleShortVersionString, even when it's a$(MARKETING_VERSION)placeholder. Explicit--min-android-build/--min-ios-buildoverride. Fails if neither explicit value nor a native project is available, with a layer-by-layer trace of what was tried. - Server ping.
GET /with a 5s timeout. - App registered.
GET /admin/appsmust include the configuredappId(requires an admin token). - Dist validation.
distDir/index.htmlmust exist. Cloudflare adapter artifacts (_worker.js,_routes.json) must not, which catches accidental web builds. notifyAppReady()source scan. At least one.jsfile underdistDirmust referencenotifyAppReady. Without that call the plugin rolls back after 10 seconds. Disable with--no-code-checkif your build minifies the symbol away.- Zip integrity. The generated ZIP is re-parsed to verify structural validity.
- Size bounds. Less than 1 KB fails (likely an empty build). Over 50 MB warns (slow on cellular). Over 500 MB fails.
Compatibility guards
Every uploaded bundle carries three pieces of native-compatibility metadata. The server uses them to decide whether a given device should receive the bundle:
min_android_build: minimum AndroidversionNamethe native shell must report.min_ios_build: minimum iOSCFBundleShortVersionStringthe native shell must report.native_packages: fingerprint of@capacitor/*,@capacitor-community/*,@ionic-enterprise/*,cordova-plugin-*, andcapacitor-*/capacitor-plugin-*deps frompackage.json, with resolved versions at publish time.
If the device's version_build (what the plugin reports from the native project) is lower than the matching min_*_build, /updates returns below_min_native_build and the plugin keeps its current bundle.
How --auto-min-update-build decides
With --auto-min-update-build (or autoMinUpdateBuild: true in config), the CLI queries the previously-active bundle on (appId, channel) and compares its native_packages fingerprint with the current one:
- Fingerprint unchanged: inherit
min_android_build/min_ios_buildfrom the previous bundle. No native shell reship needed. - Fingerprint changed (added, removed, or version-bumped native dep): set the min builds to the detected native versions (
android/app/build.gradleandios/App/App/Info.plist) and print which packages changed. - No previous bundle: use the detected native versions.
Per-app policies
The server also enforces three per-app policies stored on the apps row. Configure them via PATCH /admin/apps/<app-id>:
disable_auto_update:none(default),patch,minor, ormajor. Blocks auto-updates at or above the configured class.disable_auto_update_under_native: boolean (defaulttrue). Refuses to serve a bundle whose semver is lower than the device's nativeversion_build.min_plugin_version: semver string or null. Devices running an older@capgo/capacitor-updaterplugin are rejected withunsupported_plugin_version.
Publish flow
- Resolve config from CLI, env, and file.
- Run preflight.
- Zip
<dist-dir>in memory with DEFLATE compression (cross-platform, nozipbinary required). POST /admin/bundles/init, receive a presigned R2 URL.PUTthe zip directly to R2.POST /admin/bundles/commitwith{ bundle_id, checksum, activate }.
On --dry-run, steps 4 through 6 are printed as a preview and skipped.
Exit codes
0on success, or when--version-exists-okshort-circuits a duplicate.1on any error: missing config, preflight failure, non-2xx response, IO failure, and so on.
