Package Exports
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (walla-page) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Walla Page
Walla Page is a CLI-first wall runtime for agents.
One room maps to one Cloudflare Durable Object. A controller (CLI/API/agent) schedules scenes and speech. Display clients subscribe over WebSocket and render the live state.
Package Manager
This project uses npm.
- Install dependencies with
npm install - Run the CLI locally with
npm run walla -- ... - Publish the package to npm as
walla-page
Keeping one lockfile is simpler for contributors, and npm is the most common default in open source JavaScript projects.
Install
The published npm package is just the Node CLI. Installing it does not require Wrangler.
For CLI users:
npm install -g walla-page
walla helpFor local development in this repo:
npm installWrangler is only needed if you are developing or deploying the Cloudflare worker from this repository.
Contributing
npm install
npm run walla -- helpScope (Current Build)
- Control plane is CLI/API only
- Display UI is receiver-only (no room creation web app)
- No R2 dependency
This keeps the hackathon surface area small and testable.
Core Capabilities
- Launch a room onto Chromecast from a desktop control page (
cast) - Show fullscreen HTML now (
show, alias:push) - Schedule fullscreen HTML for later (
schedule) - Generate ElevenLabs speech that plays over the current wall (
say) - Clear the current wall without deleting the room (
clear) - Delete room state and disconnect clients (
delete,delete --force)
Current Behavior Notes
delete --forceis the explicit “disconnect clients and remove room state” path.
Security Model
- Capability token auth for room operations
- Separate room-control and display token roles
- Display page requires user click (
Load Wall) before connecting/playing audio - Scene HTML is rendered in a sandboxed iframe (
srcdoc) - TTS is rejected unless at least one display websocket is connected
- TTS has server-side word limit (
TTS_MAX_WORDS) - CLI validates
sayword count up front (--max-words)
Room Model
One room contains:
- Current scene
- Scheduled upcoming scenes
- Pair tokens for room control and display access
- Live websocket sessions
- Room settings (for example display websocket limit)
Display Fanout and Limits
Displays are multi-screen by default.
- Default: public display link per room
- Default: unlimited display websocket connections per room
- Optional cap at room creation via
displayLimit - When capped and full, new display ws attempts return
429
CLI flag:
walla create --display-limit <n>walla create --private-displayn >= 1sets a cap0means unlimited--private-displayrequires a display token instead of a bare room link
Alarm + Lifecycle
Each room maintains one DO alarm set to the next relevant timestamp:
- next scheduled scene start
- current scene end
- cleanup deadline
On alarm:
- expire finished scene
- activate due scenes
- evaluate cleanup conditions
- set next alarm
Cleanup Semantics
Automatic
- When last socket disconnects, room sets
idleSinceandcleanupAt - Default idle TTL is 7 days (
ROOM_IDLE_TTL_MS=604800000) - If no sockets and no future scenes at cleanup time, room storage is deleted
Explicit
DELETE /api/rooms/:roomIdwalla delete [--force]
Without --force, delete fails with 409 if sockets are connected.
With --force, all sockets are closed and room state is removed.
CLI
Run:
npm run walla -- helpCommands:
walla create [--display-limit <n>] [--private-display]walla cast [--open]walla clearwalla delete [--force]walla configwalla logoutwalla statuswalla quickstartwalla show <file.html> [--title <title>] [--duration <seconds>]walla push <file.html> [--title <title>] [--duration <seconds>]walla schedule <file.html> --at <time> [--title <title>] [--duration <seconds>]walla say <text> [--at <time>] [--title <title>] [--voice-id <id>] [--max-words <n>]
Config file:
~/.config/wallapage/config.jsonHTTP API
Room lifecycle:
POST /api/roomsDELETE /api/rooms/:roomId(?force=1for forced delete)
Room operations:
GET /api/rooms/:roomId/statePOST /api/rooms/:roomId/pairPOST /api/rooms/:roomId/schedulePOST /api/rooms/:roomId/ttsPOST /api/rooms/:roomId/speakPOST /api/rooms/:roomId/showcaseGET /api/rooms/:roomId/ws
Create payload:
{
"displayLimit": 0,
"publicDisplay": true
}displayLimit >= 1: cap displaysdisplayLimit = 0or omitted: unlimitedpublicDisplay = trueor omitted: bare room display linkpublicDisplay = false: token-protected display link
Display route:
- Public:
GET /rooms/:roomId - Private:
GET /rooms/:roomId?token=...
Control route:
GET /rooms/:roomId/control
Cast receiver route:
GET /cast/receiver
Built-in audio route (allow-listed asset endpoint):
GET /audio/campfire-demo.mp3
Quickstart
Install and run local worker:
npm install
cp .dev.vars.example .dev.vars
# edit .dev.vars and set BASE_URL and CAST_APP_ID for local testing when needed
npm run devCreate room (unlimited displays):
npm run walla -- createOpen the control page and cast to a TV:
npm run walla -- cast
npm run walla -- cast --openOr create room with display cap:
npm run walla -- create --display-limit 4The control page is the preferred launch path. It loads a Cast button and can also open the browser display as a fallback.
Drive the room:
npm run walla -- show ./examples/campfire.html
npm run walla -- say "Dinner in five minutes."
npm run walla -- clear
npm run walla -- status
npm run walla -- delete
# if displays are still connected:
npm run walla -- delete --forceEnvironment
Worker vars:
BASE_URLCAST_APP_IDROOM_IDLE_TTL_MSCREATE_ROOM_LIMITCREATE_ROOM_WINDOW_MSTTS_LIMITTTS_WINDOW_MSTTS_MAX_WORDSDISPLAY_TOKEN_TTL_MSPRODUCER_TOKEN_TTL_MS
Secrets:
ELEVENLABS_API_KEY(required forsay//tts)ELEVENLABS_VOICE_ID(optional default voice)
Notes:
CAST_APP_IDis a normal worker var, not a secret- keep production Cast app IDs out of git and set them per environment
- use
.dev.varsfor local development
Local Development
Wrangler loads local variables from .dev.vars.
Start from the example file:
cp .dev.vars.example .dev.varsExample:
BASE_URL=http://127.0.0.1:8787
CAST_APP_ID=20C7AD7F
ELEVENLABS_API_KEY=
ELEVENLABS_VOICE_ID=Notes:
CAST_APP_IDis only needed if you want the Cast control page to launch a custom receiverELEVENLABS_API_KEYis only needed forsay//tts- for local-only browser display work, you can leave
CAST_APP_IDblank
Deployed Worker Setup
For deployed environments, set CAST_APP_ID as a normal Worker environment variable in Cloudflare, not as a secret.
You have two reasonable options:
- Cloudflare dashboard
- Go to
Workers & Pages - Open the
wallapageworker - Open
SettingsthenVariables - Add
CAST_APP_IDas a plain text variable - Add
ELEVENLABS_API_KEYas a secret
- Wrangler config per environment
Define environment-specific vars in wrangler.jsonc and deploy with --env.
Example:
{
"name": "wallapage",
"main": "src/index.ts",
"vars": {
"BASE_URL": "https://dev.example.com"
},
"env": {
"production": {
"vars": {
"BASE_URL": "https://walla.page",
"CAST_APP_ID": "20C7AD7F"
}
}
}
}Deploy a named environment:
npx wrangler deploy --env productionCloudflare docs:
- Environment variables: https://developers.cloudflare.com/workers/configuration/environment-variables/
- Wrangler environments: https://developers.cloudflare.com/workers/wrangler/environments/
Secrets
Set secret:
npx wrangler secret put ELEVENLABS_API_KEYSet a secret for a named Wrangler environment:
npx wrangler secret put ELEVENLABS_API_KEY --env productionDo not use wrangler secret put for CAST_APP_ID. It is not secret and the client-side control page exposes it anyway.
Chromecast Setup
To use the custom Cast receiver flow, the Google Cast SDK Developer Console must match the worker config.
Required setup:
- Create or edit a Custom Receiver application in the Google Cast SDK Developer Console.
- Set the Receiver Application URL to:
https://walla.page/cast/receiver- Register each Chromecast device for testing if the Cast app is unpublished.
- Use the resulting app ID as
CAST_APP_IDin your Worker environment. - Leave
Google Cast for Audiounchecked unless you intentionally want audio-only speakers to appear.
Google Cast docs:
- Registration and test devices: https://developers.google.com/cast/docs/registration
- Web Receiver codelab: https://developers.google.com/cast/codelabs/cast-receiver
Deploy:
npm run deployTesting
Typecheck:
npm run checkSmoke test:
npm run test:smokeFuture (Post-Hackathon)
- Multi-tenant room ownership and room listing
- PIN-based short display URLs
- Expanded allow-listed audio catalog
- More policy controls (rate/quotas by room/tenant)