agent-activity
v1.0.9
Published
CLI for Agent Activity — let coding agents report progress to an iPhone via updatable notifications and Live Activities.
Maintainers
Readme
agent-activity
Command-line tool for Agent Activity — let a coding agent (or any script) report progress to your iPhone as updatable notifications and Live Activities (Lock Screen + Dynamic Island).
agent-activity speaks the AgentPacket JSON envelope defined in
PACKET_SCHEMA.md. It POSTs that envelope to the
Agent Activity 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 agent-activity@latestIt's a single executable script: no compile step and no runtime dependencies.
Use agent-activity in docs and scripts; agentactivity is installed as a secondary alias for typo-free terminal use.
For repo development, run npm link inside cli/ or call node cli/bin/agent-activity.js directly.
Codex usage + fallback delegation
agent-activity codex usage --json
agent-activity codex live --interval 60
agent-activity codex config --delegation-enabled true --target-agent claude --target-command claude --target-model sonnet
agent-activity codex delegate --dry-runcodex usage fetches Codex 5-hour and weekly usage from codex app-server and
writes ~/.agent-activity/codex-usage.json. The macOS app reads that file for a
local usage panel. Delegation is off by default; enable it with
agent-activity codex config --delegation-enabled true. Then codex delegate
starts the fallback agent once per reset window when 5-hour remaining is
<=20% or weekly remaining is <=10%. Use --dry-run to print the command
without launching it.
Connect
Use the same flow shown in the iPhone onboarding:
agent-activity qrScan the generated QR image in Agent Activity, or choose Pair with Code and
enter the 10-character code printed under it. First run creates relay
credentials automatically and stores them at ~/.agent-activity/config.json.
Delivery defaults to auto: saved LAN host, then Bonjour-discovered LAN
devices, then relay only if local delivery fails.
agent-activity config set --host 192.168.1.42 --port 8473
agent-activity config show
agent-activity up show QR + short pairing code
agent-activity status show saved/discovered devices
agent-activity reset-pairing delete saved iPhone connection
agent-activity qr show QR image + short pairing code
agent-activity health # confirm the phone answersConfig is stored at ~/.agent-activity/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.
agent-activity config set --relay wss://relay.example.com --token YOUR_TOKENPrecedence
Every setting can come from (highest wins) flags → env vars → config file → defaults:
| Setting | Flag | Env var | Default |
|---------|------------|--------------------|---------|
| host | --host | AGENT_ACTIVITY_HOST | — |
| port | --port | AGENT_ACTIVITY_PORT | 8473 |
| relay | --relay | AGENT_ACTIVITY_RELAY | — |
| token | --token | AGENT_ACTIVITY_TOKEN | — |
| via | --via | AGENT_ACTIVITY_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:
agent-activity --host 192.168.1.42 ping
agent-activity ping --host 192.168.1.42Add --json to any command for machine-readable output. Every command exits
nonzero on failure.
Commands
agent-activity config set --host <ip> [--port 8473] [--relay <wss-url>] [--token <t>] [--via auto|lan|relay]
agent-activity config show
agent-activity pair <aa1.code> save iPhone pairing code
agent-activity devices show paired/discovered iPhones in a table, including alarm status
agent-activity devices rename <id> <name> rename saved iPhone connection
agent-activity devices remove delete saved iPhone connection
agent-activity health GET /health on the LAN server
agent-activity ping [--to <id> | --all] connectivity check (no UI)
agent-activity blockage --project <id> --body <blocker> [--request <next step>] [--destination-url <url>]
agent-activity notify --project <id> [--name <n>] --title <t> [--destination-url <url>] [--to <id> | --all]
agent-activity notify update --id <id> --project <id> [--progress] [--body] [--status] [--image] [--to <id> | --all]
agent-activity notify dismiss --id <id> --project <id> [--to <id> | --all]
agent-activity live start [--project <id>] [--name <n>] --title <t> [...] → prints the activity id
agent-activity live update --id <id> --project <id> [...] (also: --id --id b, or --project p, --to <id> | --all)
agent-activity live end --id <id> --project <id> [--dismiss immediate|default|after:<sec>] [--to <id> | --all]
agent-activity alarm set --title <t> (--at <iso-date> | --in <duration>) prints alarm UUID
agent-activity alarm cancel --id <uuid>
agent-activity artifact <file.md|file.html> [--artifact-id id] [--title …] [--type plan] [--ttl-days 7] [--passcode 1234] [--no-notify] publish a shareable artifact
agent-activity artifacts [--json] [--to <id> | --all] list active/archived/expired/deleted artifacts
agent-activity codex usage [--cached] fetch/store or show Codex usage
agent-activity codex live [--once] [--interval 120] stream usage to Live Activity
agent-activity codex delegate [--dry-run] [--force] start opt-in fallback agent when low
agent-activity codex config [--target-agent claude] [--target-model sonnet]
agent-activity list [--json] [--local] list ongoing Live Activities (aliases: ps, activities)
agent-activity logs list active Live Activities
agent-activity discover mDNS scan for servers on the LAN (optional)Run agent-activity <command> --help for the full flag list of any command.
Codex usage and delegation
agent-activity codex usage reads Codex 5-hour and 7-day windows through the
Codex app-server and stores the latest sample at
~/.agent-activity/codex-usage.json.
agent-activity codex usage --json
agent-activity codex live --interval 120
agent-activity codex config --delegation-enabled true --target-agent claude --target-command claude --target-model sonnet
agent-activity codex delegate --dry-runDelegation is off by default. When enabled, it delegates from Codex to Claude when the 5-hour window has 20% or less remaining, or the weekly window has 10% or less remaining. Use cron or launchd for frequent checks:
*/5 * * * * /opt/homebrew/bin/agent-activity codex delegate >/tmp/agent-activity-codex-delegate.log 2>&1Notifications
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 (AGENT_ACTIVITY_DESTINATION_URL, CODEX_TASK_URL,
CODEX_THREAD_URL, etc.) when available.
agent-activity 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
agent-activity 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
agent-activity notify update --id deploy-42 --project deploy \
--progress 0.8 --body "Running migrations…"
# remove it
agent-activity 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)
agent-activity 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
agent-activity notify update --id "$ID" --project ci --progress "$p" --body "Building… ${p}"
sleep 2
done
agent-activity notify update --id "$ID" --project ci --progress 1.0 --body "Done ✅" --soundAlarms
agent-activity 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 Agent Activity
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.
agent-activity alarm set --title "Check build" --body "Tests should be done" --in 10m --ringtone retroBell --alarm-lead-minutes 5
agent-activity alarm set --title "Review artifact" --in 10m --symbol doc.richtext.fill --tint "#34C759" --image "$IMG" --destination-url "$URL" --alarm-button-title "Open artifact"
agent-activity alarm set --title "Stand up" --at 2026-06-30T15:00:00Z
agent-activity 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.
agent-activity 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
agent-activity 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
agent-activity artifact build-summary.md
# stable artifact id; each upload to same id increments version
agent-activity artifact plan.html --artifact-id q3-migration-plan
# custom title, 14-day expiry, and a 4-digit passcode gate
agent-activity artifact perf.html --title "Perf regression deep-dive" \
--type audit --ttl-days 14 --passcode 4821
# machine-readable output, and skip the phone notification
agent-activity artifact notes.md --json --no-notify
# list artifacts created by this relay token
agent-activity 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 —
agent-activity config set --relay <url> --token <t>, or pair viaagent-activity 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 (AGENT_ACTIVITY_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=$(agent-activity 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)
agent-activity live update --id "$ID" --project build \
--status "Compiling module 4/8" --completed 4 --progress 0.5
agent-activity 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:
agent-activity 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)
agent-activity 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.
agent-activity 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.
agent-activity list --json | jq -r '.[].id' # just the ongoing idsLocal id cache — list --local
Every live start records { id, project, title, startedAt } to
~/.agent-activity/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):
agent-activity 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
agent-activity list --local --json # raw cache recordsThe 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:
agent-activity 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:
agent-activity live update --project build \
--phase success --status "Build succeeded" --tint "#34C759"
# ✓ act-aaa
# ✓ act-bbbA 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:
agent-activity devices # find registered IDs
agent-activity ping --to test-device # only this device
agent-activity ping --all # all paired devices
agent-activity live update --id "$ID" --project build --to test-device --status "Step 1"
agent-activity live end --id "$ID" --project build --to test-device --dismiss after:5If 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=$(agent-activity 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.
agent-activity live update --id "$ACT" --project "$PROJECT" \
--status "Installing dependencies" --completed 1 --progress 0.25 \
--tasks "Install deps:done,Compile,Test,Archive"
agent-activity live update --id "$ACT" --project "$PROJECT" \
--status "Compiling" --completed 2 --progress 0.50 \
--tasks "Install deps:done,Compile:done,Test,Archive"
agent-activity live update --id "$ACT" --project "$PROJECT" \
--status "Running tests" --completed 3 --progress 0.75 \
--tasks "Install deps:done,Compile:done,Test:done,Archive"
agent-activity 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).
agent-activity live update --id "$ACT" --project "$PROJECT" \
--status "Build succeeded ✅" --phase success --tint "#34C759"
agent-activity live end --id "$ACT" --project "$PROJECT" --dismiss after:30On failure, an agent would instead:
agent-activity live update --id "$ACT" --project "$PROJECT" \
--status "Tests failed" --phase failure --tint "#FF453A" --symbol "xmark.octagon.fill"
agent-activity live end --id "$ACT" --project "$PROJECT" --dismiss default
# …and optionally fire a notification so it shows up even if the activity was dismissed:
agent-activity notify --project "$PROJECT" --title "Build failed" \
--body "3 tests failing in CheckoutTests" --tint "#FF453A" --soundFull 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=$(agent-activity live start --project build --name "Build" --title "Frontend" --style bar | tail -1)
B=$(agent-activity live start --project build --name "Build" --title "Backend" --style bar | tail -1)
C=$(agent-activity 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.
agent-activity list
# …or, offline, the ids THIS machine started:
agent-activity list --local
# 3) Update a single activity by its id.
agent-activity 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.
agent-activity 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.
agent-activity live update --id "$A" --id "$B" --status "Wrapping up"
agent-activity live end --id "$A" --project build --dismiss after:5
agent-activity live end --id "$B" --project build --dismiss after:5
agent-activity live end --id "$C" --project deploy --dismiss after:5
agent-activity list --local # now emptyDiscover (optional, mDNS)
If your network allows multicast DNS, you can scan for the app instead of typing the IP:
agent-activity discover
# Found 1 service(s):
# Agent Activity 192.168.1.42:8473
# agent-activity config set --host 192.168.1.42 --port 8473mDNS 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 (~/.agent-activity/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.
