Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions telemetry-worker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ genclass.dev/dashboard/<secret> --> dashboard page --GET /api/projects/<secret>?
| file | what |
|---|---|
| `src/index.ts` | entry point: default export only (`fetch`, `scheduled`). The Workers runtime treats every named export of the main module as an entrypoint, so nothing else is exported here. |
| `src/collector.ts` | ingest (`POST /v1/events`), envelope validation (incl. optional `token`), routing for `/api/*`, `/start`, `/dashboard*`; hands account paths to `src/auth.ts` |
| `src/collector.ts` | ingest (`POST /v1/events`), envelope validation (incl. optional `token`), routing for `/api/*`, `/start`, `/start/open-source`, `/dashboard*`; hands account paths to `src/auth.ts` |
| `src/dashboard.ts` | ids and hashing, project creation and lookup, token cache, aggregation of a batch into counters, stats for the dashboard, retention |
| `src/pages.ts` | HTML/CSS/JS for `/start` and `/dashboard/<secret>`, and the shell and dashboard the account pages reuse (`/account/apps/<id>` is the dashboard in its account mode); the dashboard's views, its Connect dialog (Framework, API Keys, MCP, Analytics), the MCP view and the agent link box, Copy prompt and Copy as Markdown; inline, per-response CSP nonce, fonts from Google Fonts only |
| `src/pages.ts` | HTML/CSS/JS for `/start`, `/start/open-source` and `/dashboard/<secret>`, and the shell and dashboard the account pages reuse (`/account/apps/<id>` is the dashboard in its account mode); the dashboard's views, its Connect dialog (Framework, API Keys, MCP, Analytics), the MCP view and the agent link box, Copy prompt and Copy as Markdown; inline, per-response CSP nonce, fonts from Google Fonts only |
| `src/studio.ts` | the Supabase Studio frame the dashboards and the organization pages use: the 48px header (organization and project switchers, Connect, Feedback, search, help, the account menu), the icon rail that widens on hover, product menus, the dialog and tabs, the command menu (Ctrl/Cmd K), Studio's dark tokens and icons; the copy helpers every page shares |
| `src/escape.ts` | `esc` and `embed`, for every page module |
| `src/auth.ts` | accounts: Google, email and password, GitHub, passkeys (sign-in, the passkey step, fresh confirmations), sessions, CSRF, the device flow, owned apps, their routes and their retention |
Expand Down Expand Up @@ -80,6 +80,7 @@ genclass.dev/dashboard/<secret> --> dashboard page --GET /api/projects/<secret>?
| `GET /api/projects/<secret>?range=24h\|7d\|30d` | dashboard stats JSON (every plan includes the dashboard; a plan file that left it out would answer 402 `{ ok:false, error, locked:true }`) (default `7d`): `name, token, created, lastEvent, range, from, generated, totals, breakdowns{metric: [{key,count}]}` (top 25 each; 200 for `detect_issue` and `possible_issue`), `distinct{metric: n}` (distinct keys before that cut, e.g. pages with a problem), `series` (24 hourly or 7/30 daily buckets: sessions, decisions, detections, acted, possible, ix_views, ix_sessions), `recent` (last 50 detections/interventions in the window; possible ones are not listed). `totals` also has `latencyAvgMs`, `modelLoadAvgMs`, `failOpens`, `modelErrors`, `interventions`, `actionLimits`, `detectionsWithPage` and `possibleWithPage`. Apps that turned on interaction analytics (runtime `interactions` events, [docs/runtime/ANALYTICS.md](../docs/runtime/ANALYTICS.md)) also get `ix_views`, `ix_engaged_ms`, `ix_click`, `ix_rage` and `ix_dead` totals and breakdowns (by route, or route TAB element key; `ix_scroll` by route TAB milestone; top 200 each), counted with their own per-batch budget; and per visit `ix_sessions`, `ix_ref`, `ix_utm_source`, `ix_utm_medium`, `ix_utm_campaign`, `ix_device`, `ix_browser`, `ix_os`, `ix_country`, `ix_returning`, plus `visits` (`sessions, pages, bounces, entry[], exit[]` over the visits that started in the period) and `funnels` (`[{ name, steps, counts }]`; docs/runtime/ANALYTICS.md section 3.1); with navigation paths (ANALYTICS.md section 11) also `ix_entry` and `ix_exit` (totals and by route), `ix_nav` and `ix_nav_ms` (from TAB to), `ix_arrive` (by `nav`, and route TAB `nav`), `ix_first` (route TAB element key), `ix_first_n` and `ix_first_ms` (by route) and `ix_path` (the first 5 routes of a page load, joined with ` > `). 404 `{ ok:false, error:"not found" }` for an unknown or malformed secret. Same-origin only (no CORS). |
| `PUT /api/projects/<secret>/funnels` | body `{ funnels: [{ name, steps }] }` (application/json, at most 4 KB): replaces the app's funnels (at most 5, 2 to 6 routes each; a step ending in `*` matches every route under it). 200 `{ ok, funnels }`, 400 with the reason, 403 for the sample. Account mode: `PUT /api/account/projects/<id>/funnels` with the browser session and the site's Origin |
| `GET /start` | "Get a token" page |
| `GET /start/open-source` | "Use GenClass without an account": the open-source parts only (the runtime, the agent link, the MCP server and the local page check, all Apache-2.0), with no account, no token and `telemetry: false`, so nothing reaches genclass.dev; for teams that cannot sign up for or send data to a hosted dashboard. Choices (Guard and Eager, Findings in our own logs: the `console-json` sink, Serve the model ourselves: `fetch-model`, Deploy when done) rewrite one copied prompt (`openSourcePrompt`, run from its own source in the page, as `installPrompt` is); the page sends no request. Linked from `/start` and under the sign-in ways on `/login` and `/signup` |
| `POST /api/projects/<secret>/agent` | the MCP server's log of the closed loop, mirrored, when the developer sets `GENCLASS_REPORT` for `@genclass/mcp` (docs/runtime/AGENT.md section 4.5): `{ schema: "genclass-agent/1", events: [{ t: "found" \| "explain" \| "armed" \| "replay" \| "test" \| "explore" \| "cleared", at, id?, kind?, route?, reproduced?, runs?, races?, what? }] }`, at most 50 events and 16 KB a request, rate limited per token (`SESSION_LIMIT`). Every accepted event is one row of `agent_log` (at most 500 per app, the oldest go first; unknown types are dropped); the ones about a finding also update `agent_reports` (kind, normalised route, found, explained, last replay and verdict, how many replays reproduced it, tested, proven: a replay that stops reproducing after one that did). 90 days; the stats carry them as `agent` (`findings`, `counts`, `stages`: found, explained, reproduced, fixed, closed, open, continued; `log`, the last 50) and the dashboard's Closed-loop development view shows them. The sample and unknown secrets 404; other methods 405. Ids, kinds, routes, verdicts, counts and times only, never page text |
| `GET /dashboard/<secret>` | the dashboard (for an app with no account too: it is on Free), in the Supabase Studio frame (`src/studio.ts`), in views the rail switches between: Project overview (the name and status, the summary, four stats, the problems chart, usage cards per hour or day, Get connected, the problems that need attention with a Copy prompt each and Fix them all, and Getting started until the first event), Problems (each kind with its pages, its fix and a Copy prompt for a coding agent; Fix them all copies one prompt for every kind and the dev page's checks, in which the agent says what to do on the page and waits for the loads itself), Pages and Actions, Activity, Visitors (without interaction data, under the copy-prompt button, the runtime analytics need, `ANALYTICS_RUNTIME` 0.3.0-beta.1, with the install line and the app's older versions from the `runtime` breakdown; the Connect dialog says the same), Health, MCP (the @genclass/mcp setup per coding agent, with GENCLASS_DASHBOARD set to the agent link, a Copy prompt, the tools, the JSON address), Closed loop (Closed-loop development: Fig. 3, every issue's path, found, reproduced, fix proven, closed, or round again, with the period's counts from the agent's reports; Table 15, the agent's log mirrored from `@genclass/mcp`; Fig. 6, the app's problems and what the agent does with each), Integrations (forwarding, account mode) and Project Settings (general, the key, agent access; Export in account mode). Problems, Visitors and Project Settings have Studio's product menu. The header's Connect opens the Connect dialog (also `#connect`); its Framework tab's prompt (`installPrompt`: the runtime, or the script tags of a site with no build step, the state in one atom per response, the agent link, the MCP server) follows the choices above it, Guard (and Eager), Badge on the live site, Connect my coding agent to production (makes the agent link, or names a private link) and Deploy when done, by running `installPrompt`'s own source in the page; the hash names the view (`#found`, `#recent-s`, `#mcp`); every data view copies as Markdown; and without the script every section shows. On a private link the page's own address is the agent link. With a plan file that left the dashboard out, the locked page (sign in, add the app to the account, see the plans; `src/locked.ts`). 404 page for an unknown secret; `GET /dashboard` redirects to `/start`. `/dashboard/sample` stays open |

Expand Down Expand Up @@ -407,7 +408,7 @@ The daily cron also deletes expired challenges and device codes and expired or r
|---|---|---|---|
| `AUTH_ORIGIN` | var | `https://genclass.dev` | expected `Origin`, WebAuthn origin, OAuth redirect URIs, cookie `Secure` |
| `AUTH_RP_ID` | var | `genclass.dev` | WebAuthn RP ID |
| `RUNTIME_ON_NPM` | var (wrangler.toml) | `0.3.0-beta.3` | the newest `@genclass/runtime` on npm, on any dist-tag; below `0.3.0-beta.0` the Connect dialog, `/start` and private dashboards say first that keys and `login` are not published yet. Set it with `src/pages.ts` `SNIPPET_RUNTIME` to every release once release.yml has published it and jsDelivr serves it, then deploy; at `0.3.0` also set `RUNTIME_PKG` (the snippets pin the script tag to `SNIPPET_RUNTIME` and name the `@beta` dist-tag in their commands while it is a prerelease) |
| `RUNTIME_ON_NPM` | var (wrangler.toml) | `0.3.0-beta.4` | the newest `@genclass/runtime` on npm, on any dist-tag; below `0.3.0-beta.0` the Connect dialog, `/start` and private dashboards say first that keys and `login` are not published yet. Set it with `src/pages.ts` `SNIPPET_RUNTIME` to every release once release.yml has published it and jsDelivr serves it, then deploy; at `0.3.0` also set `RUNTIME_PKG` (the snippets pin the script tag to `SNIPPET_RUNTIME` and name the `@beta` dist-tag in their commands while it is a prerelease) |
| `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` | secret | unset | Google sign-in, the first way in; both needed. An email and a password are offered either way |
| `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET` | secret | unset | GitHub sign-in; both needed |

Expand Down
7 changes: 5 additions & 2 deletions telemetry-worker/src/account-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,12 @@ export interface AuthPageOptions {
notice?: string | null;
}

/** Under the sign-in ways: the runtime needs no account, and what an account's Free plan gives (billing/plans.json). */
/**
* Under the sign-in ways: the runtime needs no account (the way in without one: /start/open-source, pages.ts
* openSourcePage), and what an account's Free plan gives (billing/plans.json).
*/
const optional = (): string =>
`<p class="opt">The runtime needs no account. ${entitled(FREE, "dashboard") ? "The dashboard is free for every app." : "Dashboards come with a paid plan."}</p>`;
`<p class="opt">The runtime needs no account: <a href="/start/open-source">set it up without one</a>. ${entitled(FREE, "dashboard") ? "The dashboard is free for every app." : "Dashboards come with a paid plan."}</p>`;

/** The passkey sign-in button's script: sign in, then go to `next` (or reload, on the device page). */
const PASSKEY_LOGIN_JS = `
Expand Down
14 changes: 11 additions & 3 deletions telemetry-worker/src/collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
// OPTIONS /v1/events (CORS preflight), GET /v1/health.
//
// Per-app tokens and dashboards (src/dashboard.ts, src/pages.ts; also routed on genclass.dev/{api/*,start*,dashboard*}):
// POST /api/projects, GET /api/projects/<secret>, GET /start, GET /dashboard/<secret>. A batch with a known top-level
// `token` is also aggregated into D1 for that app's dashboard; without one it is stored in R2 only, exactly as before.
// POST /api/projects, GET /api/projects/<secret>, GET /start, GET /start/open-source (no account, no token), GET
// /dashboard/<secret>. A batch with a known top-level `token` is also aggregated into D1 for that app's dashboard;
// without one it is stored in R2 only, exactly as before.
// Every plan includes the dashboard (billing/plans.json), so a private link opens it, also for an app with no account
// (Free). A plan file that left the dashboard out would open the locked page instead (src/locked.ts) and its stats
// would answer 402. Collection never depends on it: every known token is aggregated.
Expand Down Expand Up @@ -42,7 +43,7 @@ import {
import { routeBilling } from "./billing.js";
import { json, readCapped } from "./http.js";
import { afterBatch } from "./paid.js";
import { dashboardPage, notFoundPage, ownEndpoint, securityHeaders, startPage } from "./pages.js";
import { dashboardPage, notFoundPage, openSourcePage, ownEndpoint, securityHeaders, startPage } from "./pages.js";
import { lockedLink } from "./locked.js";
import { hasDashboard, projectAccess } from "./plans.js";
import { SAMPLE_PROJECT, SAMPLE_SECRET, sampleStats } from "./sample.js";
Expand Down Expand Up @@ -406,6 +407,13 @@ async function routeSite(request: Request, url: URL, path: string, env: Env, opt
return html(200, startPage(nonce, { runtimeReady: runtimeHasTokens(env), endpoint: ownEndpoint(env) }), nonce);
}

// the open-source parts with no account and no token (pages.ts openSourcePage): static, nothing stored
if (path === "/start/open-source") {
if (m !== "GET" && m !== "HEAD") return null;
const nonce = randomBase62(22);
return html(200, openSourcePage(nonce), nonce);
}

if (path === "/dashboard") {
if (m !== "GET" && m !== "HEAD") return null;
return new Response(null, { status: 302, headers: { location: "/start", "cache-control": "no-store" } });
Expand Down
Loading
Loading