@agora-build/atem
A terminal that connects builders, Agora platform, and AI agents. Manage Agora projects and tokens, route tasks between Astation and AI coding agents, and more -- all from a single CLI/TUI.
Install
npm install -g @agora-build/atem
Or via shell script:
curl -fsSL https://dl.agora.build/atem/install.sh | bash
Both download a prebuilt binary for your platform (linux-x64, linux-arm64, darwin-x64, darwin-arm64).
The npm package downloads the binary for its published version. Documentation
on GitHub main can describe features newer than the latest release. Check
atem --version and atem cred --help; use a current release that includes
the commands you need, or build from source below. Run a source build using
its explicit path, such as ./target/release/atem, to avoid an older installed CLI.
Commands
atem # Launch TUI
atem repl # Interactive REPL with AI command interpretation
Authentication
atem login # Log in with Agora Console (opens browser)
atem logout # Log out from SSO
atem status # Account, pairing, verification, credentials, Astation link and sessions (read-only)
atem pair # Pair with Astation, then verify this device (shows the status when already paired)
atem pair --save # Same, and keep credentials for offline use
atem pair verify # Verify this device with the paired Astation (no re-pairing)
atem unpair [--yes] # Remove the Astation pairing (asks first; your own login stays)
atem login unlocks a limited set: project list, tokens for the active
project, and the dev servers (serv rtc, serv convo, serv webhooks).
Pairing with Astation unlocks the full set: vault, sync, memory, skills,
atem cred, and Astation-driven terminal viewing, chat and mark
tasks. serv files, config, and tokens from
AGORA_APP_ID/AGORA_APP_CERTIFICATE need neither.
atem pair receives your Agora login from Astation, then verifies the device:
atem and your Mac each show a 12-character safety code. If they match, type
y here; on the Mac click Codes Match, then approve with Touch ID.
Either side can finish first. atem reads that y from the terminal itself,
never from piped input or keys typed earlier, so run atem pair in an
interactive terminal. If verification fails, the pairing stays saved, Astation
is told, and atem pair exits nonzero; run atem pair verify to retry. A connected or SSO-paired session alone does not
mean the device is verified; check atem status. With --save
the login Astation shares keeps working when Astation disconnects; without it
it stops 5 minutes after the disconnect (atem asks when --save isn't given).
While it may be used, Agora API calls (atem project list) use it; otherwise
they use your own atem login. One pairing at a time: atem unpair removes
it and keeps your own login.
Once paired, atem pair only prints the status (Already paired. Verify this device: atem pair verify · Replace the pairing: atem unpair, then atem pair):
it never pairs again or changes keys, trust or sessions. atem pair verify
verifies the existing pairing on the runtime's Astation link, does nothing when the
device is already verified, and refuses after a revocation (run atem unpair,
which sets the revoked keys aside, then atem pair). atem status reads
local files and asks an already running cred agent and atem runtime
(never starting either, never writing, never using the network): the session
authorization, the Astation link and the local agents, or no runtime running.
Device keys (atem cred)
atem cred unlock # Unlock this device's keys (approve with Touch ID on your Mac)
atem cred lock # Wipe unlocked keys and stop the cred agent
atem cred projects # Send known Git projects to Astation's credential picker (keys must be unlocked)
This device's keys are sealed on disk under a storage key that only the paired
Mac (the home Astation) holds. atem cred unlock asks the Mac for it (Touch
ID) and starts a background atem cred-agent that holds the keys in memory;
on a device no Astation verified it stops with This device isn't verified. Run atem pair verify first. and never starts pairing or verification.
atem cred lock wipes them and stops the agent. After upgrading atem, run
atem cred lock then atem cred unlock to pick up the new agent (the unlock
first updates Atem's runtime, which starts the agent, to the new build).
atem cred projects sends this repo's Git remote, and the project names atem
remembers, to Astation's credential picker, encrypted to your Mac. An
unlocked atem also sends it on its own whenever it connects to Astation (the
TUI, atem pair, and right after atem cred unlock), so you need
atem cred projects only to send it right away from another repo. Retrieving
values from Astation's local Credentials store (atem cred get/list) is not
available yet; it is described in the
encryption design.
Updated clients and cred agents retain the remote's original capitalization,
such as github.com/Agora-Build/Atem; project identity and duplicate checks
ignore case. Older clients/agents may report lowercase names. Reports are
encrypted even when memory encryption is off and can remain in the picker
after the sending command exits. You can also enter another Git remote in
Astation's Credentials editor.
See pairing, unlocking and project suggestions for the steps when Atem runs on another machine and for upgrade troubleshooting.
Tokens
atem token rtc create # Generate RTC token (interactive)
atem token rtc create --channel test --rtc-user-id 0 --expire 3600
atem token rtc create --channel test --rtc-user-id alice --with-rtm # RTC + RTM in one token (reuses rtc-user-id)
atem token rtc create --channel test --rtc-user-id 42 --with-rtm --rtm-user-id bob # Separate RTM account
atem token rtc decode <token> # Decode existing RTC token
atem token rtm create # Generate Signaling (RTM) token
atem token rtm create --rtm-user-id bob --expire 3600
atem token rtm decode <token> # Decode existing Signaling (RTM) token
The token code is also a Rust library, atem-core, in the
GitHub repository. The npm package installs the CLI only.
Projects
atem project list # List Agora projects
atem project list --show-certificates # List with app certificates visible
atem project use <N> # Set active project by index (1-based)
atem project use <APP_ID> # Set active project by App ID
atem project show # Show current active project
Configuration
atem config show # Show resolved config
atem config set astation_ws <URL> # Set Astation WebSocket URL
atem config set astation_relay_url <URL> # Set Astation relay URL
atem config clear # Clear active project selection
atem config convo # Interactive wizard to configure ConvoAI agent
atem config convo --validate # Validate existing config without modifying
atem config convo --config <PATH> # Use a specific config file
Vault (shared cross-agent context)
atem vault new --summary "auth refactor context" # Create a vault, prints its id
atem vault list # List vaults you can read
atem vault read --vault-id <id> # Current contents
atem vault read --vault-id <id> --history # Every version (shown eN vM)
atem vault read --vault-id <id> --format plain # Bare text (pipe into a prompt)
atem vault write --vault-id <id> --text "decided: JWT in cookie" # Append an entry
atem vault write --vault-id <id> --entry-id 3 --text "..." # Override entry e3
atem vault set-summary --vault-id <id> --text "..." # Update the summary
Requires pairing with Astation. Lets multiple atems (each driving an agent) share a versioned, append-only context store hosted on the Astation relay.
Memory & Skills (shared across agents and machines)
Requires pairing with Astation. Agents learn from each other: facts and skills saved on one machine (by Claude Code, Codex, or you) reach every other paired atem.
atem sync # Harvest Claude's memories → push → pull → apply
atem memory add "Relay tests need Valkey on :56379" # Save a fact (project scope inside a repo)
atem memory add --scope global --confidence high "..."
atem memory list # Global, this machine, and this project
atem memory list --all # Every project and machine
atem memory list --full # Everything: full id, status, timestamps, whole content
atem memory list --history [<id>] # Include outdated facts, as replacement chains
atem memory search "valkey" # Local full-text search (offline; CJK works)
atem memory replace <id> "the fact as it is now" # New fact replaces the old one (history kept)
atem memory invalidate <id> # Mark outdated without a replacement
atem memory rm <id> # Remove everywhere
atem memory purge <id> [--yes] # Remove a fact that contained a credential, stop re-harvesting it (asks first)
atem memory apply # Rewrite the managed blocks and skills from the local store
atem memory status # Account, machine, project, pending changes
atem skill add ./my-skill # Directory with SKILL.md (global by default)
atem skill list
atem skill rm <name> # Remove everywhere
atem skill history <name> # Every version, newest first
atem skill restore <name> --version 3 # Re-push an old version as a new one
atem skill purge <name> --version 3 [--yes] # Erase versions that contained a credential (or --all-versions; asks first)
Listings show short ids like 8245ecf6…e6fe (first 8 + last 4 characters).
Any command that takes an <id> accepts that form (… or ..), a full id,
or a unique prefix.
A project is identified by its git origin remote, so two repos with the same
folder name never collide. atem shows the remote's own spelling
(github.com/Agora-Build/Atem) but matches case-insensitively.
Memories are applied as a managed block in ~/.claude/CLAUDE.md,
~/.codex/AGENTS.md and <repo>/CLAUDE.local.md; skills go to
.claude/skills and .agents/skills. Files tracked by git are never written.
Anything that looks like a credential value is refused; save the
credential's name, never its value. See the memory design.
Works with end-to-end encryption on or off. When it is on, changes sync only
while the device is unlocked (atem cred unlock); see below.
End-to-end encryption
Turned on or off in Astation (Settings > Security > End-to-End Encryption). When it is on, memory content, skill files and vault content are encrypted on this machine before they reach the relay, and project names become keyed hashes. Your Mac, verified Atem devices holding the account key, and holders of the recovery material can decrypt that content. The relay still sees routing identifiers and metadata, such as timestamps and ciphertext sizes; it is not authoritative for device trust or encryption state. Migration cannot prove that a server erased old plaintext backups.
The device must be verified (atem pair, or atem pair verify to retry) and unlocked (atem cred unlock).
While it is locked, atem sync keeps changes queued. Switching encryption on
or off migrates existing data; each verified atem finishes its part at its next
atem cred unlock (atem sync alone doesn't contact Astation). Turning on
or Turning off is a pending migration, even when the field counts show
no remaining plaintext or obsolete fields. Astation commits the final state
after accepting a verified migration completion; the CLI can then disconnect.
Turning encryption off migrates shared data to plaintext; local Astation
Keychain credentials keep their protection. The local knowledge.db stays
plain text (mode 0600). See the user guide
and protocol design.
Dev Servers
atem serv convo # Launch ConvoAI test page (HTTPS)
atem serv convo --config ~/convo.toml # Use custom config
atem serv convo --background # Detached daemon — POSTs /join, registers, exits
atem serv attach <ID> # Open UI bound to a running convo daemon (talk to live agent)
atem serv attach 3 # …or by index from `atem serv list`
atem serv rtc # Launch RTC test page (HTTPS)
atem serv rtc --channel test --port 8443
atem serv rtc --background # Run as background daemon
atem serv files ~/docs # Serve a directory or file over HTTPS (Markdown rendered)
atem serv files ~/docs --background # Port auto-picked; reused next run
atem serv webhooks # Receive Agora webhooks locally (auto-tunneled via ngrok)
atem serv webhooks --no-tunnel # Local listener only — bring your own tunnel
atem serv webhooks --background # Run as background daemon
atem serv list # List running background servers (#, ID, PID, PORT, STATUS, URL)
atem serv info <ID|#> # One server: status, start, every URL with its access code, log
atem serv kill <ID|#> # Kill a server (POSTs /leave for convo)
atem serv killall # Kill all background servers
# Fleet test loop — {appid} and {ts} are expanded by atem
for i in $(seq -f '%04g' 1 10); do
atem serv convo --background --channel 'atem-convo-{appid}-{ts}-'$i
done
serv convo — ConvoAI voice agent: live transcription (RTM), preset selection, avatar (Akool, LiveAvatar, Anam), RTC Stats, API History, camera toggle, RTC encryption (key + salt forwarded to the agent), HIPAA mode (routes via /hipaa/api/...), audio dump.
--background re-execs as a detached daemon: parent POSTs /join, registers in ~/.config/atem/servers/<channel>.json, exits. The daemon polls Agora's GET /agents/{id} every 60s and writes the status (RUNNING/IDLE/STOPPED/…) back into the registry — atem serv list reads it without making any network calls. kill/killall SIGTERM the daemon, which catches the signal and POSTs /leave before exiting. Daemon log file (<channel>.log) contains the /join URL and request body with secrets masked.
serv attach <id> — opens a foreground HTTPS UI bound to a running convo daemon's channel. The page hides Start/Stop because the daemon owns the agent; you Join to talk to the live agent. Encryption / HIPAA / geofence read from the same convo.toml so the local SDK matches what the daemon used.
serv rtc — RTC test page: join/leave, publish/subscribe audio+video, token generation, RTM messaging, RTC encryption (8 modes; gcm2 modes auto-generate a copyable salt).
serv files — serves a directory (or a single file) over HTTPS. Markdown is rendered to HTML; append ?raw=1 to any URL for the raw bytes. Useful for browsing AI-generated docs on a remote machine.
serv webhooks — local receiver for Agora webhook events (ConvoAI: agent_joined, agent_left, agent_history, agent_error, agent_metrics, …; RTC NCS events). Validates Agora-Signature-V2 (HMAC-SHA256) when a secret is configured in webhooks.toml; accepts unsigned events otherwise with a warning. Live web console at http://127.0.0.1:9090/?t=… (the printed URL) shows incoming events; each event also prints a one-line summary to stdout. Default tunnel provider is ngrok; set tunnel_provider = "cloudflared" for zero-auth quick tunnels. Use --no-tunnel if you're running cloudflared / ngrok separately for stable URLs across atem restarts.
Access code. Every atem serv page and API needs the access code in the URL it prints (https://…:8443/?t=…), new at each start. Opening that URL sets a cookie for the server and drops the code from the address bar; without it, every path is a plain 404, so other devices on your network can't find or use the server. To share a page, use its Copy link button: it copies the page's address, on whichever host you opened it, with the code added back (on plain HTTP away from localhost, where browsers allow no clipboard, it shows the link to copy). Background servers print it too (atem serv files, rtc, webhooks), and their log has it. For serv webhooks only the live console needs it: Agora's POSTs to /webhook keep the URL you registered. In the ConvoAI page's API History, credentials are masked on screen; Copy with full credentials copies the requests with them.
Channel placeholders — --channel accepts {appid} (first 12 chars of the active app id) and {ts} (unix epoch seconds at startup). Useful in for-loops to match the auto-gen channel format.
AI Agents
atem agent launch # Open the original Claude Code CLI in an Atem terminal
atem agent launch codex # Open the original Codex CLI in an Atem terminal
atem agent launch claude --continue # Arguments after the agent type go to that CLI: continue the last Claude session
atem agent launch codex resume --last # Continue the last Codex session
atem agent launch claude -- --help # Use -- to read the agent's own help
atem agent list # The sessions the background runtime holds
atem agent attach ses-… # Reopen a session (screen restored)
atem agent stop ses-… # Stop a session
atem runtime restart # Restart the runtime as this build; sessions keep running
atem agent launch [claude|claude-code|codex] [ARGS]... runs the installed
claude or codex CLI, unchanged, in a full-screen Atem terminal. It uses
your working directory and your own login for that CLI. Nothing here replaces
the CLI with an API conversation. It works without Astation.
Sessions belong to a background atem runtime (started on demand, one per
profile; it exits after 10 minutes with no sessions and no frontends), and each agent runs in its own atem worker process. Closing the terminal window or hiding it (prefix, then h)
leaves the session running; atem agent attach reopens it. Several Claude
and Codex sessions can run at once, and they keep running if the runtime
itself restarts. The runtime also holds this machine's one Astation
connection: atem pair, atem pair verify, atem cred unlock and atem cred projects run on it (they start the runtime if needed) and never open their
own, so pairing never disconnects a running session. When an agent that
printed something ends, its last output and a short summary are saved under
~/.config/atem/logs/<session id>/.
If you are paired and verified, Astation can view each session, take control of it (one controller at a time, on a lease Astation keeps renewing; typing locally takes control back at once), and chat with the agent: sending a prompt takes control when nobody holds it (if another device does, Astation asks before taking over), the whole retained conversation can be scrolled back page by page, and each finished prompt names its reply messages. Every remote request names the exact session. On a verified host, Astation, your phone and approved services reach it over end-to-end encrypted channels checked against your account's signed device list. Keep Atem and Astation up to date together: an older Astation is refused until it is updated.
Atem's own keys start with a prefix, Ctrl+] by default. Press it, then:
| Key | Action |
|---|---|
h or m | Hide the terminal; the agent keeps running |
g | Take control back locally |
a | You answered an approval in the agent's own dialog: end its wait (see below) |
x | Stop the agent |
r | Redraw the screen |
[ | Browse local history (arrows, Space to select, Enter to copy, Esc to return) |
c | Copy the visible text (tmux buffer inside tmux, otherwise OSC 52) |
? | Help and recent notices |
The bottom row reads, for example,
Atem | Claude | Local | Astation ✓ | Notice | Ctrl+] then ?:help:
ClaudeorCodexis the agent on screen.Localmeans your keyboard drives the agent;Remotemeans Astation holds control. Typing takes it back (prefix, theng, does it without typing).Astation ✓means the runtime's Astation link is authenticated;Astation ✗means none (not paired, Astation unreachable, or re-authenticating). Every reconnect or re-authentication is a new remote attachment: Astation views again and asks for control again.Noticeappears once Atem has recorded a warning or status message. Press the prefix, then?, to read the recent notices (newest first).
Press the prefix twice to send it to the agent. To change it, set
terminal_prefix = "Ctrl-a" in ~/.config/atem/config.toml, or
ATEM_TERMINAL_PREFIX (which wins over the file). Ctrl+], Ctrl+\ or
Ctrl+ other than i or m are accepted. The CLAUDE_CLI_BIN/CLAUDE_CLI_ARGS
and CODEX_CLI_BIN/CODEX_CLI_ARGS variables still override the command.
See designs/agent-runtime.md.
Chat from Astation. Astation can open a chat view of a Claude session
launched with atem agent launch claude: your prompts appear as messages and
Claude's replies come from its own session log, with a status of ready,
working, waiting or unknown. Atem adds its hooks with --settings for that run
only; your ~/.claude settings are not changed. A prompt sent from chat needs
control of the session, is typed into Claude once, and is reported as
accepted, then complete (or failed) when Claude's turn ends.
Codex works the same way once you allow Atem's Codex hooks (asked
once on the first atem agent launch codex, see below): Atem reads Codex's
own session log, so Codex shows ready, working, waiting or unknown. Codex
reports nothing before its first prompt, so type the first prompt in the
terminal; after that chat prompts work. Replies arrive whole (no streaming),
and Codex's chat has no reasoning text or error details.
Interrupt from chat stops a running turn only. See
designs/agent-chat-host.md.
Approve prompts from Astation and your phone. When Claude Code or Codex
asks for permission to run a tool, Astation shows the request as a card (in
a Claude chat view too) and Atem asks the relay to notify your devices
with control. A card shows the tool and a short summary — the command's first
line or the file path, never file contents — and offers Allow once,
Always allow (Claude: one of the rules Claude itself suggests, with where
it is saved; Codex, for shell commands only: Always allow this command —
exactly this command in this folder, for the rest of this Codex process, kept
by Atem; later runs of
the very same command are allowed without asking and show as "auto allowed",
and the rule can be revoked from Astation) or Deny (with an optional
note for the agent).
Answering needs no control of the session: any of your devices that can
control it answers, whoever is typing, and the first answer wins. The
finished card shows how it ended. The terminal can
always answer instead: Claude keeps its own dialog on screen (the first
answer wins), and for Codex any key you type in the terminal hands the
prompt back to Codex's own dialog. The agents don't tell Atem which tool
call a permission prompt was about, so Atem can't see an answer given in
their own dialog: after answering there, press the prefix, then a (Atem
reminds you when a tool ran while an approval waits). Until then the card
stays open and chat prompts are held, so a prompt is never typed into an
open dialog. If nobody answers, nothing is allowed:
after the hook's wait (Claude 10 min, Codex 5 min) the agent asks in the
terminal as usual. Claude gets the hook with --settings for each launch;
to turn it off, set approvals_hook = false under [claude] in
~/.config/atem/config.toml (Claude sessions launched afterwards keep their
prompts in the terminal).
The first atem agent launch codex asks once whether to add Atem's hooks
(one per Codex event, for approvals and chat) to ~/.codex/hooks.json, after
your own (a backup is kept as hooks.json.atem-bak); to turn them off later,
set hooks = false under [codex] in ~/.config/atem/config.toml — the next
atem agent launch codex removes them. See the session protocol for the details.
TUI Modes
| Mode | Description |
|---|---|
| Main Menu | Navigate between features |
| Agent Sessions | The runtime's Claude/Codex sessions: launch or attach |
| Token Gen | Generate Agora RTC/RTM tokens locally |
| Projects | Browse Agora projects via API |
Supported Platforms
| Platform | Architecture |
|---|---|
| Linux | x64, arm64 |
| macOS | x64, arm64 |
Build from Source
git clone https://github.com/Agora-Build/Atem.git
cd Atem
cargo build --release
# Binary at target/release/atem
Related Projects
- Astation -- macOS menubar hub that coordinates Chisel, Atem, and AI agents — talk to your coding agent from anywhere
- Chisel -- Dev panel for visual annotation and UI editing by anyone, including AI agents
- Vox -- AI latency evaluation platform
License
MIT