WWWAND

World Wide Wand

Talk to the web. WWWAND is an AI voice assistant that connects to any website and lets you interact entirely through natural conversation.

Powered by Data2Link
Stored using JJFS
Setup lives local
WWWAND
World Wide Wand
ALPHA
🔗
Connect data2link
Enter your data2link API key. WWWAND uses it to store your settings and power AI. You can create a key at data2l.ink.
🔑
One more thing
WWWAND uses ElevenLabs for voice. Paste your API key below to enable speech synthesis and recognition. You can update this anytime in settings.
WWWAND v0.1.18
🪄
Say "Hey Wanda", tap the button, or type below
↓ Scroll to bottom
   
Dev Docs
wwwand-alpha · v0.1.18

WWWAND Compatibility Standard

Make your site voice-accessible. Serve a static manifest and handle actions — on your server or directly in the browser — and your users can interact with your service entirely through natural conversation.

Overview

A WWWAND-compatible site exposes a manifest under the /.wwwand/ path. When a user asks Wanda about a registered site, the AI reads the manifest to understand what's possible, then executes the appropriate action. Actions can be handled in two ways: server mode (default) POSTs to your server's action endpoint, while client mode relays the action to your site's open browser tab via a lightweight script — no server required.

WWWAND ≠ the open web. Sites are not automatically accessible. Users register compatible sites in their settings, and Wanda learns their capabilities at that point. This keeps interactions fast, private, and intentional.

Manifest Endpoint

GET /.wwwand/manifest.json

Returns a JSON description of your site's identity, version compatibility, and available actions. Must be publicly accessible — no auth required.

{
  "wwwand": {
    "standard": "wwwand-alpha",
    "version": "0.1.18",
    "minClientVersion": "0.1.18",
    "actionMode": "server"  // "server" (default) | "client" — see Client-Side Actions below
  },
  "site": {
    "name": "My App",
    "description": "A brief description Wanda reads to understand context",
    "url": "https://myapp.com"
  },
  "auth": {
    "required": true,
    "type": "bearer"  // "bearer" | "apiKey" | "none"
  },
  "intents": {
    "media": {
      "itemTerms": ["product", "item"],
      "actions": {
        "search": "search",
        "add": "addToCart"
      }
    }
  },
  "actions": [
    {
      "name": "search",
      "description": "Search for products by keyword",
      "type": "read",  // "read" | "write"
      "params": [
        { "name": "query", "type": "string", "required": true, "description": "Search term" },
        { "name": "limit", "type": "number", "required": false, "description": "Max results" }
      ]
    },
    {
      "name": "addToCart",
      "description": "Add a product to the user's cart",
      "type": "write",
      "params": [
        { "name": "productId", "type": "string", "required": true, "description": "Product identifier" }
      ]
    }
  ]
}

Manifest Fields

FieldRequiredDescription
wwwand.standardreqMust be "wwwand-alpha" — identifies this as a WWWAND-compatible manifest
wwwand.versionreqYour implementation's semver version
wwwand.minClientVersionreqMinimum WWWAND client version required to use this site
wwwand.actionModeopt"server" (default) or "client""client" routes actions to the site's open browser tab instead of POSTing to a server endpoint
wwwand.handleUndispatchedoptSet to true for client-mode sites that want to receive user text when the AI doesn't include a dispatch block. Requires "actionMode": "client". See Intent Resolution below.
site.namereqHuman-readable site name
site.descriptionreqConcise description — Wanda uses this to decide when your site is relevant
site.urlreqCanonical URL of your site — must match the URL the user registers in WWWAND settings
auth.requiredoptWhether requests need a user token
auth.typeopt"bearer", "apiKey", or "none"
actions[]reqArray of available actions. At least one required.
intentsoptDeclares how user intents map to your actions. Wanda expands these into the AI prompt so it knows which action to dispatch for common requests (e.g. "stop" → stopSong). See Intent Resolution below.
intents.<category>.itemTermsoptArray of terms your site uses for its items (e.g. ["song", "track"]). Used by client-side intent matching in onUndispatched.
intents.<category>.actionsoptMaps intent words to action names (e.g. { "play": "playSong", "stop": "stopSong" }). Expanded into the LLM prompt so it dispatches the right action.

Action Endpoint (Server Mode)

POST /.wwwand/action

When actionMode is "server" (the default), Wanda POSTs action requests to this endpoint. The JSON body contains the action name and parameters extracted from the conversation. Note: the userText parameter (the user's original words) is only available in client mode — server endpoints receive { action, params }. For client-side alternatives, see Client-Side Actions below.

// Request body
{
  "action": "search",
  "params": {
    "query": "wireless headphones",
    "limit": 3
  }
}

// Success response
{
  "success": true,
  "result": {
    // Any JSON — Wanda reads this and summarises it for the user
    "items": [
      { "id": "p1", "name": "Sony WH-1000XM5", "price": "$349" }
    ]
  },
  "message": "Found 1 result"  // Optional human-readable summary
}

// Error response
{
  "success": false,
  "error": "Product not found"
}

Authentication (Server Mode)

If your manifest declares auth.required: true, users will be prompted to enter a token when they register your site in WWWAND. That token is stored securely in their data2link account and passed with every server-mode action request. Client-mode sites handle authentication in the browser — the data is already local.

// bearer token — passed as Authorization header
Authorization: Bearer <user_token>

// apiKey — passed as X-API-Key header
X-API-Key: <user_token>

CORS

Your /.wwwand/manifest.json endpoint must allow cross-origin requests. In server mode, the /.wwwand/action endpoint also needs CORS. Client-mode sites only need CORS on the manifest — actions are relayed through the browser, not via HTTP.

// Server mode — manifest + action endpoint
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key

// Client mode — manifest only (actions go through the browser)
Access-Control-Allow-Origin: *

Minimal Example (Server Mode)

A complete Node.js / Express implementation of a server-mode WWWAND-compatible endpoint:

const express = require('express')
const app = express()
app.use(express.json())

const CORS = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-API-Key'
}

app.options('/.wwwand/*', (req, res) => res.set(CORS).sendStatus(204))

app.get('/.wwwand/manifest.json', (req, res) => {
  res.set(CORS).json({
    wwwand: { standard: 'wwwand-alpha', version: '0.1.18', minClientVersion: '0.1.18', actionMode: 'server' },
    site: { name: 'My App', description: 'Does something useful', url: 'https://myapp.com' },
    auth: { required: false },
    actions: [
      {
        name: 'getStatus',
        description: 'Get current app status',
        type: 'read',
        params: []
      }
    ]
  })
})

app.post('/.wwwand/action', (req, res) => {
  const { action, params } = req.body
  if (action === 'getStatus') {
    res.set(CORS).json({ success: true, result: { status: 'All systems operational' } })
  } else {
    res.set(CORS).status(400).json({ success: false, error: `Unknown action: ${action}` })
  }
})

app.listen(3000)
Tip: Keep your action descriptions specific and action-oriented. Wanda's AI reads them verbatim to decide which action to invoke — "Search for items by keyword" is better than "Search".

Client-Side Actions

For static sites, client-only apps, or anything where data lives in the browser (IndexedDB, localStorage, playback state), WWWAND can relay actions directly to your open browser tab instead of POSTing to a server.

How It Works

Your site includes a small client script. When Wanda calls an action, WWWAND relays it to your tab via a cross-origin bridge, your JavaScript handles it, and the result flows back to Wanda. No server, no WebSocket infrastructure, no backend at all.

Tradeoff: The site's tab must be open for actions to work. If no tab is connected, Wanda will tell the user to open the site first. This is already the natural expectation for browser-local apps.

1. Set actionMode in Your Manifest

Add "actionMode": "client" to your manifest's wwwand block. The manifest itself is still a static JSON file served via GET — only the action execution changes.

{
  "wwwand": {
    "standard": "wwwand-alpha",
    "version": "0.1.18",
    "minClientVersion": "0.1.18",
    "actionMode": "client",       // "client" = browser tab handles actions
    "handleUndispatched": true  // opt in to receive user text when AI skips the dispatch block
  },
  "site": { "name": "My Music", "description": "A music player with browser-local songs", "url": "https://mymusic.example" },
  "intents": {
    "media": {
      "itemTerms": ["song", "track"],
      "actions": {
        "play": "playSong",
        "stop": "stopSong",
        "pause": "pauseSong",
        "resume": "resumeSong",
        "next": "nextSong",
        "previous": "previousSong",
        "shuffle": "shuffleSongs"
      }
    }
  },
  "actions": [
    { "name": "listSongs", "description": "List all songs in the library", "type": "read", "params": [] },
    { "name": "playSong", "description": "Play a song by ID", "type": "write", "params": [{ "name": "id", "type": "string", "required": true }] },
    { "name": "stopSong", "description": "Stop playback", "type": "write", "params": [] },
    { "name": "pauseSong", "description": "Pause playback", "type": "write", "params": [] },
    { "name": "resumeSong", "description": "Resume playback", "type": "write", "params": [] },
    { "name": "nextSong", "description": "Skip to next song", "type": "write", "params": [] },
    { "name": "previousSong", "description": "Go back to previous song", "type": "write", "params": [] },
    { "name": "shuffleSongs", "description": "Shuffle the song order", "type": "write", "params": [] }
  ]
}

2. Include the Client Script

Add this script tag to your site. It connects your tab to WWWAND automatically — no configuration needed.

<script src="https://wwwand.com/wwwand-client.js"></script>

3. Register an Action Handler

Handle actions in JavaScript. Return any JSON-serializable value — the client script wraps it in the standard { success, result } response format automatically. Throw an error to signal failure. Async handlers are supported.

<script>
WwwandClient.onAction(async (action, params, userText) => {
  if (action === 'listSongs') {
    const songs = await getAllSongsFromIndexedDB()
    return { songs }
  }
  if (action === 'playSong') {
    // userText lets you correct dispatches — e.g. if the AI sent "play"
    // but the user actually said "next", use userText to detect that:
    if (/\bnext\b/i.test(userText)) return nextSong()
    if (/\bprevious\b/i.test(userText)) return previousSong()
    playSong(params.id)
    return { ok: true }
  }
  throw new Error(`Unknown action: ${action}`)
})
</script>

That's it. The manifest (static JSON) handles discovery, the client script handles execution. No server, no infrastructure — any HTML page can become WWWAND-compatible.

WwwandClient SDK Reference

MethodDescription
onAction(handler)Register a handler called when WWWAND dispatches an action. Receives (action, params, userText). The userText parameter contains the user's original words — useful for dispatch correction (see Intent Resolution below). Return a result object or throw an error. Async handlers are supported.
onUndispatched(handler)Register a handler called when the AI didn't include a dispatch block but your site opted in via handleUndispatched: true. Receives (userText) — the user's raw words. Return a result object if your site handled the intent, or null/undefined to decline. See Intent Resolution below.
setSiteUrl(url)Override the site URL used for matching. Defaults to window.location.origin + window.location.pathname (trailing slashes stripped). Call this if your manifest URL differs from the page origin — e.g. deploying to /app/ but registering https://mysite.com.
audioControl(command)Control audio that WWWAND is playing on your behalf via the autoplay fallback. command is "stop", "pause", or "resume". See Autoplay Fallback below.

Intent Resolution

WWWAND uses a three-layer system to match user intent to the right action. Understanding how they interact is key to building responsive client-mode sites.

Layer 1: Manifest Intents → LLM Prompt

When you declare intents in your manifest, Wanda expands them into the AI system prompt. This tells the LLM which action to dispatch for common requests — e.g. "stop" → stopSong. The itemTerms field tells the system what words users use for your items (e.g. ["song", "track"]) — these are available for client-side matching but also help the LLM understand context.

Layer 2: handleUndispatched → onUndispatched

Sometimes the LLM decides not to dispatch any action (e.g. the user said something ambiguous or chatty). When your manifest includes "handleUndispatched": true, Wanda sends the raw userText to your client via onUndispatched(). Your handler can match against your own declared itemTerms and intents, then take action directly. Return a result object if you handled it, or null to decline.

// Match against your declared intents when the AI didn't dispatch
WwwandClient.onUndispatched((userText) => {
  const lower = userText.toLowerCase()
  const terms = ['song', 'track']
  if (terms.some(t => lower.includes(t)) && /\b(play|start)\b/.test(lower)) {
    return playFirstSong() // return a result → handled
  }
  if (/\b(stop|halt)\b/.test(lower)) {
    return stopPlayback() // handled
  }
  return null // not handled — other sites may try
})

Layer 3: Dispatch Correction via userText

When the LLM does dispatch an action, it might pick the wrong one — e.g. the user said "next" but the LLM dispatched playSong. The userText parameter in onAction() lets you detect and correct these mismatches client-side.

Autoplay Fallback

Browser autoplay policies can block audio from site tabs. WWWAND solves this by playing audio itself — the WWWAND tab already has audio permissions from the user's microphone interaction. When your onAction handler returns { audioBlocked: true, dataUrl: "..." }, Wanda plays the audio in its own tab and your site controls playback via WwwandClient.audioControl().

How It Works

Your onAction handler returns a result with audioBlocked: true and a dataUrl containing the audio data (base64-encoded). Wanda creates an Audio element and plays it. Your site can then send audioControl("stop" | "pause" | "resume") to control playback.

Action Result Fields

When using the autoplay fallback, include these fields in your action result:

FieldTypeDescription
audioBlockedbooleanSet to true to signal that your site can't play audio and Wanda should play it instead.
dataUrlstringA data: URL (or blob: URL) containing the audio to play. Required when audioBlocked is true.
namestringHuman-readable name of the track (e.g. "Stairway to Heaven"). Used in Wanda's spoken response.
idstringTrack identifier. Returned in the cleaned-up result for reference.
stoppedbooleanReturn true when your stop action succeeds.
pausedbooleanReturn true when your pause action succeeds.
resumedbooleanReturn true when your resume action succeeds.
rewoundbooleanReturn true when your rewind/restart action succeeds.
shuffledbooleanReturn true when your shuffle action succeeds.
// Example: play action with autoplay fallback
WwwandClient.onAction(async (action, params, userText) => {
  if (action === 'playSong') {
    const song = getSong(params.id)
    try {
      await playInBrowser(song) // try playing in the site tab
      return { ok: true }
    } catch (e) {
      if (e.name === 'NotAllowedError') {
        // Autoplay blocked — hand off to WWWAND
        return { audioBlocked: true, dataUrl: song.dataUrl, name: song.title, id: song.id }
      }
      throw e
    }
  }
  if (action === 'stopSong') {
    // If WWWAND is playing on our behalf, tell it to stop
    WwwandClient.audioControl('stop')
    return { stopped: true }
  }
})
Settings
60%
No speakers enrolled yet. Say "Hey Wanda, adapt to my voice" to enroll.
55%
Higher = stricter matching. Default: 55%
30%
Lower = more forgiving. Default: 30%
No sites registered.
D2L Debug 0 requests