brivio
Brivio CLI
Command-line tool for Brivio — let a coding agent (or any script) report progress to your iPhone as updatable notifications and Live Activities (Lock Screen + Dynamic Island).
brivio speaks the AgentPacket JSON envelope defined in
PACKET_SCHEMA.md. It POSTs that envelope to the
Brivio iOS app over your LAN (http://<iphone-ip>:8473/v1/packet), or
to a relay over HTTPS when you're off the local network.
- Zero dependencies. Pure Node.js built-ins. No build step.
- Node 18+.
Install
npm install -g brivio@latest
It's a single executable script: no compile step and no runtime dependencies.
Use brivio in docs, scripts, and local development.
For repo development, run npm link inside cli/ or call node cli/bin/brivio.js directly.
Codex usage
brivio codex usage --json
brivio codex live --interval 60
codex usage fetches Codex 5-hour and weekly usage from codex app-server and
writes ~/.brivio/codex-usage.json. The macOS app reads that file for a
local usage panel.
Connect
Use the same flow shown in the iPhone onboarding:
brivio qr
Scan the generated QR image in Brivio, or choose Pair with Code and
enter the 10-character code printed under it. First run creates relay
credentials automatically and stores them at ~/.brivio/config.json.
Delivery defaults to auto: saved LAN host, then Bonjour-discovered LAN
devices, then relay only if local delivery fails.
brivio config set --host 192.168.1.42 --port 8473
brivio config show
brivio up show QR + short pairing code
brivio status show saved/discovered devices
brivio reset-pairing delete saved iPhone connection
brivio qr show QR image + short pairing code
brivio health # confirm the phone answers
Config is stored at ~/.brivio/config.json (mode 0600):
{
"host": "192.168.1.42",
"port": 8473,
"relayUrl": "wss://relay.example/…",
"token": "…",
"via": "auto",
"devices": [{ "id": "iphone-17e", "name": "iPhone 17e" }]
}
Off the LAN — relay
When the agent runs somewhere that can't reach the phone directly, configure a
relay. In auto mode the CLI still tries local network first, then POSTs to
<relay>/v1/packet?token=<token> over HTTPS.
brivio config set --relay wss://relay.example.com --token YOUR_TOKEN
Precedence
Every setting can come from (highest wins) flags → env vars → config file → defaults:
| Setting | Flag | Env var | Default |
|---|---|---|---|
| host | --host |
BRIVIO_HOST |
— |
| port | --port |
BRIVIO_PORT |
8473 |
| relay | --relay |
BRIVIO_RELAY |
— |
| token | --token |
BRIVIO_TOKEN |
— |
| via | --via |
BRIVIO_VIA |
auto |
--via auto (default) prefers the LAN and falls back to the relay if the
phone is unreachable. Force one with --via lan or --via relay.
Global flags work before or after the command:
brivio --host 192.168.1.42 ping
brivio ping --host 192.168.1.42
Add --json to any command for machine-readable output. Every command exits
nonzero on failure.
Commands
brivio config set --host <ip> [--port 8473] [--relay <wss-url>] [--token <t>] [--via auto|lan|relay]
brivio config show
brivio pair <aa1.code> save iPhone pairing code
brivio devices show paired/discovered iPhones in a table, including alarm status
brivio devices rename <id> <name> rename saved iPhone connection
brivio devices remove delete saved iPhone connection
brivio health GET /health on the LAN server
brivio ping [--to <id> | --all] connectivity check (no UI)
brivio blockage --project <id> --body <blocker> [--request <next step>] [--destination-url <url>]
brivio notify --project <id> [--name <n>] --title <t> [--destination-url <url>] [--to <id> | --all]
brivio notify update --id <id> --project <id> [--progress] [--body] [--status] [--image] [--to <id> | --all]
brivio notify dismiss --id <id> --project <id> [--to <id> | --all]
brivio live start [--project <id>] [--name <n>] --title <t> [...] → prints the activity id
brivio live update --id <id> --project <id> [...] (also: --id --id b, or --project p, --to <id> | --all)
brivio live end --id <id> --project <id> [--dismiss immediate|default|after:<sec>] [--to <id> | --all]
brivio alarm set --title <t> (--at <iso-date> | --in <duration>) prints alarm UUID
brivio alarm cancel --id <uuid>
brivio artifact <file.md|file.html> [--artifact-id id] [--title …] [--type plan] [--ttl-days 7] [--passcode 1234] [--no-notify] publish a shareable artifact
brivio artifacts [--json] [--to <id> | --all] list active/archived/expired/deleted artifacts
brivio codex usage [--cached] fetch/store or show Codex usage
brivio codex live [--once] [--interval 120] stream usage to Live Activity
brivio list [--json] [--local] list ongoing Live Activities (aliases: ps, activities)
brivio logs list active Live Activities
brivio discover mDNS scan for servers on the LAN (optional)
Run brivio <command> --help for the full flag list of any command.
Codex usage
brivio codex usage reads Codex 5-hour and 7-day windows through the
Codex app-server and stores the latest sample at
~/.brivio/codex-usage.json.
brivio codex usage --json
brivio codex live --interval 120
Notifications
Blockage alerts
When an agent is blocked on user input, use blockage instead of a vague ping.
It sends one updatable sound notification with the exact blocker and optional
next step. Tapping it opens --destination-url, or the current Codex/ChatGPT URL
from env (BRIVIO_DESTINATION_URL, CODEX_TASK_URL,
CODEX_THREAD_URL, etc.) when available.
brivio blockage \
--project build \
--body "Missing OPENAI_API_KEY in .env" \
--request "Add the key, then reply done"
Reuse --id to update the same blocker instead of stacking alerts. Use
--dry-run --json to inspect the packet without sending it.
An updatable banner. Reuse the same --id to update it in place — no
new-banner spam. If you omit --id on a fresh notify, one is generated and
printed/returned so you can update it later.
# present a notification with a progress bar
brivio notify \
--project deploy --name "Deploy" \
--title "Deploying to prod" \
--body "Uploading build…" \
--progress 0.15 \
--destination-url "https://example.com/report.html" \
--id deploy-42 \
--symbol "arrow.up.circle.fill" --tint "#0A84FF" --sound
# update it in place
brivio notify update --id deploy-42 --project deploy \
--progress 0.8 --body "Running migrations…"
# remove it
brivio notify dismiss --id deploy-42 --project deploy
--image accepts an https://… URL, a data:image/png;base64,… data URL, or
raw base64 — the phone downloads/decodes it and attaches it.
--destination-url opens when the notification is tapped.
Notification progress example (one-liner loop)
ID=build-$(date +%s)
brivio notify --project ci --title "CI build" --id "$ID" --progress 0.0 --body "Queued"
for p in 0.25 0.5 0.75 1.0; do
brivio notify update --id "$ID" --project ci --progress "$p" --body "Building… ${p}"
sleep 2
done
brivio notify update --id "$ID" --project ci --progress 1.0 --body "Done ✅" --sound
Alarms
brivio alarm set schedules an actual iOS AlarmKit alarm. Use it when a
user asks to be reminded, called, alerted when work finishes, or alerted at a
milestone. Scheduling requires alarm access enabled in Brivio
onboarding/settings and starts a companion Live Activity shortly before the
alarm. Scheduled alarms appear in the iOS Alerts tab, where the user can
cancel them; CLI cancellation uses the same alarm id.
brivio alarm set --title "Check build" --body "Tests should be done" --in 10m --ringtone retroBell --alarm-lead-minutes 5
brivio alarm set --title "Review artifact" --in 10m --symbol doc.richtext.fill --tint "#34C759" --image "$IMG" --destination-url "$URL" --alarm-button-title "Open artifact"
brivio alarm set --title "Stand up" --at 2026-06-30T15:00:00Z
brivio alarm cancel --id <uuid>
--in accepts 30s, 10m, 2h, 1d. --ringtone accepts
systemDefault, retroBell, digitalPulse, cosmicAlert, rhythmAlarm,
or bubbleChime. --symbol, --tint, --image, --destination-url, and
--alarm-button-title brand the app-owned Alerts row and companion Live
Activity; native AlarmKit currently uses the title, tint, and button labels.
Actual AlarmKit alarms are created on
device, so LAN and connected relay delivery can create them; the APNs worker
cannot create a local alarm unless the app receives and handles the packet.
brivio devices prints ALARMS, AGENT, and AUTH columns. ALARMS
means this Mac can schedule alarms on that iPhone, AGENT is the phone-wide
agent-alarm switch, and AUTH is iOS alarm permission. Enable per-device alarm
access in the iPhone app under Settings → Connections.
Artifacts
brivio artifact publishes a local Markdown or HTML file through the
relay Worker and prints a public, unguessable URL (<relay>/r/<id>) you can
open on any device. .md files are rendered to a standalone, dark-mode-friendly
HTML page; .html files are uploaded verbatim.
# publish a Markdown report (rendered to HTML); the link expires in 7 days
brivio artifact build-summary.md
# stable artifact id; each upload to same id increments version
brivio artifact plan.html --artifact-id q3-migration-plan
# custom title, 14-day expiry, and a 4-digit passcode gate
brivio artifact perf.html --title "Perf regression deep-dive" \
--type audit --ttl-days 14 --passcode 4821
# machine-readable output, and skip the phone notification
brivio artifact notes.md --json --no-notify
# list artifacts created by this relay token
brivio artifacts
- The artifact is stored by the relay Worker and auto-deletes after
--ttl-days(integer1–30, default7). --artifact-idreuses the same public URL and incrementsversion/versionCounton each upload.- Published artifacts include CLI provenance: hostname, working directory, git branch/commit/subject/dirty state, and CI run info when present.
--titledefaults to the first#heading (Markdown), the<title>tag (HTML), or the filename.--typecontrols the artifact icon:report,plan,preview,design,audit,code, ordata. If omitted, the CLI infers one from title/name.--passcode NNNN(exactly 4 digits) makes viewers unlock the page first; repeated wrong guesses lock it out and eventually destroy the artifact.- Uploads are capped at 2 MB of HTML.
- Unless
--no-notify, the CLI also sends a notification whosedestinationURLis the report URL, so tapping the alert on the iPhone opens the report. Use--to <id>/--allto pick devices, as withnotify. - Requires relay credentials —
brivio config set --relay <url> --token <t>, or pair viabrivio connect. --jsonprints the Worker's raw response:{ "ok": true, "id": "…", "url": "https://…/r/…", "expiresAt": 1751…, "locked": false }.
Live Activities
A persistent Lock Screen + Dynamic Island widget. live start prints the
activity id on its own line (and returns it as id in --json) — capture it
to address subsequent updates and the end.
Styles: bar (linear progress), ring (circular), tasks (checklist),
pulse (indeterminate working state).
--destination-url sets the URL opened when the Live Activity is tapped. On
Codex runs, live start uses exact-link env (BRIVIO_DESTINATION_URL,
CODEX_DESTINATION_URL, CODEX_TASK_URL, CODEX_THREAD_URL,
CHATGPT_CODEX_URL) first, then ChatGPT universal links from
CODEX_SHARE_ID, CODEX_TASK_ID, or CODEX_CONVERSATION_ID.
ID=$(brivio live start \
--project build --name "Build pipeline" \
--title "Compiling" --status "Resolving deps" \
--style bar --symbol "hammer.fill" --tint "#34C759" \
--destination-url "$CODEX_TASK_URL" \
--total 8 --completed 0)
brivio live update --id "$ID" --project build \
--status "Compiling module 4/8" --completed 4 --progress 0.5
brivio live end --id "$ID" --project build --dismiss after:5
--tasks
For the tasks style, pass a comma-separated list; each item is name or
name:done:
brivio live update --id "$ID" --project build \
--tasks "Lint:done,Typecheck:done,Build,Test,Deploy"
becomes:
"tasks": [
{ "id": "lint", "name": "Lint", "done": true },
{ "id": "typecheck", "name": "Typecheck", "done": true },
{ "id": "build", "name": "Build", "done": false },
{ "id": "test", "name": "Test", "done": false },
{ "id": "deploy", "name": "Deploy", "done": false }
]
--dismiss
How the activity disappears when you end it: immediate, default, or
after:<seconds> (e.g. after:300 keeps the final state up for 5 minutes).
Enumerate ongoing activities — list (ps, activities)
brivio list fetches the device's GET /v1/activities and prints a clean,
aligned table of the Live Activities currently running — so an agent can
discover which projects/ids are live and act on them. ps and activities are
aliases.
brivio list
# Ongoing Live Activities (3):
# ID PROJECT STATUS PROGRESS PHASE UPDATED
# act-aaa build Compiling 50% running 2m ago
# act-bbb build Linking — running 31s ago
# act-ccc deploy Uploading 20% running just now
- PROGRESS is shown as
0–100%, or—when indeterminate (negative/omitted). - UPDATED is a relative time derived from the activity's
updatedAt(blank if the device doesn't provide one). --jsonemits the raw activities array (straight fromGET /v1/activities), ideal for scripting.- If the device is unreachable you get the same friendly error as the other LAN commands, and a nonzero exit.
brivio list --json | jq -r '.[].id' # just the ongoing ids
Local id cache — list --local
Every live start records { id, project, title, startedAt } to
~/.brivio/activities.json, and every live end removes that id. This lets
an agent re-discover the ids it started even while offline (no device round
trip):
brivio list --local
# Locally-tracked Live Activities (2):
# ID PROJECT TITLE STARTED
# act-18dbb14d web Building web just now
# act-470d05b9 api Building api 30s ago
brivio list --local --json # raw cache records
The cache is small and self-healing: if the file is missing or corrupt it is
treated as empty and recreated on the next write — it never blocks a live start/end.
Update many activities at once
Each activity is addressable by its own id (returned by live start). To update
several in one call:
# 1) Repeat --id to target specific activities:
brivio live update --id act-aaa --id act-bbb \
--status "Paused for review" --phase waiting
# 2) Omit --id and pass --project to update EVERY ongoing activity in that
# project. The CLI fetches /v1/activities, finds all whose projectID matches,
# and applies the update to each — printing one result line per id:
brivio live update --project build \
--phase success --status "Build succeeded" --tint "#34C759"
# ✓ act-aaa
# ✓ act-bbb
A single --id behaves exactly as before. With --json, multi-update emits
{ ok, count, results:[{ id, ok }] }. The command exits nonzero if any
individual update failed.
Targeting multiple devices
devices output includes stable id values. Use them to control where packets go:
brivio devices # find registered IDs
brivio ping --to test-device # only this device
brivio ping --all # all paired devices
brivio live update --id "$ID" --project build --to test-device --status "Step 1"
brivio live end --id "$ID" --project build --to test-device --dismiss after:5
If neither --to nor --all is used, CLI uses activeDeviceId from config (or first paired device).
Full example — an agent reports build progress
A realistic sequence an agent would run: start an activity, push several updates, flip to success, and end it.
#!/usr/bin/env bash
set -euo pipefail
PROJECT="ios-app"
# 1) Start the Live Activity (capture the id it prints).
ACT=$(brivio live start \
--project "$PROJECT" --name "iOS App" \
--title "Build & Test" --status "Starting…" \
--style tasks --symbol "gearshape.2.fill" --tint "#5E5CE6" \
--total 4 --completed 0 \
--tasks "Install deps,Compile,Test,Archive")
echo "Live Activity: $ACT"
# 2) Step through the work, updating status / progress / tasks.
brivio live update --id "$ACT" --project "$PROJECT" \
--status "Installing dependencies" --completed 1 --progress 0.25 \
--tasks "Install deps:done,Compile,Test,Archive"
brivio live update --id "$ACT" --project "$PROJECT" \
--status "Compiling" --completed 2 --progress 0.50 \
--tasks "Install deps:done,Compile:done,Test,Archive"
brivio live update --id "$ACT" --project "$PROJECT" \
--status "Running tests" --completed 3 --progress 0.75 \
--tasks "Install deps:done,Compile:done,Test:done,Archive"
brivio live update --id "$ACT" --project "$PROJECT" \
--status "Archiving" --completed 4 --progress 1.0 \
--tasks "Install deps:done,Compile:done,Test:done,Archive:done"
# 3) Flip to a success state, then end (keep it visible 30s).
brivio live update --id "$ACT" --project "$PROJECT" \
--status "Build succeeded ✅" --phase success --tint "#34C759"
brivio live end --id "$ACT" --project "$PROJECT" --dismiss after:30
On failure, an agent would instead:
brivio live update --id "$ACT" --project "$PROJECT" \
--status "Tests failed" --phase failure --tint "#FF453A" --symbol "xmark.octagon.fill"
brivio live end --id "$ACT" --project "$PROJECT" --dismiss default
# …and optionally fire a notification so it shows up even if the activity was dismissed:
brivio notify --project "$PROJECT" --title "Build failed" \
--body "3 tests failing in CheckoutTests" --tint "#FF453A" --sound
Full example — an agent fans out across many activities
Start three activities, list them, update one by id, update a whole project at once, then end them. Copy-paste friendly:
#!/usr/bin/env bash
set -euo pipefail
# 1) Start three Live Activities (two in "build", one in "deploy").
# Each `live start` prints its id on the last line — capture it.
A=$(brivio live start --project build --name "Build" --title "Frontend" --style bar | tail -1)
B=$(brivio live start --project build --name "Build" --title "Backend" --style bar | tail -1)
C=$(brivio live start --project deploy --name "Deploy" --title "Staging" --style pulse | tail -1)
echo "started: $A $B $C"
# 2) See what's ongoing (from the device). The same ids appear here.
brivio list
# …or, offline, the ids THIS machine started:
brivio list --local
# 3) Update a single activity by its id.
brivio live update --id "$A" --project build --progress 0.4 --status "Bundling"
# 4) Update EVERY ongoing activity in the "build" project at once
# (omit --id, pass --project). One result line per id is printed.
brivio live update --project build --phase success --status "Build done" --progress 1.0
# 5) End them. Repeat --id to end several explicit ids in one call;
# `live end` also removes each id from the local cache.
brivio live update --id "$A" --id "$B" --status "Wrapping up"
brivio live end --id "$A" --project build --dismiss after:5
brivio live end --id "$B" --project build --dismiss after:5
brivio live end --id "$C" --project deploy --dismiss after:5
brivio list --local # now empty
Discover (optional, mDNS)
If your network allows multicast DNS, you can scan for the app instead of typing the IP:
brivio discover
# Found 1 service(s):
# Brivio 192.168.1.42:8473
# brivio config set --host 192.168.1.42 --port 8473
mDNS is best-effort — VPNs and isolated/guest Wi-Fi often block it. If
nothing shows up, just use config set --host <ip> directly. Discovery failures
never throw; the command exits 0 with guidance.
Output & exit codes
- Human-readable by default;
--jsonprints the server's{ ok, id, message }response (or{ ok:false, error }on failure). - Exit code
0on success, non-zero on any error (bad flags, unreachable host, HTTP 4xx/5xx, auth rejection). - Set
NO_COLOR=1to disable ANSI colors.
Wire format
Every command builds one AgentPacket JSON envelope and POSTs it. The shape is
the single source of truth in PACKET_SCHEMA.md:
{
"kind": "notify" | "activity" | "ping",
"action": "present|update|dismiss" | "start|update|end",
"project": "build", "projectName": "Build", "id": "stable-id",
"title": "…", "body": "…", "status": "…",
"destinationURL": "https://chatgpt.com/codex/tasks/…",
"progress": 0.37, "completed": 3, "total": 8,
"symbol": "hammer.fill", "emoji": "🛠️", "tint": "#34C759",
"style": "bar|ring|tasks|pulse", "phase": "running|waiting|success|failure",
"tasks": [ { "id": "t1", "name": "Lint", "done": true } ],
"image": "https://… | data:image/png;base64,… | <raw base64>",
"sound": true, "dismiss": "immediate|default|after:300"
}
Endpoints used: GET /health, POST /v1/packet (ping), POST /v1/notify,
POST /v1/activity, GET /v1/activities (list/logs), and
POST /v1/artifacts on the relay Worker (artifact); /v1/reports remains a compatibility alias.
The local id cache (~/.brivio/activities.json) is CLI-only state — it is
written on live start/end and read by list --local; it is never sent to
the device.