CLI

Learn when to use the Otalan CLI, what workflows it covers, and how it complements the dashboard instead of replacing the platform model.

@otalan/cli is the release automation companion for the Otalan control-plane workflow.

The dashboard is the right place to learn the model, inspect state, and operate the platform. The CLI is the right place to make packaging and publishing repeatable from a repository or CI pipeline.

Package

# Install the Otalan CLI globally with Bun.
bun add -g @otalan/cli
# Verify that the otalan command is available.
otalan version

What the CLI is for

Use the CLI when you want:

  • repeatable local release commands
  • CI-driven packaging and publish steps
  • repository-aware export and bundle preparation
  • a release workflow that can fail fast and clearly in automation

The CLI is especially useful once your team has already validated the tuple model and no longer wants release engineers to build ZIP files by hand.

What the CLI does

At a high level, the CLI covers the release side of the workflow:

  • authenticate against Otalan using an OTA Publish Key
  • link the repository to an Otalan app
  • prepare a bundle artifact
  • publish the release through the same validated publish flow the dashboard uses
  • inspect bundle history and active state
  • trigger rollback operations
  • pause or resume delivery of the currently active bundle

Rollback targets must already be registered in Otalan. If a version is not listed in bundle history for the tuple, the CLI cannot roll back to it.

Release automation clients can call GET /v1/releases/channels, optionally with appId, to discover existing project channels and the active apps using each one.

The critical design point is that the CLI does not bypass the platform. It still publishes through the validated Otalan release flow.

Supported targets and runtime

The package ships a Bun entrypoint, not standalone native binaries. Bun >= 1.3.11 must be installed wherever the command runs.

Current package support is:

  • macOS and Linux with Bun installed
  • Windows is experimental until the release flow is validated there
  • Capacitor 7 and 8 with --target capacitor
  • Expo SDK 54, 55, and 56 with --target expo

Release environment variables

Use CI secrets for publish credentials. Keep app runtime variables separate from release automation variables.

Typical CI variables:

VariableUsed byPurpose
OTALAN_API_URLotalan login or CI scriptPublic Otalan API URL, such as https://api.otalan.com
OTALAN_API_KEYotalan login or CI scriptOTA Publish Key created in the dashboard
OTALAN_APP_IDotalan init, otalan bundle, otalan publishRegistered mobile app ID, such as com.example.app
OTALAN_PLATFORMCI scriptios or android release lane
OTALAN_CHANNELotalan publishRelease channel, such as production, beta, or internal
OTALAN_RUNTIME_VERSIONotalan bundle and publish metadataNative compatibility line for the installed app

Do not use the OTA App Key for CLI publishing. Do not put OTALAN_API_KEY in frontend, mobile, Expo public, or Vite public env variables.

The variable is named OTALAN_API_KEY because the CLI sends it as API auth, but the value must be an OTA Publish Key. OTA Publish Keys use the otalan_ci_ prefix. OTA App Keys use the otalan_ota_ prefix and belong only in the installed app runtime.

Command map

The package-level command surface currently includes:

  • otalan help and otalan version
  • otalan keygen for local OTA Publish Key or OTA App Key material generation
  • otalan login for saving the OTA Publish Key and API URL
  • otalan doctor for checking API connectivity and resolved project context
  • otalan init for writing otalan.config.json
  • otalan bundle for creating .otalan/bundle/bundle-<bundle-id>.zip and .otalan/bundle/manifest.json
  • otalan publish for upload, validation polling, and activation
  • otalan channels for listing release channels in the authenticated project
  • otalan bundles for listing published bundles for a tuple
  • otalan status for showing the active bundle
  • otalan rollback for reactivating an older bundle in the same tuple
  • otalan pause and otalan resume for pausing or resuming the active bundle rollout

The .otalan/ folder is generated output. Add it to .gitignore; otalan publish reads the generated ZIP and manifest from the current local or CI workspace.

Capacitor workflow

For Capacitor apps, the CLI typically packages the built web output already produced by your application.

That means:

  • your normal frontend build still runs first
  • the CLI packages the generated files
  • without --input-dir, the CLI checks dist/ first and then www/
  • --input-dir <path> points the CLI at a custom web output folder
  • the CLI uploads the archive through the Otalan publish flow
  • the CLI waits for validation to complete

The CLI makes the packaging step consistent, but it does not try to replace your application build command.

Typical Capacitor release loop

For a Capacitor project, the local release flow is usually:

Run setup before the first local publish:

# Save your OTA Publish Key and API URL.
otalan login --api-key "$OTALAN_API_KEY" --api-url "${OTALAN_API_URL:-https://api.otalan.com}"

# Link this repository to the Otalan app.
otalan init --app-id "$OTALAN_APP_ID"

Run for each new web release:

# Build the Capacitor web assets.
bun run build
# Package the generated assets for one platform/runtime line.
otalan bundle --target capacitor --platform "$OTALAN_PLATFORM" --runtime-version "$OTALAN_RUNTIME_VERSION"
# Upload, validate, and activate the bundle on one channel.
otalan publish --channel "$OTALAN_CHANNEL"

Use your project's actual build command if it is not bun run build.

otalan bundle packages the generated web output linked by otalan init. otalan publish uploads the ZIP and waits for Otalan to validate and activate it.

Expo workflow

Expo is where the CLI becomes especially valuable.

For Expo apps using expo-updates, the CLI can run the export flow, package the exported assets, and generate the Otalan manifest required by the publish request.

For Expo, otalan bundle --target expo runs the Expo export and writes the generated Otalan manifest expected by publish.

The published ZIP is still retained as the release archive. Otalan also extracts and stores the Expo launch asset and listed assets, so dashboard storage for Expo releases is archive storage plus Expo asset storage.

That makes it a better long-term option than a purely manual dashboard flow when your team wants:

  • repeatable exports
  • CI-friendly release automation
  • less room for human error in packaging

Otalan does not block older runtime versions by version number. Other runtimes and older Expo or Capacitor versions might work or might not, but they are not supported for the moment.

Publish options that matter in CI

otalan publish reads the current bundle output, uploads the ZIP, and polls until validation reaches ready or failed.

Use a current CLI version for CI publishing. otalan publish applies the upload details returned by Otalan automatically, so oversized ZIP uploads can be rejected early.

Common release options:

  • --channel production
  • --rollout-percent 25
  • --optional
  • --release-notes "Fix startup crash"
  • --output-dir <path> when publishing from a non-default bundle folder
  • --bundle-id 1.0.5 when generating a customer-facing release ID
  • --bundle-from-package when the bundle ID should come from package.json

Release metadata is bounded before Otalan records release state. Keep app IDs and bundle IDs under 255 characters, channels and runtime versions under 128 characters, release notes under 4,000 characters, file names under 255 characters, content types under 128 characters, and generated Expo manifests under 256 KB.

CI example

Use one job per app/platform/runtime lane. Keep the publish key in the CI secret store.

bun install --frozen-lockfile
bun add -g @otalan/cli
otalan login --api-url "$OTALAN_API_URL" --api-key "$OTALAN_API_KEY"
otalan init --app-id "$OTALAN_APP_ID"
bun run build
otalan bundle \
  --target capacitor \
  --platform "$OTALAN_PLATFORM" \
  --runtime-version "$OTALAN_RUNTIME_VERSION"
otalan publish \
  --channel "$OTALAN_CHANNEL" \
  --release-notes "$GIT_COMMIT"

For Expo, use --target expo and run separate jobs for iOS and Android if their generated exports differ.

Rollback flow

Rollback is intentionally narrow:

  1. List the tuple with otalan bundles or the dashboard Bundles page.
  2. Choose a previous bundle that is already registered for the same appId, platform, channel, and runtimeVersion.
  3. Run otalan rollback or use the dashboard rollback action.
  4. Re-check the app and confirm it receives the restored bundle.

Rollback does not change the native binary. If the problem requires a native plugin, permission, entitlement, or runtime-version change, ship a store update and publish future OTA bundles on a new runtimeVersion.

Common failure modes

  • the CI job uses an OTA App Key instead of an OTA Publish Key
  • OTALAN_APP_ID does not match a registered app in the selected project
  • the selected app is archived or inactive
  • platform, channel, or runtimeVersion differs from the installed app
  • Capacitor output was not built before otalan bundle
  • Expo publish omitted the generated .otalan/bundle/manifest.json
  • Expo config uploaded by the generated manifest contains fields that should have stayed out of release metadata
  • CI uses stale or custom upload tooling instead of the current otalan publish flow
  • release notes or generated manifest metadata exceed API limits
  • a rollback target is not present in bundle history for the exact tuple

Dashboard versus CLI

Use the dashboard when you need:

  • a first publish path
  • operational visibility
  • bundle history inspection
  • manual rollout changes
  • manual rollback
  • workspace, billing, and credential administration

Manual dashboard publishing still uses a CLI-generated artifact. Run otalan bundle in the app repository, then upload .otalan/bundle/bundle-<bundle-id>.zip on the Publish page. For Expo releases, use .otalan/bundle/manifest.json in the Expo manifest field too.

Use the CLI when you need:

  • automation
  • repeatability
  • repository-aware packaging
  • CI integration

The two tools are complementary. Most teams should start with the dashboard, then move the publish step into the CLI when the process stabilizes.

Credentials and keys

The CLI should use an OTA Publish Key, not an OTA App Key.

Create that OTA Publish Key in the dashboard, either by letting Otalan generate it or by registering a locally generated suffix for the OTA Publish Key type.

That separation matters because:

  • OTA App Keys belong inside supported app runtime flows
  • OTA Publish Keys belong in the CLI and release automation
  • operator actions remain easier to reason about when credentials are scoped correctly

Exit behavior and release confidence

One of the most useful properties of the CLI is that publish automation can treat the final exit result as meaningful.

In the intended workflow:

  • success means validation completed and the release reached a ready state
  • failure means validation or activation did not complete successfully

That gives CI a reliable contract instead of a fire-and-forget upload.

Documentation boundary

These docs explain how the CLI fits into the product model. The dedicated package documentation should still own:

  • installation steps
  • exact command syntax
  • repository configuration
  • CI examples
  • troubleshooting for package-level behavior

That split keeps the control-plane docs focused and prevents duplication of package-specific reference material.