@ubox-tools/cli
v0.3.1
Published
Author Ubox experiences locally and sync them to the Ubox platform
Readme
ubox CLI
Author Ubox experiences locally — by hand or with an AI agent — keep them
under git, and sync them to the Ubox platform (https://ubox-api.ubox.world)
with direct API calls.
npm install -g @ubox-tools/cli
ubox login
ubox init "My Experience"
# …author apps under apps/<name>/…
ubox pushPrefer not to install it globally? Run any command through npx instead — it fetches the latest published version on demand:
npx @ubox-tools/cli login
npx @ubox-tools/cli init "My Experience"
npx @ubox-tools/cli push- Node 18+ required.
- Every command works non-interactively (flags + env vars) for CI and AI agents; interactive prompts are sugar on top when a TTY is present.
Concepts
An experience is a git-managed folder containing one or more apps. Each app maps to a remote application on the platform; an application runs on a player (the platform's "ubox" entity) inside an xpace (a physical place).
The link between your local folder and the platform lives in ubox.json
(the manifest) at the project root. It records each app's remote
applicationId, the asset round-trip map, and any player/installation IDs —
commit it. Everything else about history is git's job: the CLI always
overwrites the application's current version in place; platform-side
versions are not used.
Project layout
My Experience/
├── ubox.json # manifest — links local apps to remote IDs (commit this)
├── CLAUDE.md # agent guidance (or GEMINI.md with --gemini)
├── docs/ # Ubox library docs, downloaded by `ubox init`
├── assets/ # assets shared by all apps
└── apps/
└── main/
├── index.html # → html
├── style.css # → css
├── data.js # → data_source
├── ubox.js # → ubox_events
├── logic.js # → business_logic
├── workflow.js # → workflow
├── parameters.json # app parameters (see below)
└── assets/ # assets private to this app (optional)The six file names are fixed — they map one-to-one onto the application's six
code fields. An app is any directory under apps/ containing at least one of
them (or a parameters.json).
Assets
Reference assets by natural relative path in your source:
<img src="assets/logo.png"> <!-- app-local asset -->
<img src="../../assets/shared.png"> <!-- shared root asset -->body { background: url(assets/bg.jpg); }On push, the CLI uploads the referenced files and rewrites the paths to the
platform's {resources:images/logo.png} tokens; on pull, tokens are
rewritten back to relative paths and the files are downloaded. The original
filename is the join key, so the round-trip is lossless. You never write
resource tokens by hand.
Parameters
parameters.json is an array of parameter objects:
[
{
"key": "inactivityTimeout",
"value": "150000",
"default_value": "150000",
"description": "Milliseconds of no movement before an idle user is kicked"
}
]All values are strings on the platform — the CLI coerces numbers/booleans
(150000 → "150000") and rejects anything else with a pointed error.
Reference a parameter in source as {parameter:key}; the platform resolves it
at load time, and ubox dev resolves it locally (value, falling back to
default_value when empty). A value may itself contain {player:<appName>} —
a cross-app link that ubox dev resolves to that app's local URL, including a
?api_key=<uuid> query mirroring the production player-link shape (e.g. for a
QR code pointing at a companion mobile app). After a sync, see
.ubox/reference.md for a generated table of every asset and parameter token.
index.html during authoring
With ubox dev you don't need any local <link>/<script> tags — the dev
server assembles the page exactly like the production player (css into a
<style> tag, the four JS files into a single <script> at the end, body
wrapped in <div id="myDiv">). You may still include local tags for the other
five files so the app also runs in a plain browser; both ubox dev and push
strip tags that reference exactly style.css, data.js, ubox.js,
logic.js, or workflow.js. CDN and other references are kept.
Authentication
ubox login # prompts for email + password, stores a token
ubox whoami # show identity + host in use
ubox logout # remove stored credentialsCredentials are stored in ~/.config/ubox/credentials.json (mode 600).
For CI / agents, environment variables override the stored file:
| Variable | Meaning |
|---|---|
| UBOX_TOKEN | bearer token (skips ubox login) |
| UBOX_HOST | API host override |
| UBOX_EMAIL / UBOX_PASSWORD | non-interactive ubox login |
A 401 clears stored credentials automatically (never an env token).
Commands
Global flags: --host <url>, --json (machine-readable output), --yes
(skip prompts; fail if input is missing), -v/--verbose.
Project
ubox init [name] [theme] # scaffold a new experience (downloads latest docs)
ubox init # no name = init the current directory, git-style
ubox init --update # refresh docs/ in an existing project
ubox init --gemini # write GEMINI.md instead of CLAUDE.mdSync
ubox link <appName> [id|name] # attach an existing remote application;
# a name searches with an interactive picker
ubox link --app <appName>=<id|name> # flag form, repeatable
ubox pull [appName…] # populate local files from the remote app(s)
ubox pull <appName> --app <id|name> # link and pull in one step
ubox status [appName…] # local vs remote diff + token reference
ubox push [appName…] # push source + assets + params (all apps if omitted)
ubox push source|assets|params [appName…] # a single phase
ubox push … --force # push even when everything matchespush is idempotent: assets are skipped when their bytes already match the
remote blob, parameters and source when they equal the remote values. A first
push of an unlinked app creates the remote application (named
"<project> <appName>"), or offers to adopt an existing one with that exact
name.
Local development
ubox dev [-p <port>] [--ws-port <n>] [--host <ip>] [--no-reload] [--no-log] [--log-filter <cmds>]Runs the experience locally, offline — no login needed:
- Production-shaped pages. Each app is served at
/apps/<name>/assembled exactly like the player builds it (css in<style>, the four JS files in one<script>in data → ubox → logic → workflow order, body in<div id="myDiv">) — with{parameter:…}and{player:…}tokens resolved fromparameters.json. - Message relay. A local WebSocket relay replaces the Ubox messaging
server; a
UboxClientshim is injected soubox-data-xpaceworks unchanged and apps talk to each other across tabs (or devices, with--host <lan-ip>). - Live reload. Edits under
apps/andassets/reload connected browsers (--no-reloadto disable). - Dashboard. The page at
/launches your apps and monitors the relay live: connected clients, shared variables (fromapp_online/var_changedmessages), and a filterable message log — click a message for its full payload. Relay messages also log to the terminal (--log-filter setVar,launchto narrow,--no-logto silence).
The default 127.0.0.1 binding is deliberate: browsers only grant
camera/microphone and crypto.subtle to secure contexts, and plain-HTTP LAN
origins don't qualify. Use --host <lan-ip> when you need other devices to
connect and don't need those APIs.
Applications
ubox app list [--search <term>] [--page <n>] [--limit <n>]
ubox app create <name> [--link <appName>] # --link records the id on a local app
ubox app update <appName> [--name <n>] # rename remote (default "<project> <appName>")Players
ubox player list
ubox player create --name <n> --xpace <name|id> [--app <appName>]
ubox player install [appName] [--player <id|name>]
ubox player activate [appName] [--player <id|name>]
ubox player public [appName] [--player <id|name>] [--on|--off] # default --onApp-scoped player commands target, in order: an explicit --player, the
player recorded for that app in ubox.json, or an interactive picker. Omit
appName on install/activate to browse instead: pick a player, then pick
from its available (install) or installed (activate) apps — handy outside a
project, or for apps that aren't part of the experience.
"Publishing" an experience means making its player public — applications
themselves are never made public.
Non-interactive / CI usage
export UBOX_TOKEN=… # or UBOX_EMAIL/UBOX_PASSWORD + `ubox login`
ubox push --yes --json # no prompts; JSON report on stdout--yesauto-accepts single-match confirmations (e.g. name adoption) and fails with a clear message where input would be required.--jsonprints a machine-readable result object for scripting.- Exit code is non-zero on failure.
Known platform limitations
The CLI detects these server-side bugs and degrades with explicit messages rather than failing opaquely:
- Freshly-created apps can't be installed yet. Applications created
through the API don't appear in a player's available list, which blocks
player installfor them. Apps created in the studio install normally. - Parameter sync fails while an app is installed on any player. Sync parameters before the first install; on re-deploys the CLI skips the step with a warning when the PUT is rejected (and skips it entirely when nothing changed).
- Same-second uploads can overwrite each other's file. Uploaded resource
blobs are stored at second-resolution timestamp paths.
push assetsworks around it (one file per request, spacing per extension) and verifies for collisions afterwards. - Server-side application search is unreliable for newly-created apps —
app listand name-based lookups are advisory; a numeric id always works. - Deletes are unavailable for applications, players, and xpaces (server errors) — create carefully.
Development
npm run build # tsc → dist/
npm run dev # tsc --watch
npm test # unit tests (transform + project + dev modules)Repo guide for contributors and agents: CLAUDE.md. Design
document: SPEC.md. Empirical API endpoint status:
api_working_reference.md.
