CLI Reference

bext-lite is two binaries. bext-lite-build exports a PRISM app to a portable bundle; bext-lite serves, packs, checks, signs and mobile-packages that bundle.


bext-lite-build#

The AOT exporter. Compiles a PRISM site's routes, API routes, actions and islands into self-contained JS bundles plus a manifest.json, so the runtime can serve the site with no compiler on the device.

bext-lite-build <site_dir> [-o <out_dir>]
Flag Default Meaning
<site_dir> required The PRISM app source directory.
-o, --output <dir> <site_dir>/dist-lite Where to write the exported bundle.
-h, --help Print usage.

The bundles are byte-identical to what the full bext server produces, and render byte-identical HTML on QuickJS and V8. The exporter generates wrappers from source for every route, so the app does not need to have been served first.

"use client" React pages are compiled through a real React SSR build (via bun), emitting a shared react-base.js once and a small per-route bundle that requires it. String-builder pages need no bun and no React.

Environment variables

Variable Effect
BEXT_LITE_VENDOR_NODE_MODULES Path to a node_modules holding react / react-dom / scheduler, used for the React SSR build. Only needed if the app has "use client" pages and does not vendor its own React.
BEXT_SHARED_FRAMEWORK_DIR Path to the shared framework (@bext-stack/framework) for apps that import it.
BEXT_LITE_BUN Override the bun binary used for the React build (default: bun on PATH).
BEXT_LITE_ALLOW_DEGRADED_CLIENT 1/true/yes/on: if a React page cannot be compiled, ship a string-builder fallback tagged renderer:"degraded" instead of failing the export. Off by default (a broken React bundle fails the build).
# String-builder-only app: no bun, no env vars needed.
bext-lite-build ./app -o ./dist

# App with React pages, using the repo's vendored React.
BEXT_LITE_VENDOR_NODE_MODULES="$PWD/vendor/node_modules" \
  bext-lite-build ./app -o ./dist

bext-lite serve#

Serve an exported dist-lite/ over HTTP.

bext-lite serve <dist-lite-dir> [--port N] [--host H] \
  [--update-url URL --pubkey HEX [--data-dir D]]
Flag Default Meaning
<dist-lite-dir> required (unless packed) The exported bundle to serve.
-p, --port N 8787 TCP port.
--host H 127.0.0.1 Bind address.
--update-url URL Enable OTA content updates from this URL (see OTA). Requires --pubkey. file:// and http(s):// are both supported.
--pubkey HEX The publisher's ed25519 public key. Unsigned bundles are refused.
--data-dir D <dist>/.bext-lite-data Where app data (KV / SQLite / fs) and the OTA bundle store live. Kept outside the swappable bundle so an update never wipes user data.
-h, --help Print usage.
bext-lite serve ./dist --port 8080

Pages render on a warm-context pool (cold first hit, then about 1 ms warm). Static files under public/ are served directly. /api/* routes run their handler per call.

Note

--update-url requires --pubkey. Passing an update URL with no public key is a hard error - the runtime refuses to run an unsigned or tampered bundle.


bext-lite check#

The lite-eligibility gate. Renders every route once under QuickJS and runs a static policy scan, then reports whether the app is publishable. Exits non-zero if any route errors or the policy denies the bundle - wire it into CI.

bext-lite check <dist-lite-dir> [--data-dir D]
Flag Default Meaning
<dist-lite-dir> required The exported bundle to check.
--data-dir D Data dir for routes that touch on-device data during the render.

Output lists each route with its status, then a summary and the policy findings:

lite-eligibility check - site 'device-playground'
  page /                        200 html (17842 B)
  ...
N rendered, 0 engine error(s)
policy: 0 deny, 0 warn

=> PUBLISHABLE (lite-eligible + policy-clean)

The policy scan denies dynamic code execution (eval, Function) - QuickJS with no compiler on the device cannot run it, and it is a signing risk.


bext-lite pack#

Bundle an exported dist-lite/ into the runtime binary itself, producing one self-contained executable that serves the embedded app with no external files.

bext-lite pack <dist-lite-dir> --out <file>
Flag Default Meaning
<dist-lite-dir> required The exported bundle to embed.
-o, --out <file> required Output binary path.
bext-lite pack ./dist --out ./myapp
./myapp --port 8080          # runs the embedded app, no dist dir needed

A packed binary takes the same --port / --host flags as serve, and unpacks the embedded archive to a temp dir on launch.


OTA signing and updates#

A bext-lite app splits into the native runtime plus a dist-lite/ payload of interpreted JS. That lets you update the payload (routes, API, UI, logic) over the air with no binary change and no app-store review - the CodePush / Expo model. Every bundle is ed25519-signed; the runtime refuses anything that does not verify.

bext-lite keygen#

Generate a signing keypair. Keep the private key offline.

bext-lite keygen
# private_key=<hex>
# public_key=<hex>

Serve with the public key (--pubkey <public_key>); publish with the private key.

bext-lite publish#

Sign an exported bundle into a release you can host.

bext-lite publish <dist-lite-dir> --key <hex|file> --version <V> --out <dir> \
  [--url <archive-url>] [--notes <text>] [--min-runtime <ver>] [--skip-policy]
Flag Default Meaning
<dist-lite-dir> required The exported bundle to sign.
--key <hex|file> required The private key: a hex string, or a path to a file holding it (including keygen's private_key=... line).
-v, --version <V> required Content version for this release.
-o, --out <dir> required Output directory for release.json + bundle.bin.
--url <archive-url> The URL the bundle will be hosted at (embedded in release.json).
--notes <text> Human-readable release notes.
--min-runtime <ver> Lowest native runtime version this content needs. A runtime older than this refuses the update with a 409 so a content update never lands on a runtime missing bridges it depends on.
--skip-policy off Publish even if the static policy scan denies the bundle (operator escape hatch). By default a policy DENY blocks signing.
bext-lite publish ./dist --key ./signing.key --version 1.2.0 --out ./release

Serving with OTA on#

bext-lite serve ./dist --update-url https://cdn.example.com/app/release.json \
  --pubkey <public_key> --data-dir /var/lib/myapp

The runtime downloads the release, verifies the signature and version, extracts to a temp dir, then atomically swaps the live app behind a lock - in-flight requests finish on the old bundle, new ones get the new one, no restart. A failed or tampered download never becomes current.

Control plane#

Endpoint Access Purpose
GET /__bext-lite/version open Reports version (content) and runtime (native).
POST /__bext-lite/update loopback only Trigger an update check. Returns 409 { nativeUpdateRequired: true, ... } when the bundle's minRuntime exceeds the installed runtime.

Set BEXT_LITE_UPDATE_ON_LAUNCH=1 to check for updates at boot. A PRISM app's own UI can offer "Check for updates" by fetch('/__bext-lite/update', {method:'POST'}).


bext-lite mobile#

Package an exported app as a native iOS or Android app. The command family mirrors the desktop and server flows: it orchestrates the exporter plus the platform build tools rather than reimplementing a build.

bext-lite mobile init  [--platform ios|android|both] [--name <app>] [--id <bundle.id>] [--out <dir>] [--force]
bext-lite mobile run   <ios|android> --dist <dist-lite-dir> [--device <id>]
bext-lite mobile build <ios|android> --dist <dist-lite-dir> [--release]
bext-lite mobile dev   <ios|android> --dist <dist-lite-dir>
Warning

Device builds need a platform toolchain: a Mac with Xcode for iOS, or the Android SDK + NDK for Android. When the toolchain is absent, run / build / dev print exactly what is missing and how to install it, then exit non-zero - they never panic and never produce a broken artifact. So the same command is safe to run on a CI box or a Linux laptop; it simply stops at the guard.

mobile init#

Scaffold the native shell project(s). Non-destructive: it refuses to overwrite a non-empty existing shell without --force.

Flag Default Meaning
--platform ios|android|both both Which shell(s) to scaffold.
--name <app> BextApp App display name.
--id <bundle.id> dev.bext.app Bundle / application id.
-o, --out <dir> mobile Output directory for the scaffold.
--force off Overwrite a non-empty existing shell dir.
bext-lite mobile init --platform ios --name "My App" --id com.acme.myapp

mobile run#

Export, bundle dist-lite/ into the shell, build, and launch on a simulator or device.

Flag Default Meaning
<ios|android> required Target platform.
--dist <dist-lite-dir> required The exported app to bundle.
--device <id> Target a specific simulator / device.
bext-lite-build ./app -o ./dist
bext-lite mobile run ios --dist ./dist

mobile build#

Produce a store artifact: .ipa for iOS, .aab for Android.

Flag Default Meaning
<ios|android> required Target platform.
--dist <dist-lite-dir> required The exported app to bundle.
--release off Release (vs debug) build.
bext-lite mobile build android --dist ./dist --release

mobile dev#

Describe and run the live-reload loop: re-export on change, then hot-swap the bundle into the running app via Tier-1 content OTA. Content-only changes need no native rebuild.

Flag Default Meaning
<ios|android> required Target platform.
--dist <dist-lite-dir> required The exported app to hot-swap.
bext-lite mobile dev ios --dist ./dist

At launch the shell boots an embedded bext-lite, points a native WebView at http://127.0.0.1:<port>/, and answers the device/* SDK namespace (camera, biometrics, geolocation, secure store, haptics, share, notifications) with Swift / Kotlin. See the Introduction for how the same source runs headless and on-device.