Audience: you, the AI model that is setting up an aura-server. Read this
file in full, then execute the numbered steps in order. Do not improvise,
reorder, or skip steps. You proceed autonomously and report results to the
user in plain language; the one thing you always need from them is the
API key (XAI_API_KEY or OPENAI_API_KEY, step 3). See When to ask the user below for the few exceptions.
aura gives an AI chat a real voice call: the user types "call me", the host
launches aura-server (it holds the API key, the engine, the chat context, and
the tools), the server mints a single-use session secret and prints a
connection string, and the user's thin client aura-cli (mic/speaker, via
cpal) connects over one Noise-encrypted UDP tunnel. There is no broker and no relay — in a remote setup the AI server is
itself the call endpoint.
You are the AI setting up the server. Read this whole file first, then execute:
- Detect OS + decide LOCAL vs REMOTE — from the environment, not by asking → Step 0
- Clone the repo into a stable working dir (
~/aura) → Step 1 - Build + install the server (
./install.sh --server) — this also putsaura-call/aura-call-statuson PATH → Step 2 - Choose the voice provider + get the API key — the ONE time you stop and ask the user → Step 3
- (REMOTE only) transport — run the NAT check, then let it choose: a reachable public VPS → open the UDP port once (
scripts/aura-open-port.sh) and use direct UDP (lower latency, no broker); behind NAT/CGNAT → iroh automatically, open nothing; LOCAL → skip → Step 4 - Copy the skill
skills/SKILL.mdinto your framework's skills dir (mkdir -pfirst) → Step 5 - Set your framework to auto-approve the orchestrator's tool calls + raise the turn/timeout budget → Step 5b
- Self-check — verify binaries, key, port, skill, framework prep BEFORE you report done → Step 6
- Tell the user how to start a call → Step 7
- ❌ Re-asking the user for things you can detect or decide. Detect the OS; decide LOCAL vs REMOTE from where you are running; use the defaults (port 47821,
~/aura, your own skills dir). Ask only for the items in "When to ask the user" below. - ❌ Starting before reading the whole guide, or reordering / skipping steps.
- ❌ Stopping after the build. Built binaries ≠ a working call — you are done only after the Step 6 self-check passes.
- ❌ Killing the build because it seems slow. A first
cargo build --release(it also fetches the pinned toolchain + crates) can take several minutes — give it a generous timeout and let it finish; do not kill or retry it. - ❌ Echoing, printing, or logging the API key or the session secret, or putting either on a command line (
argvis visible inps). - ❌ Opening the firewall per call. The UDP port is opened ONCE at Step 4 and never touched again.
- ❌ Asking the user where the skill goes. You know your own skills directory — copy
SKILL.mdthere yourself (mkdir -pthe directory first). - ❌ Continuing past a failed step. On any failure: stop, tell the user exactly what failed and the fix, do not proceed.
- Proceed autonomously — OS detection, the mode decision (when it is clear), cloning, building, installing, copying the skill, opening the port when you have root, and every verification. Report what you did in plain language; do not ask permission for these.
- Ask the user — only these:
- The API key — which provider and the key itself (Step 3); BYOK, you cannot proceed without it.
- LOCAL or REMOTE (Step 0) — only if you genuinely cannot tell from the environment.
- Run the
sudofirewall commands (Step 4) — only when you lack root; you print the exact commands and the user runs them.
- Never sign in, paste the key, or change anything outside this machine on the user's behalf.
aura-server— runs where the host/AI runs (on127.0.0.1for a LOCAL call, on a VPS for a REMOTE call). Holds the BYOK key + engine + context + tools. Does not use the microphone, so it needs no audio library.aura-cli— the thin client on the user's own machine (it has the mic and speakers). Holds no key, no engine, no context. On Linux it needs the ALSA dev headers; on macOS/Windows it needs nothing extra.
This onboarding sets up the server side. The user installs aura-cli on
their own machine separately (see the repository README.md / install.sh --client); for a LOCAL call on the same machine, install.sh (default = both)
installs aura-cli too.
- Never echo, print, or log the API key or the session secret. Never
place either on a command line (
argv) — anything on a command line is visible inps. The key lives only in the environment, the OS keychain, or achmod 600aura.env(~/.config/aura/.env). The session secret travels only inside the connection string via theAURA_CONNECTenvironment variable or stdin. - One predictable UDP port, opened once. The tunnel uses a fixed port (default 47821). For a REMOTE server you open it once at onboarding (step 4), never per call.
- No silent failure. If a step fails, stop, tell the user exactly what failed and how to fix it, and do not continue to a later step.
Determine where you are running and confirm the basics.
uname -s # Linux | Darwin (macOS)
uname -m # x86_64 | aarch64 / arm64Decide the call mode for this server up front:
- REMOTE — this server runs on a VPS / remote host the user reaches over the network. Steps 1–6 apply, including the firewall step (step 4).
- LOCAL — this server runs on the user's own machine and the client connects
over loopback (
127.0.0.1). Steps 1–6 apply except step 4 (no firewall: loopback is never exposed).
A quick heuristic: if you are on a cloud VM / SSH host that the user is not sitting at, treat it as REMOTE. If the host is the user's personal workstation, treat it as LOCAL. When unsure, ask the user one question: "Is this server on your own machine (local) or on a remote/VPS host?"
Confirm the build prerequisites exist (you install them in step 2 if missing):
command -v git || echo "git missing — install it (e.g. apt/dnf/brew install git)"
command -v cargo || echo "cargo missing — rustup will be installed in step 2"
command -v curl || echo "curl missing — install it for downloads / IP lookup"Already ran the one-line installer (
curl … | bash -s -- --server)? It cloned the repo to~/auraand built the server for you — Steps 1–2 are done.cd ~/auraand continue from Step 3. Otherwise, do Steps 1–2 below.
Pick a stable working directory the server will run from (its .aura/ callback
files live here; the key itself goes in the global ~/.config/aura/.env — step
3). ~/aura is a good default.
# If you already have the repo, skip the clone and just cd into it.
git clone https://github.com/RealWagmi/aura "$HOME/aura"
cd "$HOME/aura"If git is unavailable and you only have a release tarball/zip, unpack it into
$HOME/aura and cd there instead. Verify you are at the repository root:
test -f Cargo.toml && test -f install.sh && echo "at repo root: OK"If install.sh is not present at the root, you are in the wrong directory — fix
that before continuing.
The repository pins the Rust toolchain via rust-toolchain.toml
(Rust 1.92.0); rustup selects it automatically — do not install a
different toolchain.
If cargo/rustup is missing, install Rust first (non-interactive):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"Now build and install only the server with the bundled installer. The
server pulls no audio library, so you do not need ALSA/libasound2-dev
to build it:
./install.sh --serverThis builds aura-server in release mode and installs it to ~/.local/bin
(override with --prefix DIR). The installer adds ~/.local/bin to your PATH
via your shell rc if it is not already there and tells you to restart the shell
or source the rc. install.sh is idempotent — safe to re-run; it rebuilds
and overwrites.
Make aura-server available in the current shell, then confirm:
export PATH="$HOME/.local/bin:$PATH" # if the installer just added it
command -v aura-server && echo "aura-server installed: OK"If the build fails, surface the exact cargo error to the user and stop. The
most common cause on a fresh box is a missing C linker — install build-essential
(Debian/Ubuntu) / gcc + make (Fedora/RHEL via dnf, Arch via pacman) and
re-run ./install.sh --server.
aura is BYOK (bring your own key) and supports two realtime voice
providers. Ask the user which API key they can provide — this is where you
stop for input. Offer exactly these options:
| Choice | Key to provide | Voice model | Cost ballpark |
|---|---|---|---|
| xAI Grok | XAI_API_KEY |
grok-voice-think-fast-1.0 |
flat $0.05/min — predictable |
| OpenAI | OPENAI_API_KEY |
gpt-realtime-2.1 |
token-billed, ≈$0.05–0.10/min — smartest, 128k context |
| OpenAI mini | OPENAI_API_KEY |
gpt-realtime-2.1-mini |
token-billed, ≈$0.02–0.04/min — cheapest |
The server auto-picks the provider by which key it finds (xAI wins when both
are present). To pin it explicitly, add AURA_VOICE_PROVIDER=xai|openai to the
same .env; for the mini model also add
AURA_VOICE_MODEL=gpt-realtime-2.1-mini.
The server resolves the key in this order (first non-empty value wins):
- the environment variable (
XAI_API_KEY/OPENAI_API_KEY); - a
.envfile (KEY=VALUElines), loaded into the environment without overriding an already-set variable — first./.envin the directory the server starts in, then a fixed user-global$AURA_HOME/.env(else${XDG_CONFIG_HOME:-~/.config}/aura/.env); - the OS keychain (service
aura, entry named like the env var).
Each key is host-pinned: XAI_API_KEY is sent only to api.x.ai,
OPENAI_API_KEY only to api.openai.com; the server refuses to send
either anywhere else.
Store the key without ever echoing or logging it, and choose the location by how the server runs.
Recommended — a user-global ~/.config/aura/.env. The host (e.g. Claude
Code) launches the server from whatever directory it is in, so a key tied to one
project directory may not be found. The global file is read regardless of the
launch directory, so it is the safe default. Create it owner-only first, then
have the user paste the key so it never appears in your transcript or shell history:
umask 077
mkdir -p ~/.config/aura
# Write OPENAI_API_KEY= instead if the user chose OpenAI:
printf 'XAI_API_KEY=' > ~/.config/aura/.env && chmod 600 ~/.config/aura/.env
# The user pastes the key — read from stdin, never argv, never printed:
( read -rs k; printf '%s\n' "$k" >> ~/.config/aura/.env; unset k )
echo "key written to ~/.config/aura/.env" # confirmation only; the key is never printedThat snippet is for a terminal the USER types into (they paste; the key never
appears in your transcript). If the user instead sends the key to you in chat,
append the KEY=<key> line to the same file with your FILE-WRITE/edit tool
(never a shell echo/printf — command lines are visible in ps), then
chmod 600 the file.
If the user chose OpenAI, also append the provider pin (plus the model line only for mini, and an optional ISO-639-1 transcription hint for non-English users — it improves the user-side transcript in call recaps):
printf 'AURA_VOICE_PROVIDER=openai\n' >> ~/.config/aura/.env
printf 'AURA_VOICE_MODEL=gpt-realtime-2.1-mini\n' >> ~/.config/aura/.env # mini only
printf 'AURA_TRANSCRIBE_LANG=ru\n' >> ~/.config/aura/.env # optional, e.g. RussianVerify it exists and is owner-only, without printing its contents:
f=~/.config/aura/.env
test -f "$f" && [ "$(stat -c '%a' "$f" 2>/dev/null || stat -f '%Lp' "$f")" = "600" ] \
&& echo "$f present and 0600: OK"
stat -cis GNU/Linux;stat -f '%Lp'is macOS/BSD — the line tries both.
Alternatives. For a dedicated server you always start from one fixed directory,
a chmod 600 ./.env in that directory works the same way (same commands, .env
instead of ~/.config/aura/.env). Or store the secret in the OS keychain
(service aura, entry named like the env var) on macOS (security) or Windows (Credential
Manager) — directory-independent, nothing on disk. On Linux the keychain backend
is the kernel keyring (keyutils), which does not reliably persist for a
long-running server, so prefer .env there.
Never run echo "$XAI_API_KEY", never put the key on a command line, and
never write it anywhere but a 0600 .env (or the keychain).
A LOCAL call binds loopback and needs no open port. A REMOTE call picks its transport from a one-time reachability check — do not re-ask the user:
- LOCAL → skip 4a–4c. Open no port. Continue to Step 5.
- REMOTE → run the NAT check (4a) FIRST, then: a reachable public IP (a VPS) → open
the port (4b + 4c) and persist
AURA_PUBLIC_HOSTto the aura.env— the server reads it back and auto-selects direct UDP (lowest latency, no third party); behind NAT/CGNAT → persist nothing, the server falls back to iroh.
4a. NAT check (do not skip). A REMOTE machine behind NAT (private 10.x/192.168.x address) cannot accept direct inbound UDP whatever the OS firewall says — run this BEFORE opening anything:
PUBLIC_IP="$(curl -fsS ifconfig.me || curl -fsS https://api.ipify.org)"; echo "public IP = $PUBLIC_IP"
LOCAL_IPS="$( (ip -4 addr show 2>/dev/null || ifconfig 2>/dev/null) | awk '/inet /{sub("/.*","",$2); print $2}' )"
case " $(echo $LOCAL_IPS) " in
*" $PUBLIC_IP "*) echo "DIRECT: the public IP is on an interface" ;;
*) echo "BEHIND NAT: no interface has $PUBLIC_IP (local: $(echo $LOCAL_IPS))" ;;
esac- DIRECT (typical VPS) → continue with 4b + 4c.
- BEHIND NAT → pick ONE:
- iroh transport (the automatic fallback — zero network config). Do
nothing here and skip 4b/4c: with no
AURA_PUBLIC_HOSTpersisted, the server selects iroh automatically for aaura-call remotecall — no.envedit and no port opened. iroh hole-punches through NAT (blind encrypted relay as fallback). - Router port-forwarding — only if the user controls the router AND it
has a real public WAN IP: forward WAN UDP 47821 → this machine's LAN IP,
use the router's WAN IP as
AURA_PUBLIC_HOST, then do 4b + 4c. Under CGNAT (the router's WAN address is itself private) inbound is impossible — use option 1.
- iroh transport (the automatic fallback — zero network config). Do
nothing here and skip 4b/4c: with no
4b. (DIRECT only) Find the server's public address and set
AURA_PUBLIC_HOST to it. This is both what the client dials and what tells the
server to bind all interfaces (a non-loopback value) instead of loopback-only —
you already have it from 4a:
Persist it to the aura .env so every later call reuses it with no argument — the
server reads it back and auto-selects direct. Replace any prior line rather than
appending (the server takes the FIRST AURA_PUBLIC_HOST, so a stale duplicate
would win); use the user's preferred DNS name instead of the IP if they have one:
mkdir -p ~/.config/aura; f=~/.config/aura/.env; touch "$f"; chmod 600 "$f"
{ grep -v '^AURA_PUBLIC_HOST=' "$f" 2>/dev/null; printf 'AURA_PUBLIC_HOST=%s\n' "${PUBLIC_IP}"; } > "$f.tmp"
chmod 600 "$f.tmp"; mv "$f.tmp" "$f"Persist it ONLY for the DIRECT/VPS path — a NAT'd server stays iroh and records
nothing (a stored public host would wrongly push it onto the direct path). Write a
bare AURA_PUBLIC_HOST=<host> line — no quotes, no export prefix.
4c. (DIRECT only) Open the UDP port once with the bundled script. The
default port is 47821; pass a different one only if the user overrides
AURA_PORT.
scripts/aura-open-port.sh # opens UDP 47821 (or: scripts/aura-open-port.sh <PORT>)Behaviour of the script (do not re-implement it — just run it and relay its output):
- It detects the active firewall front-end in priority order
(
ufw→firewalld→nftables→iptables) and opens UDPPORTidempotently. - If it runs as root, it performs the change and prints that it opened the
port. Tell the user: "I opened UDP
PORTon the server firewall." - If it is not root, it does not fail: it prints the exact commands
(with
sudo) for the user to run. Relay those commands verbatim to the user and ask them to run them. - It always prints a final note about filters outside this machine that
must also allow inbound UDP
PORT(the script cannot change them). Relay it to the user. Two cases:- Cloud host: add an inbound UDP
PORTrule in the provider's security group / firewall (AWS, GCP, Azure NSG, DigitalOcean, Hetzner, ...). - Home / office router (server on a LAN behind NAT): the OS firewall is
not enough — the user must add a port-forwarding rule on the router
(WAN UDP
PORT→ this PC's LAN IP) and use the router's public WAN IP asAURA_PUBLIC_HOST(dynamic DNS if it changes). CGNAT: if the ISP gives no real public IP (compare the router's WAN IP withcurl -fsS ifconfig.me), inbound is impossible for the direct transport — so use the iroh transport (persist noAURA_PUBLIC_HOSTand the server picks iroh automatically; it hole-punches and can fall back to a blind encrypted relay, needing no open port), or a VPS, a VPN/overlay (WireGuard / Tailscale), or LOCAL-only calls.
- Cloud host: add an inbound UDP
Opening the port is a one-time onboarding action. You never touch the firewall again on later calls.
The skill is a single self-contained file, skills/SKILL.md. Copy it into the
directory your framework scans for skills — you know that path for your own
runtime. The file is identical for every framework; the per-host details
(context source, dispatch, callback) are examples inside it, so one file
serves every host.
# Claude Code (the default) scans ~/.claude/skills/<name>/:
mkdir -p ~/.claude/skills/voice-call && cp skills/SKILL.md ~/.claude/skills/voice-call/SKILL.mdCreate the directory first (mkdir -p) — otherwise the cp (or a curl -o
if you fetch the file instead) fails with No such file or directory.
For another runtime, substitute its skills directory — e.g.
~/.codex/skills/voice-call/, ~/.hermes/skills/voice-call/, ~/.openclaw/skills/voice-call/, or
wherever your host scans. The file you copy is the same in every case.
On Windows: also copy skills/SKILL_WINDOWS.md into the same voice-call/
skill dir next to SKILL.md (e.g.
cp skills/SKILL_WINDOWS.md ~/.claude/skills/voice-call/SKILL_WINDOWS.md). It
holds the Windows-only deltas; SKILL.md still owns the call flow and now points
to the companion at the top, so the model reads it before running the flow.
The aura-call, aura-call-status, and aura-inbox helpers the skill uses are
already on your PATH — install.sh installed them next to aura-server in
step 2 (the server install ships them; a client-only install does not). Confirm:
command -v aura-call && command -v aura-call-status && command -v aura-inbox && echo "helpers on PATH: OK"Reload your host (e.g. restart Claude Code) so it picks up the new skill. From here the skill owns the call flow.
During a call the user is on the voice line, not watching the chat. So the
host session that orchestrates the call — the skill's Step 4 aura-inbox
watch-loop, plus any edits/bash and delegated sub-agents — must run its tool calls
without blocking on an approval prompt, and the session must be allowed to run
long enough for a whole call. If tool calls need manual approval, the loop
silently freezes on a confirmation nobody can give mid-call, and every dispatch
falls back to a cold worker. Configure your framework once:
1. Auto-approve the orchestrator's tool calls.
- Claude Code — aura already runs the dispatched
claude -psub-agent inacceptEdits; for the live orchestrating session, add Bash allow-rules for the on-PATH helpers:Bash(aura-inbox:*),Bash(aura-call:*),Bash(aura-call-status:*)(note the:before*— that is the Claude Code rule syntax;Bash(aura-inbox*)does NOT match). Run the session under--permission-mode acceptEditsso its file edits don't prompt — butacceptEditsdoes not auto-approve Bash, so those allow-rules are required (not an alternative) oraura-inbox waitfreezes on a prompt nobody can answer mid-call. For a fully unattended loop,--dangerously-skip-permissionsbypasses all prompts instead. - Codex — set the approval policy so the loop's commands don't block, e.g.
--dangerously-bypass-approvals-and-sandbox(or the equivalentapproval_policysetting). Verify the exact knob for your Codex version. - Hermes — the dispatch runs as an unattended worker (
delegate_task), so auto-approve its commands:Also enable aura's direct-dispatch fallback (used when your watch-loop is not running): persist the worker command next to the key, in the same user-globalhermes config set approvals.cron_mode approve~/.config/aura/.env(Hermes' exec tool gives each command a fresh cwd, so a./.envhere would not be found reliably):(printf 'AURA_HERMES_WORKER=hermes -z\n' >> ~/.config/aura/.envhermes -zis Hermes' oneshot mode: prints only the final answer to stdout and bypasses approval prompts — exactly what a headless worker needs. aura appends the task text as the last argument.) And run the in-call watch-loop in the background — Hermes kills foreground tools on any incoming user message (busy_input_mode=interrupt): useterminal(background=true, notify_on_complete=true)as described in the skill's Step 4. - OpenClaw — auto-approve exec so the consult is not gated:
(These are already the defaults for the gateway host; the one-command local-only shortcut
openclaw config set tools.exec.security full openclaw config set tools.exec.ask offopenclaw exec-policy preset yolosets both the config AND the host approvals file. Restart withopenclaw gateway restart.)
2. Raise the time / turn budget so a multi-minute call's watch-loop is not cut off mid-way.
- Hermes:
hermes config set agent.gateway_timeout 1800 hermes config set delegation.child_timeout_seconds 1800 hermes config set max_turns 200 - Claude Code / Codex / OpenClaw — make sure the session/turn cap and any sub-agent timeout are high enough for a full call (no single command; raise whatever limit your runtime enforces).
The Hermes and OpenClaw commands above are the proven settings from the heyarp Hermes/OpenClaw setup, and map to aura's dispatch (Hermes = an unattended
delegate_taskworker; OpenClaw = a gateway consult). aura's Hermes/OpenClaw paths are not yet live-verified, so confirm the exact config keys against your installed framework version. Claude Code is the live-verified default.
Confirm everything is wired without dialing the model. Run the three checks, then gate completion on the checklist at the end — do not report success until every box is checked.
6a. Binaries on PATH. The server must be installed; the client is only
required on the same machine for a LOCAL call.
command -v aura-server && echo "server: OK" || echo "server MISSING — redo step 2"
# The on-PATH host helpers the skill drives (call + status + the orchestrator inbox):
for h in aura-call aura-call-status aura-inbox; do
command -v "$h" >/dev/null && echo "$h: OK" || echo "$h MISSING — redo step 2"
done
# LOCAL only (client lives on the user's machine for REMOTE):
command -v aura-cli && echo "client present (LOCAL ready)" || echo "client not here (expected for REMOTE)"6b. The key resolves. Run the server's startup just long enough to prove the key is found, then stop it before it waits for a caller. The server resolves the key before it binds or mints anything; if the key is missing it exits non-zero immediately with a clear message.
# Run from the server working directory. The key comes from ~/.config/aura/.env
# (or the environment / OS keychain).
# It prints "host = ..." / "composed context ..." and then the connection-string
# line on stderr. Reaching that line proves the key resolved. We stop it there.
timeout 8s aura-server 2>&1 | grep -m1 -E 'AURA_CONNECT=|composed context|host =' \
&& echo "key resolved + server starts: OK" \
|| echo "server did NOT start — check the API key (step 3)"If instead you see no BYOK key found (or no BYOK xAI/OpenAI key found), the
key isn't where step 3 put it — confirm ~/.config/aura/.env holds the line
XAI_API_KEY=... / OPENAI_API_KEY=..., and that no set-but-empty env var of
that name is shadowing it. The connection string printed during this probe is
throwaway (single-use, ~120 s) — it is never used and expires harmlessly.
6c. State-dir agreement. Compare the directory the server posts in-call tasks to with the one the helpers resolve — run BOTH commands and compare:
timeout 8s aura-server 2>&1 | grep -m1 'in-call dispatch inbox at'
aura-inbox alive # prints: ALIVE <absolute inbox dir>The two directories MUST match, or every in-call dispatch silently
cold-fallbacks and aura-call-status reads a stale file. They match
automatically when both run from the same cwd; if your framework's exec tool
starts every command in a fresh/implicit cwd (Hermes, OpenClaw and other
messenger gateways), pin the root ONCE in the GLOBAL aura env file (the same
place Step 3 recommends for the key — a cwd-relative ./.env would be lost
the moment the cwd changes):
mkdir -p ~/.config/aura
printf 'AURA_STATE_DIR=%s\n' "$HOME" >> ~/.config/aura/.envThe server, aura-inbox, and aura-call-status all read that file; the
post-call recap files land under the same root.
6d. (REMOTE) Reachability reminder. You cannot fully verify external UDP reachability from inside the VM. Remind the user that step 4 must be complete on both the OS firewall and the cloud security group / NAT.
✅ Self-check — do NOT report "done" until every box is checked:
-
aura-serveris onPATH(6a) — andaura-clitoo if this is a LOCAL host -
aura-call,aura-call-status, andaura-inboxare onPATH(Step 2) - the API key resolves — the server reaches the connection-string line (6b)
-
aura-inbox aliveprints the SAME directory the 6b probe logged (6c) — else setAURA_STATE_DIR - (REMOTE) UDP 47821 was opened once — OS firewall and cloud SG / NAT (Step 4)
-
skills/SKILL.mdwas copied into your skills directory (Step 5) - your framework is set to auto-approve the orchestrator's tool calls and has a high enough turn/timeout budget for a full call (Step 5b)
- you did NOT re-ask the user beyond the key (and at most the one mode question), and did NOT stop at the build
Any unchecked box → go back to that step and fix it. Only when all boxes are checked do you continue to Step 7 and tell the user how to start a call.
Onboarding sets up the server; the skill runs the call. When the user later asks for a call, OPEN AND FOLLOW the skill you installed in Step 5 — do not run a call from onboarding memory. Onboarding taught you to INSTALL; the skill owns the call protocol (launch, connection string, connect, orchestrate, recap) and its Step 4.1 check prevents the most common silent failure.
Onboarding is done. Tell the user in plain language how to start one — they run nothing by hand; the skill launches the server for them:
"To start a call, say 'call me' (or use the host's call command) in your chat.
I'll launch the voice server; you'll either be connected automatically (local) or
I'll send you a one-line AURA_CONNECT=... aura-cli command to paste on your
machine (remote)."
The server is env-driven (no config file is loaded); the skill launches it and
aura-call passes the environment through. All optional:
AURA_PORT/AURA_PUBLIC_HOST/AURA_TRANSPORT— UDP port, the public host clients dial, and the REMOTE transport. The server auto-selects the transport fromAURA_PUBLIC_HOST: a reachable value →direct, none on a REMOTE call →iroh.AURA_TRANSPORT=iroh|directforces it; you rarely set it by hand (all covered above).
- Client can't reach a REMOTE server / call never connects. The UDP port is
almost always blocked outside the VM by the cloud security group / NAT.
The in-VM firewall (step 4c) is necessary but not sufficient — add an
inbound UDP 47821 rule in the cloud provider console. Confirm
AURA_PUBLIC_HOSTis the reachable public IP/DNS name, not a private/loopback address. If the server is behind NAT/CGNAT (Step 4a says BEHIND NAT), stop fighting the firewall — persist noAURA_PUBLIC_HOSTso the server uses iroh. aura-serverexits immediately with a key error (no BYOK key found). The API key did not resolve. Check: does~/.config/aura/.env(step 3) hold aXAI_API_KEY=.../OPENAI_API_KEY=...line? is a set-but-empty env var of that name shadowing it (a set var beats the.env)? Re-do step 3. Never print the key to debug it.- Build error mentioning
alsa/libasound/asound. That dependency is client-only — onlyaura-clipulls cpal/ALSA. If you are building the server, you do not need it; make sure you ran./install.sh --server(not the default which also builds the client). If you are building the client on Linux, install the ALSA dev headers:libasound2-dev(Debian/Ubuntu,apt),alsa-lib-devel(Fedora/RHEL,dnf),alsa-lib(Arch,pacman),alsa-lib-devel(openSUSE,zypper). macOS and Windows need no extra audio package. cargo/rustupnot found. Install Rust as in step 2 (curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y, thensource "$HOME/.cargo/env"); the pin file selects 1.92.0 automatically.aura-server/aura-clinot found after install.~/.local/binis not onPATHyet. Restart the shell orsource ~/.bashrc(or~/.zshrc/~/.profile), or run with the full path~/.local/bin/aura-server.scripts/aura-open-port.shprintedsudocommands. That means it ran without root and could not change the firewall itself — relay those exact commands to the user to run, then re-verify reachability.