JSPM

walla-page

1.0.2
    • ESM via JSPM
    • ES Module Entrypoint
    • Export Map
    • Keywords
    • License
    • Repository URL
    • TypeScript Types
    • README
    • Created
    • Published
    • Downloads 11
    • Score
      100M100P100Q71342F
    • License MIT

    CLI for controlling Walla Page rooms from Node.

    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 help

      For local development in this repo:

      npm install

      Wrangler is only needed if you are developing or deploying the Cloudflare worker from this repository.

      Contributing

      npm install
      npm run walla -- help

      Scope (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 --force is 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 say word 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-display
      • n >= 1 sets a cap
      • 0 means unlimited
      • --private-display requires 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:

      1. expire finished scene
      2. activate due scenes
      3. evaluate cleanup conditions
      4. set next alarm

      Cleanup Semantics

      Automatic

      • When last socket disconnects, room sets idleSince and cleanupAt
      • 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/:roomId
      • walla 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 -- help

      Commands:

      • walla create [--display-limit <n>] [--private-display]
      • walla cast [--open]
      • walla clear
      • walla delete [--force]
      • walla config
      • walla logout
      • walla status
      • walla quickstart
      • walla 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.json

      HTTP API

      Room lifecycle:

      • POST /api/rooms
      • DELETE /api/rooms/:roomId (?force=1 for forced delete)

      Room operations:

      • GET /api/rooms/:roomId/state
      • POST /api/rooms/:roomId/pair
      • POST /api/rooms/:roomId/schedule
      • POST /api/rooms/:roomId/tts
      • POST /api/rooms/:roomId/speak
      • POST /api/rooms/:roomId/showcase
      • GET /api/rooms/:roomId/ws

      Create payload:

      {
        "displayLimit": 0,
        "publicDisplay": true
      }
      • displayLimit >= 1: cap displays
      • displayLimit = 0 or omitted: unlimited
      • publicDisplay = true or omitted: bare room display link
      • publicDisplay = 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 dev

      Create room (unlimited displays):

      npm run walla -- create

      Open the control page and cast to a TV:

      npm run walla -- cast
      npm run walla -- cast --open

      Or create room with display cap:

      npm run walla -- create --display-limit 4

      The 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 --force

      Environment

      Worker vars:

      • BASE_URL
      • CAST_APP_ID
      • ROOM_IDLE_TTL_MS
      • CREATE_ROOM_LIMIT
      • CREATE_ROOM_WINDOW_MS
      • TTS_LIMIT
      • TTS_WINDOW_MS
      • TTS_MAX_WORDS
      • DISPLAY_TOKEN_TTL_MS
      • PRODUCER_TOKEN_TTL_MS

      Secrets:

      • ELEVENLABS_API_KEY (required for say / /tts)
      • ELEVENLABS_VOICE_ID (optional default voice)

      Notes:

      • CAST_APP_ID is a normal worker var, not a secret
      • keep production Cast app IDs out of git and set them per environment
      • use .dev.vars for local development

      Local Development

      Wrangler loads local variables from .dev.vars.

      Start from the example file:

      cp .dev.vars.example .dev.vars

      Example:

      BASE_URL=http://127.0.0.1:8787
      CAST_APP_ID=20C7AD7F
      ELEVENLABS_API_KEY=
      ELEVENLABS_VOICE_ID=

      Notes:

      • CAST_APP_ID is only needed if you want the Cast control page to launch a custom receiver
      • ELEVENLABS_API_KEY is only needed for say / /tts
      • for local-only browser display work, you can leave CAST_APP_ID blank

      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:

      1. Cloudflare dashboard
      • Go to Workers & Pages
      • Open the wallapage worker
      • Open Settings then Variables
      • Add CAST_APP_ID as a plain text variable
      • Add ELEVENLABS_API_KEY as a secret
      1. 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 production

      Cloudflare docs:

      Secrets

      Set secret:

      npx wrangler secret put ELEVENLABS_API_KEY

      Set a secret for a named Wrangler environment:

      npx wrangler secret put ELEVENLABS_API_KEY --env production

      Do 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:

      1. Create or edit a Custom Receiver application in the Google Cast SDK Developer Console.
      2. Set the Receiver Application URL to:
      https://walla.page/cast/receiver
      1. Register each Chromecast device for testing if the Cast app is unpublished.
      2. Use the resulting app ID as CAST_APP_ID in your Worker environment.
      3. Leave Google Cast for Audio unchecked unless you intentionally want audio-only speakers to appear.

      Google Cast docs:

      Deploy:

      npm run deploy

      Testing

      Typecheck:

      npm run check

      Smoke test:

      npm run test:smoke

      Future (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)