amplink
v0.1.0
Published
Universal mobile viewport for AI coding sessions
Maintainers
Readme
amplink
To install dependencies:
bun installTo run:
bun run index.tsThis project was created using bun init in bun v1.3.11. Bun is a fast all-in-one JavaScript runtime.
Cloudflare voice backend
The repo now includes a Cloudflare Workers backend under cloudflare/worker.ts with a Durable Object per voice session in cloudflare/amplink-session.ts. It provides:
GET /sessionsto list a user's sessions from D1POST /start-sessionto create a new voice sessionGET /ws?session={id}&device=mobileto attach the mobile client to the session Durable ObjectGET /listen?token={token}to attach the desktop listener over WebSocketGET /adminfor the built-in voice admin web UIGET/PUT /api/voice-profileto read or update the live per-user voice profilePOST /api/voice-previewto preview the current voice/personality before saving/control/*routes from cloudflare/control.ts for legacy desktop endpoint registration in KV
BYO Cloudflare setup
If you want Amplink to deploy into your own Cloudflare account, use the local installer:
bun run setup:cloudflareIt will:
- open a prefilled Cloudflare API token page for
Workers Scripts,Workers KV Storage, andD1 - prompt for your Cloudflare account ID and API token
- optionally prompt for your ElevenLabs API key and voice ID
- create or reuse the required KV namespaces and D1 database
- create a
workers.devsubdomain if the account does not have one yet - generate a local Wrangler config under
.dev/cloudflare/ - apply the D1 migration, deploy the Worker, and set Worker secrets
- write local listener settings like
AMPLINK_CONTROL_BASE_URLandAMPLINK_DESKTOP_LISTENER_TOKENinto.env.local
Notes:
- The Cloudflare API token is used locally during setup and is not written back to
.env.localby the installer. bun run setup:cloudflareignores the genericCLOUDFLARE_API_TOKENon purpose. If you want to preseed setup without a prompt, useAMPLINK_CLOUDFLARE_SETUP_TOKENinstead.- The installer starts with an account-token template URL and only offers a user-token fallback if account-token creation is not possible. Account tokens require Administrator or Super Administrator access in Cloudflare.
- Before creating the token, confirm the page includes
Account -> Workers Scripts -> Edit,Account -> Workers KV Storage -> Edit, andAccount -> D1 -> Edit. If Cloudflare drops one from the template, add it manually.
Deploy
- Create a D1 database named
amplink_db, a KV namespace for desktop registrations, and a KV namespace for voice profiles, then replace the IDs in wrangler.toml. - Set the ElevenLabs secret with
bunx wrangler secret put ELEVENLABS_API_KEY. - Set
DESKTOP_LISTENER_TOKENwithbunx wrangler secret put DESKTOP_LISTENER_TOKENfor the outbound desktop listener. - Optionally set
CONTROL_SHARED_SECRETif you want/control/*registration and legacy HTTP desktop dispatch calls to require a bearer token. - Generate Cloudflare types with
bun run cf:types. - Apply the D1 migration with
bun run scripts/run-wrangler.ts d1 migrations apply amplink_db. - Run local dev with
bun run cf:devor deploy withbun run cf:deploy.
The Cloudflare scripts now load both /Users/arach/.env.local and .env.local automatically, so CLOUDFLARE_API_TOKEN does not need to be manually exported first.
Test
Use a simple user identifier header until auth is wired in:
curl -X POST http://127.0.0.1:8787/start-session \
-H "Content-Type: application/json" \
-H "X-Amplink-User: demo-user" \
-d '{"title":"Demo voice session"}'Start the desktop listener if you want the Worker to forward heavy tasks through the persistent control socket:
bun run desktop:listenIf you want the local bridge and the desktop listener together in one terminal, use:
bun run desktop:upThen connect a WebSocket client to the returned websocketUrl and send either a raw text string or a JSON envelope such as:
{
"type": "voice.input",
"text": "Open the relay logs and summarize the last failure"
}The Durable Object will generate a quick voice reply with Workers AI, synthesize it through ElevenLabs when configured, and forward heavy tasks to the connected desktop listener over the control WebSocket.
Voice admin
Open the built-in control panel at:
https://amplink.arach.workers.dev/adminIt edits a live per-user voice profile with:
- ElevenLabs
voiceId speechRatefor faster or slower spoken deliverypersona(operator,warm,dry,roast)roastFrequencyttsMode(both,ack,result,off)- optional custom style instructions
The profile is stored in KV and applied on the next voice turn without rebuilding the iPhone app.
If CONTROL_SHARED_SECRET is set in Cloudflare, open the admin with:
https://amplink.arach.workers.dev/admin?token=YOUR_SECRETLive smoke
Run a deployed end-to-end smoke test against the Worker:
bun run smoke:cloudflareDefaults:
AMPLINK_SMOKE_BASE_URL=https://amplink.arach.workers.devAMPLINK_SMOKE_REQUIRE_TTS=1AMPLINK_SMOKE_TEXT="hello from the live smoke test"
Useful overrides:
AMPLINK_SMOKE_TEXT="open the relay logs" bun run smoke:cloudflare
AMPLINK_SMOKE_REQUIRE_TTS=0 bun run smoke:cloudflare
AMPLINK_SMOKE_BASE_URL="http://127.0.0.1:8787" bun run smoke:cloudflareDesktop listener
The default desktop path is now one outbound WebSocket from your machine to Cloudflare. The listener script lives at desktop-listener.ts and proxies each incoming task into the local Amplink bridge on ws://127.0.0.1:7888 by using the normal session/list, prompt/send, and session/snapshot RPC flow.
Run it like this:
bun run desktop:listendesktop-listener.ts loads .env.local automatically. The current local file already stores AMPLINK_CONTROL_BASE_URL, AMPLINK_DESKTOP_LISTENER_TOKEN, and AMPLINK_BRIDGE_URL, so the Bun script is the only command you need.
The combined desktop:up command uses that same AMPLINK_BRIDGE_URL and starts the bridge on its port. The current local default is ws://127.0.0.1:17888 to stay out of the way of anything already using 7888.
Current listener behavior:
- reconnects every 3 seconds if the Cloudflare socket closes
- sends a
listener.hellohandshake on connect - expects exactly one active local bridge session unless the task includes an explicit target session
- sends back
task.resultwith{ sessionId, result, status }on the same WebSocket
The old POST /dispatch bridge endpoint is still available as a fallback path, but it is no longer the primary setup.
