Skip to content

Repository files navigation

EvenDash

Your agents, already on your Even G2.

EvenDash is a private, self-hosted agent dashboard for Even Realities G2. Each user deploys one Cloudflare Worker with its own D1 database and Durable Object, then connects the Even Hub app with a device-only token. There is no EvenDash account, central service, or public web dashboard.

Current Even Hub app version: 0.2.3

Deploy to Cloudflare

The deploy button requires this repository to be reachable at the linked public GitHub URL. A fork can replace the url query parameter with its own public repository URL.

What it looks like

The 576 × 288 glasses canvas is divided 30:70:

  • Left: a customizable digital, analog, or minimal clock and three live, template-bound complication slots. Empty slots are not rendered, and populated slots align from the bottom without card borders.
  • Right: two visible feed cards with a title and two-to-three-line summary.
  • Feed order: pinned cards first, then unpinned cards; each group is sorted by startsAt descending. Read state changes the marker, never the position.

Cards are a flat, SNS-like feed—not boards. An agent can create a new daily brief every morning, update yesterday's card, or replace a card completely. EvenDash does not prescribe that choice.

Every card detail starts with its startsAt visibility time, formatted in the phone-default or manually selected clock time zone.

flowchart LR
  A["Agent · OpenClaw · Hermes"] -->|"ADMIN_TOKEN · REST / MCP"| W["Your Cloudflare Worker"]
  G["EvenDash on G2"] -->|"DEVICE_TOKEN · HTTPS / WebSocket"| W
  P["Even Hub phone settings"] -->|"Worker URL + DEVICE_TOKEN"| G
  W --> D["Private D1"]
  W --> S["Durable Object sync hub"]
  W -->|"Signed per-action webhook"| H["Agent webhook"]
  D --> W
  S --> G
Loading

Deploy

One-click Cloudflare deployment

  1. Generate three independent secrets locally:

    openssl rand -hex 32 # ADMIN_TOKEN
    openssl rand -hex 32 # DEVICE_TOKEN
    openssl rand -hex 32 # WEBHOOK_SIGNING_SECRET
  2. Press Deploy to Cloudflare above and enter those values when prompted. Cloudflare provisions the Worker, D1 database, and Durable Object from the repository configuration.

  3. Keep the resulting Worker URL and DEVICE_TOKEN for the Even Hub app. Give only ADMIN_TOKEN to trusted agents.

  4. Open EvenDash on the phone, enter the Worker URL and DEVICE_TOKEN, choose WebSocket, polling, or manual sync, and press Test connection.

Never reuse the two access tokens. A missing token configuration fails closed with HTTP 503; a missing or incorrect bearer token returns HTTP 401.

CLI deployment

Cloudflare Wrangler requires an authenticated account:

pnpm install
pnpm exec wrangler login
pnpm exec wrangler secret put ADMIN_TOKEN
pnpm exec wrangler secret put DEVICE_TOKEN
pnpm exec wrangler secret put WEBHOOK_SIGNING_SECRET
pnpm run deploy

pnpm run deploy applies all D1 migrations before deploying the Worker.

Install and test the Even Hub app

The current app manifest allows https://*.workers.dev. If you use a custom domain, add its exact HTTPS origin to app.json before packaging.

For local simulator development, use two terminals:

# terminal 1
pnpm install
pnpm run dev

# terminal 2
pnpm run simulator

The simulator loads http://127.0.0.1:5173 and exposes its local automation API on port 9898. The app starts with a safe “Configure EvenDash on your phone” screen until a Worker URL and DEVICE_TOKEN are saved.

Build an installable Even Hub package:

pnpm run pack

The result is evendash.ehpk. Install it through the Even Hub developer flow, then configure the Worker connection from the phone screen.

Connection settings persistence

Since version 0.2.3, the Worker URL, DEVICE_TOKEN, sync mode, polling interval, language, and clock time zone are stored in the Even App's native local storage instead of relying on the plugin WebView alone. A last-known-good backup is written with every successful save and restored if the primary record is damaged.

On the first 0.2.3 launch, a valid setting left by an older build is migrated from WebView storage automatically. If the older setting had already disappeared, enter it once more; subsequent app restarts load it from native storage.

G2 controls

Screen Gesture Result
Feed Swipe Select/scroll cards; the feed never wraps
Feed Tap Open selected card and mark it read
Feed Double tap Soft sleep: hide every dashboard pixel while sync stays active
Soft sleep Double tap Wake and show the latest dashboard within five minutes
Soft sleep Wait five minutes Deep sleep: exit EvenDash to let G2 return to its native low-power behavior
Detail Swipe Scroll pages, then move to the adjacent card at an edge
Detail Tap Open that card's custom action menu
Detail Double tap Return to the feed
Action menu Tap Select an option; destructive and single-option actions confirm

After automatic deep sleep, long-press either G2 touchpad to open the system menu, select EvenDash, and tap to launch it again.

Use the G2 system long-press gesture to leave EvenDash.

Every card also receives a built-in Delete card action. Confirmed deletion is a hard D1 delete and disappears from G2 on the next real-time update.

Agent integration

There are three equivalent surfaces:

Use Authorization: Bearer $ADMIN_TOKEN for agent operations. Available MCP tools are create_card, update_card, delete_card, list_feed, list_templates, update_dashboard, list_action_events, and resolve_action_event.

Create a declarative card

No JavaScript or HTML is accepted. UI is a validated JSON tree made from stack, row, columns, text, label, value, badge, divider, progress, list, HTTPS image, and chart nodes.

External PNG, JPEG, and WebP URLs are fetched through the authenticated Worker origin so the Even Hub app never needs an arbitrary network allowlist. The proxy rejects redirects, credentials, IP/private-style hosts, unsupported content types, and payloads larger than 2 MiB; G2 contains the result in a 288 × 144 monochrome detail slot. Image upload and R2 storage are intentionally outside the MVP.

curl -X POST "$WORKER_URL/api/v1/cards" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "title": "Daily brief",
    "description": "Three messages need your attention",
    "pinned": true,
    "expiresAt": "2026-07-31T00:00:00.000Z",
    "ui": {
      "type": "stack",
      "children": [
        {"type": "badge", "value": "MORNING"},
        {"type": "text", "value": "Review the release approval first."},
        {"type": "chart", "chart": "sparkline", "values": [4, 7, 5, 9, 12]}
      ]
    },
    "actions": [{
      "id": "release",
      "label": "Release decision",
      "options": [
        {"id": "approve", "label": "Approve", "value": {"decision": "approved"}},
        {"id": "deny", "label": "Deny", "value": {"decision": "denied"}, "destructive": true}
      ],
      "delivery": {"type": "poll"}
    }]
  }'

startsAt defaults to creation time. Before startsAt, the card is hidden. After expiresAt, the cron archives it and broadcasts the removal.

Register and bind a template

Bindings are data paths expressed as {"$bind":"path.to.value"}. Prototype paths are rejected, and bindings resolve to data only—never executable code.

curl -X POST "$WORKER_URL/api/v1/templates" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "id": "brief-v1",
    "name": "Daily brief",
    "version": 1,
    "target": "card",
    "dataSchema": {"headline": {"type": "string"}},
    "ui": {
      "type": "stack",
      "children": [
        {"type": "label", "value": "TODAY"},
        {"type": "text", "value": {"$bind": "headline"}}
      ]
    },
    "actions": []
  }'

curl -X POST "$WORKER_URL/api/v1/cards" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "title": "Daily brief",
    "description": "Prepared by your agent",
    "templateId": "brief-v1",
    "data": {"headline": "Ship candidate 42 is ready"}
  }'

Agents can discover templates with GET /api/v1/templates?target=card. PATCH /api/v1/cards/{id} changes selected fields; PUT fully replaces the card while keeping its ID.

The glasses action menu always includes Mark as read or Mark as unread according to the current card state. Device clients can perform the same transition with POST /api/v1/cards/{id}/read and POST /api/v1/cards/{id}/unread.

Configure the clock and complications

Complications use the same declarative templates and binding mechanism. All three slots are replaced atomically:

curl -X PUT "$WORKER_URL/api/v1/dashboard" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "clock": {"style": "digital", "timeZone": "Asia/Seoul", "hourCycle": 24},
    "complications": [
      {"slot": 1, "templateId": "weather-v1", "data": {"label": "18° CLEAR"}},
      {"slot": 2, "templateId": "count-v1", "data": {"label": "3 URGENT"}},
      {"slot": 3, "templateId": "health-v1", "data": {"label": "ALL HEALTHY"}}
    ]
  }'

Register each referenced target: "complication" template first. Agents update live values by replacing the dashboard; connected glasses are notified over the Durable Object WebSocket, with 15/30/60-second polling as fallback.

Action lifecycle

  1. G2 submits an action with DEVICE_TOKEN.
  2. EvenDash writes an immutable pending event to D1 immediately and shows Requested · waiting for agent.
  3. The agent receives it by polling GET /api/v1/events?status=pending, by a per-action webhook, or both.
  4. The agent calls POST /api/v1/events/{eventId}/resolve with status, message, and optionally either cardPatch or cardReplacement.
  5. Event resolution and card update commit atomically; G2 refreshes in real time.

Webhook requests include:

Idempotency-Key: <event id>
X-EvenDash-Timestamp: <ISO-8601 timestamp>
X-EvenDash-Signature: v1=<hex HMAC-SHA256>

Verify the signature over the exact UTF-8 bytes of <timestamp>.<raw-request-body> with WEBHOOK_SIGNING_SECRET. Webhook URLs must use HTTPS. Failed deliveries retry with bounded exponential backoff.

TODO

  • Add fully customizable clock styles, including declarative layout, typography, and data-bound visual options beyond the built-in presets.
  • Add user-facing complication creation and management so complications can be added, configured, removed, and assigned to the three dashboard slots.
  • Add free-form text actions using speech-to-text. The user will be able to choose OpenAI or ElevenLabs STT, speak through the G2 or phone microphone, and submit the transcription as an action input through the existing pending event, webhook, and polling lifecycle. Provider credentials must remain self-hosted secrets and must never be included in card payloads.

Security model

  • / returns 404 and /robots.txt disallows all crawling.
  • Every private response includes no-store, noindex, DENY framing, and nosniff headers.
  • ADMIN_TOKEN can create/update data, use MCP, poll events, and resolve them.
  • DEVICE_TOKEN can read the display, mark cards read or unread, act, and delete cards.
  • WebSocket authentication uses a dedicated subprotocol; tokens are not put in URLs or logs.
  • External images pass through a bounded HTTPS-only raster proxy instead of being loaded directly by the phone webview.
  • D1 stores all cards, templates, dashboard settings, and action history in the user's own Cloudflare account.

This is capability-token security: anyone holding a token has its authority. Rotate a leaked token immediately with wrangler secret put, and do not place tokens in card content, logs, screenshots, source control, or agent prompts outside the trusted integration.

Development philosophy

EvenDash is developed with classicist, Kent Beck-style TDD:

  • write one behavior test, observe red, implement the smallest green change, then refactor;
  • use real domain objects, real Workers runtime, real D1, real Durable Objects, real cryptography, and the actual Even Hub simulator;
  • do not use mocks, stubs, beforeEach, or afterEach;
  • every test owns its inputs and can run independently.

Run the complete contract:

pnpm run test:all

Domain tests run in Node. Worker integration tests run inside workerd with isolated D1 and Durable Object storage. The build is part of the same command.

Languages

The phone UI defaults to English and also includes Korean, Japanese, and Chinese. Agent-provided card content is rendered as supplied.

About

Self-hosted agent dashboard for Even Realities G2 on Cloudflare

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages