BYOA link
SkillCommunicationOnboard a Gradient Bang operator to run a Bring-Your-Own-Agent (BYOA) — logs in with email/password, claims a corp ship as BYOA, generates a per-ship wake secret, and writes `.env.byoa` for the `uv run byoa` CLI. Usage `/byoa-link [env]` (env defaults to `prod`; `live` is an accepted synonym for `prod`).
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the BYOA link skill
What this skill tells your AI
The instructions your AI receives, as published by pipecat-ai/gradient-bang in .claude/skills/byoa-link/SKILL.md and read by ahel’s review.
env defaults to
prodwhen omitted.liveis accepted as a user-facing synonym forprod(e.g./byoa-link liveis equivalent to/byoa-link prodand to/byoa-linkwith no arg).localis the only other valid value.
Walks an operator through everything they need to run uv run byoa against a Gradient Bang corp ship. End state: a populated .env.byoa at the main repo root (mode 0600), a ship claimed as BYOA in private mode with a wake secret registered server-side, and clear next-step instructions for the operator.
Always write the env file to the main repo root, never to the current directory. The skill is often invoked from a git worktree (.claude/worktrees/<name>/), and a file dropped there is invisible from the operator's normal checkout. Resolve the destination via git rev-parse --path-format=absolute --git-common-dir (its parent is always the main worktree root, even when invoked from a linked worktree). Only honor --out when explicitly passed.
Parameters
/byoa-link [env] [--force] [--ship-id <uuid>] [--out <path>]
- env:
prod(default, also acceptsliveas a synonym) orlocal. Picks the game server endpoint:prod/live→https://api.gradient-bang.com/functions/v1(operator-facing; no env file needed). If the operator types nothing, this is what they get.local→ sourcesSUPABASE_URLfrom.env.supabase(http://127.0.0.1:54321whennpx supabase startis running)
- --force: overwrite an existing
.env.byoawithout prompting - --ship-id: skip the ship picker; use this corp ship directly
- --out: write the env file somewhere other than
<repo-root>/.env.byoa. Without this flag, the file always lands at the main repo root regardless of cwd.
The dev env was dropped — it required internal-only env files (.env.cloud.dev, EDGE_API_TOKEN) that operators don't have. Internal team members testing against dev should run local with their dev Supabase URL exported in shell.
Pre-flight
Refuse to proceed if any of these is true. Surface a clean error and stop:
- For
local:${SUPABASE_URL}is unset in.env.supabase(or the file is missing entirely). - The resolved destination already has a
.env.byoaand--forcewas not passed. Check the resolved repo-root path (or--out), not cwd. SUBAGENT_BUS_DATABASE_URLis only required for the local-dev wake daemon (byoa --serve); not for setup. Don't pre-flight it here.
Auth header cheat-sheet
/ship_byoa_configure uses getAuthenticatedUser() — same pattern as verify_token, user_character_create, reset-password. JWT-only; no admin token. All BYOA actions (list, claim, clear, set) go through it.
| Endpoint | Scheme | Headers required |
|---|---|---|
/login | public | Content-Type only |
/ship_byoa_configure | getAuthenticatedUser() | Authorization: Bearer <user_jwt> |
Steps
1. Resolve the game server URL
For prod or live (the default — if the operator types nothing, treat as prod; live is a user-facing synonym):
SUPABASE_URL=https://api.gradient-bang.com
Hardcoded — operator never types it. (If the CNAME proxy isn't live, fall back to the direct Supabase project URL; this URL is public.)
Anything other than prod, live, or local should error out with unknown env: <value>; expected prod | live | local before any network call.
For local:
set -a && source .env.supabase && set +a
# SUPABASE_URL now points at http://127.0.0.1:54321 (or whatever the file has).
All curls in steps 2–6 append /functions/v1/<endpoint> to SUPABASE_URL, so the resolved value must NOT include /functions/v1.
BYOA_WAKE_SECRET is the per-ship bearer that authenticates wake_agent → wake-receiver (local daemon or operator's Vercel Function). Generate a fresh random hex string (openssl rand -hex 32) per ship — never share across ships. The skill writes the value into .env.byoa AND sends the same value to us via ship_byoa_configure { action: 'set', wake_secret }. We encrypt at rest and never return it to clients.
The bus channel is allocated server-side per voice session and injected into the harness env by wake_agent. Operators never see or set a channel. The local-dev wake daemon (uv run byoa --serve) loads .env.byoa first, then falls back to .env.bot for SUBAGENT_BUS_DATABASE_URL when running from a bot checkout. Standalone operators set it in shell env or add it to .env.byoa by hand.
2. Log the operator in
Prompt for email + password (unless supplied as positional args):
curl -s -X POST "${SUPABASE_URL}/functions/v1/login" \
-H "Content-Type: application/json" \
-d '{"email": "<email>", "password": "<password>"}'
Extract session.access_token and the character list (characters). On any non-success response (4xx, success: false), surface the error and stop.
3. Pick the operator's character
If exactly one character → use it. If multiple → list by name + character_id (short prefix) and ask the operator to pick.
4. List corp ships
curl -s -X POST "${SUPABASE_URL}/functions/v1/ship_byoa_configure" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{"character_id": "<character_id>", "action": "list"}'
Response shape: { success, action: "list", ships: [{ ship_id, name, sector, byoa_owner_character_id_prefix, claimable_by_me }, ...] }. Filter to claimable_by_me === true.
If --ship-id was passed, validate it appears in this filtered list. Otherwise present (name + 8-char ship_id prefix + sector) and ask the operator to pick.
If the chosen ship is already claimed by the same operator (byoa_owner_character_id_prefix matches the operator's character prefix), the claim in step 5 is idempotent — proceed anyway.
5. Claim the ship as BYOA
curl -s -X POST "${SUPABASE_URL}/functions/v1/ship_byoa_configure" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"character_id": "<character_id>",
"ship_id": "<ship_id>",
"action": "claim",
"mode": "private"
}'
Confirm the response has byoa_owner_character_id matching the operator's character_id. A 409 ship_busy means the ship has an active task — ask the operator to wait or stop it; do not auto-retry.
6. Generate + register the per-ship wake secret
Generate a fresh random hex bearer (openssl rand -hex 32). The wake secret is a per-ship, operator-side credential. Capture the freshly-generated hex in memory; write it to .env.byoa in step 7 AND send the same value to us so wake_agent can sign outbound POSTs to the operator's wake receiver:
curl -s -X POST "${SUPABASE_URL}/functions/v1/ship_byoa_configure" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"character_id": "<character_id>",
"ship_id": "<ship_id>",
"action": "set",
"wake_secret": "<generated hex>"
}'
Ask the operator whether they want a custom wake URL (the URL wake_agent POSTs to):
- Leave unset (recommended for local dev):
wake_agentfalls back tohttp://host.docker.internal:8765/wake— the default port foruv run byoa --serverunning on the host. No further config needed. - Set explicitly: pass
source_urlon the samesetcall (or a follow-up). Required for Vercel/prod deploys (https://<your-project>.vercel.app/api/wake) and for local daemons listening on a non-default port.
For production also set BYOA_WAKE_SECRET=<same hex> as project env on the operator's Vercel project (where their wake receiver runs).
7. Write .env.byoa
Path: --out value, or <main-repo-root>/.env.byoa if not provided. Never write to cwd — the skill is regularly invoked from a worktree, and writing there hides the file from the operator's normal checkout. File mode 0600.
Resolve the main repo root with git rev-parse --path-format=absolute --git-common-dir — its parent is the main worktree root whether the caller is in the main checkout or a linked worktree. The env.byoa.example template also lives at that root.
PROJECT_ROOT="$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")"
OUT_PATH="${OUT_OVERRIDE:-$PROJECT_ROOT/.env.byoa}"
umask 077
cp "$PROJECT_ROOT/env.byoa.example" "$OUT_PATH"
python3 - "$OUT_PATH" "<character_id>" "<ship_id>" "<wake_secret_hex>" <<'PY'
import sys, pathlib
path, char_id, ship_id, secret = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
text = pathlib.Path(path).read_text()
text = text.replace("BYOA_CHARACTER_ID=\n", f"BYOA_CHARACTER_ID={char_id}\n", 1)
text = text.replace("BYOA_SHIP_ID=\n", f"BYOA_SHIP_ID={ship_id}\n", 1)
text = text.replace("BYOA_WAKE_SECRET=\n", f"BYOA_WAKE_SECRET={secret}\n", 1)
pathlib.Path(path).write_text(text)
PY
chmod 600 "$OUT_PATH"
Only the three required values are filled in; every other option (BYOA_PROMPT_FILE, TASK_LLM_*, BYOA_TOOL_CALL_TIMEOUT_SECONDS, …) stays commented out at its default. SUBAGENT_BUS_DATABASE_URL is not in the template — uv run byoa --serve picks it up from .env.bot via fallback when running from the bot checkout. Standalone operator deploys without a bot checkout need to set it in the shell env or add it to .env.byoa by hand.
Verify the file is 0600, sits at the resolved repo root (not a worktree), and all three values are populated after writing; surface a warning if not.
8. Print next steps
Echo a copy-pasteable summary the operator can act on:
- Where the env file was written.
- Author
./prompt.md(≤ 8 KB, appended to the base task-agent prompt) and setBYOA_PROMPT_FILE=./prompt.md(or inlineBYOA_PROMPT=...) in.env.byoa. - Local dev (env !=
prod): start the local wake daemon (separate terminal):
The daemon readsuv run byoa --serve.env.byoaand waits for wakes fromwake_agent. As long as the ship has no per-shipsource_urlset,wake_agentdefaults tohttp://host.docker.internal:8765/wakeand routes to the daemon automatically using the per-shipBYOA_WAKE_SECRET. Only setDEFAULT_BYOA_SOURCE_URLon the edge env to override the default for all unconfigured ships. - Production (env =
prod): run/byoa-deploy-vercel prodnext. It deploys the template at deployment/vercel/ to the operator's Vercel project (production by default — preview is SSO-gated), pushesBYOA_WAKE_SECRET/TASK_LLM_*/ the matching*_API_KEYfrom.env.byoa, health-checks, then logs the operator in again to auto-registersource_urlviaship_byoa_configure. Pass--access-token <jwt>to reuse the JWT you just minted here and skip the second login prompt. - Point at
docs/byoa.mdfor full env / config reference. - Rotate the wake secret by re-running
/byoa-link <env> --force --ship-id <ship>— this writes a fresh value and updatesship_byoa_configurein one shot.
Failure modes
- 401 from /login: bad credentials. Don't retry.
- No characters: direct to
/character-createfirst. - No corp ships: operator isn't in a corp or their corp has no ships.
- 409 ship_busy on claim: ship is mid-task. Don't auto-retry.
- 403 on claim or set: caller's JWT is for a different operator (not the ship owner). Stop and re-check which account was logged in.
- 401 "No authorization token provided" / "Invalid or expired token" from /ship_byoa_configure: the
Authorization: Bearer <jwt>header is missing or the JWT is expired. Re-run/loginto mint a fresh one.
What this skill does NOT do
- Write the operator's custom prompt — their authored content, out of scope.
- Deploy the operator's BYOA agent.
- Modify the bot or edge env files — only reads them.
Signals
- GitHub stars
- 419
- Forks
- 67
- Last commit
- Jun 2026
Advanced
- Catalog kind
- skill
- Gateway key
byoa-link- Source
- github.com/pipecat-ai/gradient-bang