{"id":51595917,"url":"https://github.com/petarjs/nocturne","last_synced_at":"2026-07-11T18:42:38.249Z","repository":{"id":370127074,"uuid":"1293161733","full_name":"petarjs/nocturne","owner":"petarjs","description":null,"archived":false,"fork":false,"pushed_at":"2026-07-08T07:53:02.000Z","size":837,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-08T09:20:11.666Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/petarjs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-07-08T06:41:39.000Z","updated_at":"2026-07-08T07:53:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/petarjs/nocturne","commit_stats":null,"previous_names":["petarjs/nocturne"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/petarjs/nocturne","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petarjs%2Fnocturne","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petarjs%2Fnocturne/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petarjs%2Fnocturne/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petarjs%2Fnocturne/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/petarjs","download_url":"https://codeload.github.com/petarjs/nocturne/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petarjs%2Fnocturne/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35372639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-11T02:00:05.354Z","response_time":104,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-11T18:42:37.668Z","updated_at":"2026-07-11T18:42:38.242Z","avatar_url":"https://github.com/petarjs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nocturne\n\nA living display for AI agents — a persistent, stateful screen that agents (or a\ncurl one-liner) render onto. The full product spec lives in [CLAUDE.md](CLAUDE.md).\n\n```\napps/web        Next.js display + admin UI          :3000\napps/server     Hono on workerd (wrangler dev)      :9876\n                ├─ SceneDO    one Durable Object per dashboard: scene, op log,\n                │             view code, live WebSockets\n                └─ RegistryDO dashboard index + hashed API keys\npackages/core   @nocturne/core — scene schema, ops, pure reducer, themes,\n                fixtures, WS protocol (shared by browser and server)\n```\n\nThe server runs **locally** (workerd via `wrangler dev`, state persisted in\n`apps/server/data/`) and is exposed to the internet with a Cloudflare Tunnel.\nThe same worker deploys unchanged to Cloudflare (`pnpm --filter @nocturne/server deploy`)\nif you ever want it hosted.\n\n## Quickstart\n\n```bash\npnpm install\npnpm dev            # web on :3000 + server on :9876\n```\n\n1. Open http://localhost:3000 — the admin page.\n2. Create your **first API key** (open while zero keys exist; the first key\n   closes that window — do this before exposing the tunnel).\n3. Create a dashboard (e.g. `living-room`) and click **open**, or go to\n   `http://localhost:3000/d/living-room`.\n4. Push data at it:\n\n```bash\ncurl -X POST http://localhost:9876/v1/dashboards/living-room/widgets/cpu/data \\\n  -H \"Authorization: Bearer $NOCTURNE_KEY\" \\\n  -d '{\"value\":73}'\n```\n\nEvery browser on that dashboard updates live over WebSocket. `/display` (dev\nonly, gated out of production builds) runs local fixture mode — what the\ngolden tests capture.\n\nThe demo runbook drives all six beats end-to-end:\n\n```bash\nNOCTURNE_KEY=noct_… ./scripts/beats.sh\n```\n\n## API\n\nFor an LLM-agent-ready reference (endpoints, ops, widget schemas, moments,\nthemes), see [AGENT_API.md](AGENT_API.md) — feed that file to whatever agent\nyou're wiring up to a dashboard.\n\nAll routes under `http://localhost:9876/v1`. Writes need\n`Authorization: Bearer noct_…`; reads are open unless the dashboard has a view\ncode (`?code=` or `X-Nocturne-View-Code`).\n\n| Method | Path | Auth | |\n|---|---|---|---|\n| GET | `/v1/dashboards` | — | list dashboards |\n| POST | `/v1/dashboards` | key | `{slug, name?, scene?}` |\n| DELETE | `/v1/dashboards/:slug` | key | delete (closes live sockets) |\n| PATCH | `/v1/dashboards/:slug/settings` | key | `{name?, viewCode?: string\\|null}` |\n| GET | `/v1/dashboards/:slug/scene` | code? | `{rev, scene, …}` |\n| POST | `/v1/dashboards/:slug/ops` | key | `Op \\| Op[]` (see `packages/core/src/schema/ops.ts`) |\n| POST | `/v1/dashboards/:slug/widgets/:wid/data` | key | any JSON → `pushData` |\n| GET | `/v1/dashboards/:slug/live` | code? | WebSocket: `sync` on connect, `ops` frames on change |\n| GET/POST | `/v1/keys` · DELETE `/v1/keys/:id` | key¹ | manage API keys (hashes only at rest) |\n\n¹ open while zero active keys exist (first-run bootstrap); revoking the last\nkey re-opens it.\n\nHigh-frequency `pushData` is coalesced server-side to ≤4 WebSocket frames/sec.\nRate limiting beyond that is deliberately deferred — this is a personal tool\nbehind an unguessable tunnel URL plus an API key.\n\n## Exposing it with a Cloudflare Tunnel\n\nWebSockets work through cloudflared unchanged (`brew install cloudflared`).\n\n**Quick tunnels** (zero config; URLs rotate every run):\n\n```bash\npnpm tunnel        # → https://\u003crandom\u003e.trycloudflare.com  → :9876 (API)\npnpm tunnel:web    # → https://\u003crandom\u003e.trycloudflare.com  → :3000 (UI)\n```\n\nOpen the display through the web tunnel and point it at the API tunnel once:\n\n```\nhttps://\u003cweb-tunnel\u003e/d/living-room?api=https://\u003capi-tunnel\u003e\n```\n\n`?api=` persists to localStorage, so when the API tunnel URL rotates you only\npass it again once. Agents and cron jobs push straight at\n`https://\u003capi-tunnel\u003e/v1/...` with the bearer key.\n\n**Named tunnel** (stable hostnames, one tunnel for both):\n\n```yaml\n# ~/.cloudflared/config.yml\ntunnel: \u003ctunnel-id\u003e\ncredentials-file: /Users/you/.cloudflared/\u003ctunnel-id\u003e.json\ningress:\n  - hostname: api.example.com\n    service: http://localhost:9876\n  - hostname: dash.example.com\n    service: http://localhost:3000\n  - service: http_status:404\n```\n\nWith stable hostnames, set `NEXT_PUBLIC_API_URL=https://api.example.com` in\n`apps/web/.env.local` (or keep using `?api=` once per browser).\n\nBefore exposing anything: create your first API key, and put view codes on\ndashboards you don't want publicly viewable (admin page → dashboard → view\ncode). The blast radius of a leaked *view* is pixels; writes always need a key.\n\n## Development\n\n```bash\npnpm dev:web / pnpm dev:server   # each side alone\npnpm build                       # next build + wrangler dry-run\npnpm typecheck                   # all three packages\npnpm test:golden                 # golden frames (display in local fixture mode)\n```\n\nServer state (dashboards, keys, scenes) lives in `apps/server/data/` —\ndelete it for a factory reset.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpetarjs%2Fnocturne","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpetarjs%2Fnocturne","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpetarjs%2Fnocturne/lists"}