mcp-scraper 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +4 -0
  2. package/dist/bin/api-server.cjs +1398 -552
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +2 -2
  5. package/dist/bin/browser-agent-stdio-server.cjs +314 -0
  6. package/dist/bin/browser-agent-stdio-server.cjs.map +1 -0
  7. package/dist/bin/browser-agent-stdio-server.d.cts +1 -0
  8. package/dist/bin/browser-agent-stdio-server.d.ts +1 -0
  9. package/dist/bin/browser-agent-stdio-server.js +313 -0
  10. package/dist/bin/browser-agent-stdio-server.js.map +1 -0
  11. package/dist/bin/mcp-stdio-server.cjs +351 -314
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +2 -1
  14. package/dist/bin/mcp-stdio-server.js.map +1 -1
  15. package/dist/chunk-2BS7BUEE.js +7 -0
  16. package/dist/chunk-2BS7BUEE.js.map +1 -0
  17. package/dist/{chunk-RE6HCRYC.js → chunk-BMVQB3WN.js} +352 -315
  18. package/dist/chunk-BMVQB3WN.js.map +1 -0
  19. package/dist/{chunk-ZK456YXN.js → chunk-GXBT5CDU.js} +20 -3
  20. package/dist/chunk-GXBT5CDU.js.map +1 -0
  21. package/dist/{server-QXVVTKJP.js → server-ASCMKUQ5.js} +794 -29
  22. package/dist/server-ASCMKUQ5.js.map +1 -0
  23. package/dist/{worker-AUCXFHEL.js → worker-KJ4A7WIR.js} +2 -2
  24. package/docs/specs/api-forge-spec.md +234 -0
  25. package/docs/specs/deferred-work-spec.md +74 -0
  26. package/docs/specs/oauth-mcp-spec.md +213 -0
  27. package/package.json +3 -2
  28. package/dist/chunk-RE6HCRYC.js.map +0 -1
  29. package/dist/chunk-ZK456YXN.js.map +0 -1
  30. package/dist/server-QXVVTKJP.js.map +0 -1
  31. /package/dist/{worker-AUCXFHEL.js.map → worker-KJ4A7WIR.js.map} +0 -0
@@ -6,10 +6,12 @@ import {
6
6
  configureReportSaving,
7
7
  harvestTimeoutBudget,
8
8
  liveWebToolAnnotations
9
- } from "./chunk-RE6HCRYC.js";
9
+ } from "./chunk-BMVQB3WN.js";
10
+ import "./chunk-2BS7BUEE.js";
10
11
  import {
11
12
  BALANCE_PACK_LABELS,
12
13
  BALANCE_PRICE_IDS,
14
+ BROWSER_OPEN_MIN_BALANCE_MC,
13
15
  CONCURRENCY_PRICE_ID,
14
16
  CREDIT_COST_CATALOG,
15
17
  FREE_MONTHLY_REFRESH_MC,
@@ -17,12 +19,13 @@ import {
17
19
  LedgerOperation,
18
20
  MC_COSTS,
19
21
  MC_PER_CREDIT,
22
+ browserActiveCostMc,
20
23
  classifyHarvestProblem,
21
24
  createHarvestAttemptRecorder,
22
25
  harvestProblemResponse,
23
26
  insufficientBalanceResponse,
24
27
  serializeHarvestProblem
25
- } from "./chunk-ZK456YXN.js";
28
+ } from "./chunk-GXBT5CDU.js";
26
29
  import {
27
30
  BrowserDriver,
28
31
  MapsPlaceOptionsSchema,
@@ -3497,8 +3500,8 @@ import { chromium } from "playwright";
3497
3500
  async function fetchWithKernel(url) {
3498
3501
  const apiKey = browserServiceApiKey();
3499
3502
  if (!apiKey) throw new Error("Browser backend API key not set");
3500
- const client = new Kernel({ apiKey });
3501
- const kb = await client.browsers.create({ stealth: true, timeout_seconds: 60 });
3503
+ const client2 = new Kernel({ apiKey });
3504
+ const kb = await client2.browsers.create({ stealth: true, timeout_seconds: 60 });
3502
3505
  const browser = await chromium.connectOverCDP(kb.cdp_ws_url);
3503
3506
  try {
3504
3507
  const context = browser.contexts()[0] ?? await browser.newContext({
@@ -3511,7 +3514,7 @@ async function fetchWithKernel(url) {
3511
3514
  } finally {
3512
3515
  await browser.close().catch(() => {
3513
3516
  });
3514
- await client.browsers.deleteByID(kb.session_id).catch(() => {
3517
+ await client2.browsers.deleteByID(kb.session_id).catch(() => {
3515
3518
  });
3516
3519
  }
3517
3520
  }
@@ -4843,7 +4846,7 @@ async function extractSite(opts) {
4843
4846
  }
4844
4847
 
4845
4848
  // src/api/server.ts
4846
- import { Hono as Hono9 } from "hono";
4849
+ import { Hono as Hono10 } from "hono";
4847
4850
  import { serve as serveInngest } from "inngest/hono";
4848
4851
 
4849
4852
  // src/inngest/client.ts
@@ -8532,13 +8535,13 @@ var FacebookAdExtractor = class {
8532
8535
  }
8533
8536
  await page.waitForTimeout(1500);
8534
8537
  let prevCount = 0;
8535
- for (let scroll = 0; scroll < 20; scroll++) {
8538
+ for (let scroll2 = 0; scroll2 < 20; scroll2++) {
8536
8539
  const count = await page.evaluate(() => {
8537
8540
  const bt = document.body ? document.body.innerText ?? "" : "";
8538
8541
  return [...bt.matchAll(/Library ID/g)].length;
8539
8542
  });
8540
8543
  if (count >= maxAds) break;
8541
- if (count === prevCount && scroll > 0) break;
8544
+ if (count === prevCount && scroll2 > 0) break;
8542
8545
  prevCount = count;
8543
8546
  await page.evaluate(() => {
8544
8547
  if (document.body) window.scrollTo(0, document.body.scrollHeight);
@@ -9184,7 +9187,7 @@ facebookAdApp.post("/search", createApiKeyAuth(), async (c) => {
9184
9187
  return c.json(searchResult2);
9185
9188
  }
9186
9189
  await page.waitForTimeout(1500);
9187
- for (let scroll = 0; scroll < 3; scroll++) {
9190
+ for (let scroll2 = 0; scroll2 < 3; scroll2++) {
9188
9191
  await page.evaluate(() => {
9189
9192
  if (document.body) window.scrollTo(0, document.body.scrollHeight);
9190
9193
  });
@@ -10901,7 +10904,10 @@ function mcpAuthError() {
10901
10904
  });
10902
10905
  return new Response(body, {
10903
10906
  status: 401,
10904
- headers: { "Content-Type": "application/json" }
10907
+ headers: {
10908
+ "Content-Type": "application/json",
10909
+ "WWW-Authenticate": 'Bearer realm="mcp-scraper", error="invalid_token", error_description="Pass an MCP Scraper API key as x-api-key or Bearer token"'
10910
+ }
10905
10911
  });
10906
10912
  }
10907
10913
  async function requireMcpCallerKey(c) {
@@ -10949,11 +10955,767 @@ mcpApp.all("/", async (c) => {
10949
10955
  }
10950
10956
  });
10951
10957
 
10958
+ // src/api/browser-agent-routes.ts
10959
+ import { Hono as Hono8 } from "hono";
10960
+
10961
+ // src/api/browser-agent-db.ts
10962
+ import { randomUUID } from "crypto";
10963
+ var _ready = false;
10964
+ async function migrateBrowserAgent() {
10965
+ if (_ready) return;
10966
+ const db = getDb();
10967
+ await db.execute(`
10968
+ CREATE TABLE IF NOT EXISTS browser_agent_sessions (
10969
+ id TEXT PRIMARY KEY,
10970
+ runtime_session_id TEXT NOT NULL,
10971
+ live_view_url TEXT,
10972
+ cdp_ws_url TEXT NOT NULL,
10973
+ status TEXT NOT NULL DEFAULT 'open',
10974
+ label TEXT,
10975
+ user_id INTEGER,
10976
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
10977
+ closed_at TEXT,
10978
+ last_action_at TEXT,
10979
+ active_ms INTEGER NOT NULL DEFAULT 0,
10980
+ billed_mc INTEGER NOT NULL DEFAULT 0
10981
+ )
10982
+ `);
10983
+ await db.execute(`CREATE INDEX IF NOT EXISTS browser_agent_sessions_status ON browser_agent_sessions(status)`);
10984
+ await db.execute(`CREATE INDEX IF NOT EXISTS browser_agent_sessions_user ON browser_agent_sessions(user_id)`);
10985
+ await db.execute(`
10986
+ CREATE TABLE IF NOT EXISTS browser_agent_actions (
10987
+ id TEXT PRIMARY KEY,
10988
+ session_id TEXT NOT NULL,
10989
+ type TEXT NOT NULL,
10990
+ params_json TEXT,
10991
+ ok INTEGER NOT NULL DEFAULT 1,
10992
+ error TEXT,
10993
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
10994
+ )
10995
+ `);
10996
+ await db.execute(`CREATE INDEX IF NOT EXISTS browser_agent_actions_session ON browser_agent_actions(session_id)`);
10997
+ await db.execute(`
10998
+ CREATE TABLE IF NOT EXISTS browser_agent_replays (
10999
+ replay_id TEXT PRIMARY KEY,
11000
+ session_id TEXT NOT NULL,
11001
+ view_url TEXT,
11002
+ label TEXT,
11003
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
11004
+ stopped_at TEXT
11005
+ )
11006
+ `);
11007
+ await db.execute(`CREATE INDEX IF NOT EXISTS browser_agent_replays_session ON browser_agent_replays(session_id)`);
11008
+ _ready = true;
11009
+ }
11010
+ async function createSessionRow(input) {
11011
+ const db = getDb();
11012
+ const id = `bas_${randomUUID().replace(/-/g, "").slice(0, 20)}`;
11013
+ await db.execute({
11014
+ sql: `INSERT INTO browser_agent_sessions (id, runtime_session_id, live_view_url, cdp_ws_url, status, label, user_id, last_action_at)
11015
+ VALUES (?, ?, ?, ?, 'open', ?, ?, datetime('now'))`,
11016
+ args: [id, input.runtimeSessionId, input.liveViewUrl, input.cdpWsUrl, input.label, input.userId]
11017
+ });
11018
+ const row = await getSessionRow(id);
11019
+ if (!row) throw new Error("session insert failed");
11020
+ return row;
11021
+ }
11022
+ async function getSessionRow(id) {
11023
+ const db = getDb();
11024
+ const res = await db.execute({ sql: `SELECT * FROM browser_agent_sessions WHERE id = ?`, args: [id] });
11025
+ return res.rows[0] ?? null;
11026
+ }
11027
+ async function listSessionRows(userId, includeClosed = false) {
11028
+ const db = getDb();
11029
+ const clauses = [];
11030
+ const args = [];
11031
+ if (userId != null) {
11032
+ clauses.push("(user_id = ? OR user_id IS NULL)");
11033
+ args.push(userId);
11034
+ }
11035
+ if (!includeClosed) clauses.push(`status = 'open'`);
11036
+ const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
11037
+ const res = await db.execute({
11038
+ sql: `SELECT * FROM browser_agent_sessions ${where} ORDER BY created_at DESC LIMIT 100`,
11039
+ args
11040
+ });
11041
+ return res.rows;
11042
+ }
11043
+ async function addActiveMs(id, deltaMs) {
11044
+ const db = getDb();
11045
+ await db.execute({
11046
+ sql: `UPDATE browser_agent_sessions SET active_ms = active_ms + ?, last_action_at = datetime('now') WHERE id = ?`,
11047
+ args: [Math.max(0, Math.round(deltaMs)), id]
11048
+ });
11049
+ const res = await db.execute({ sql: `SELECT active_ms, billed_mc FROM browser_agent_sessions WHERE id = ?`, args: [id] });
11050
+ const row = res.rows[0];
11051
+ return { active_ms: Number(row?.active_ms ?? 0), billed_mc: Number(row?.billed_mc ?? 0) };
11052
+ }
11053
+ async function setBilledMc(id, billedMc) {
11054
+ const db = getDb();
11055
+ await db.execute({
11056
+ sql: `UPDATE browser_agent_sessions SET billed_mc = ? WHERE id = ?`,
11057
+ args: [Math.round(billedMc), id]
11058
+ });
11059
+ }
11060
+ async function markSessionClosed(id) {
11061
+ const db = getDb();
11062
+ await db.execute({
11063
+ sql: `UPDATE browser_agent_sessions SET status = 'closed', closed_at = datetime('now') WHERE id = ?`,
11064
+ args: [id]
11065
+ });
11066
+ }
11067
+ async function recordAction(input) {
11068
+ const db = getDb();
11069
+ await db.execute({
11070
+ sql: `INSERT INTO browser_agent_actions (id, session_id, type, params_json, ok, error)
11071
+ VALUES (?, ?, ?, ?, ?, ?)`,
11072
+ args: [
11073
+ `baa_${randomUUID().replace(/-/g, "").slice(0, 20)}`,
11074
+ input.sessionId,
11075
+ input.type,
11076
+ input.params == null ? null : JSON.stringify(input.params),
11077
+ input.ok ? 1 : 0,
11078
+ input.error ?? null
11079
+ ]
11080
+ });
11081
+ }
11082
+ async function recordReplayStart(input) {
11083
+ const db = getDb();
11084
+ await db.execute({
11085
+ sql: `INSERT INTO browser_agent_replays (replay_id, session_id, view_url, label)
11086
+ VALUES (?, ?, ?, ?)`,
11087
+ args: [input.replayId, input.sessionId, input.viewUrl, input.label]
11088
+ });
11089
+ }
11090
+ async function recordReplayStop(replayId, viewUrl) {
11091
+ const db = getDb();
11092
+ await db.execute({
11093
+ sql: `UPDATE browser_agent_replays SET stopped_at = datetime('now'), view_url = COALESCE(?, view_url) WHERE replay_id = ?`,
11094
+ args: [viewUrl, replayId]
11095
+ });
11096
+ }
11097
+ async function listReplayRows(sessionId) {
11098
+ const db = getDb();
11099
+ const res = await db.execute({
11100
+ sql: `SELECT * FROM browser_agent_replays WHERE session_id = ? ORDER BY started_at DESC`,
11101
+ args: [sessionId]
11102
+ });
11103
+ return res.rows;
11104
+ }
11105
+
11106
+ // src/services/browser-agent/browser-agent-service.ts
11107
+ import Kernel3 from "@onkernel/sdk";
11108
+ import { chromium as playwrightChromium } from "playwright";
11109
+ var DEFAULT_TIMEOUT_SECONDS = 600;
11110
+ function client() {
11111
+ const apiKey = browserServiceApiKey();
11112
+ if (!apiKey) throw new Error("Browser backend API key is required");
11113
+ return new Kernel3({ apiKey });
11114
+ }
11115
+ async function createSession(opts = {}) {
11116
+ const k = client();
11117
+ const resolvedProxyId = opts.proxyId ?? browserServiceProxyId();
11118
+ const browser = await k.browsers.create({
11119
+ stealth: opts.stealth ?? true,
11120
+ timeout_seconds: opts.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS,
11121
+ ...resolvedProxyId ? { proxy_id: resolvedProxyId } : {},
11122
+ ...opts.profileName ? { profile: { name: opts.profileName } } : {}
11123
+ });
11124
+ const runtimeSessionId = browser.session_id;
11125
+ if (opts.disableDefaultProxy) {
11126
+ try {
11127
+ await k.browsers.update(runtimeSessionId, { disable_default_proxy: true });
11128
+ } catch {
11129
+ }
11130
+ }
11131
+ if (opts.viewport) {
11132
+ try {
11133
+ await k.browsers.update(runtimeSessionId, { viewport: opts.viewport });
11134
+ } catch {
11135
+ }
11136
+ }
11137
+ return {
11138
+ runtimeSessionId,
11139
+ liveViewUrl: browser.browser_live_view_url ?? null,
11140
+ cdpWsUrl: browser.cdp_ws_url
11141
+ };
11142
+ }
11143
+ async function closeSession(runtimeSessionId) {
11144
+ const k = client();
11145
+ await k.browsers.deleteByID(runtimeSessionId);
11146
+ }
11147
+ async function screenshot(runtimeSessionId) {
11148
+ const k = client();
11149
+ const res = await k.browsers.computer.captureScreenshot(runtimeSessionId);
11150
+ const buf = Buffer.from(await res.arrayBuffer());
11151
+ return { base64: buf.toString("base64"), mimeType: "image/png" };
11152
+ }
11153
+ async function click(runtimeSessionId, x, y, opts = {}) {
11154
+ const k = client();
11155
+ await k.browsers.computer.clickMouse(runtimeSessionId, {
11156
+ x,
11157
+ y,
11158
+ button: opts.button ?? "left",
11159
+ ...opts.numClicks ? { num_clicks: opts.numClicks } : {}
11160
+ });
11161
+ }
11162
+ async function typeText(runtimeSessionId, text, delayMs) {
11163
+ const k = client();
11164
+ await k.browsers.computer.typeText(runtimeSessionId, {
11165
+ text,
11166
+ ...typeof delayMs === "number" ? { delay: delayMs } : {}
11167
+ });
11168
+ }
11169
+ async function scroll(runtimeSessionId, x, y, deltaX, deltaY) {
11170
+ const k = client();
11171
+ await k.browsers.computer.scroll(runtimeSessionId, { x, y, delta_x: deltaX, delta_y: deltaY });
11172
+ }
11173
+ async function pressKeys(runtimeSessionId, keys) {
11174
+ const k = client();
11175
+ await k.browsers.computer.pressKey(runtimeSessionId, { keys });
11176
+ }
11177
+ async function goto(cdpWsUrl, url) {
11178
+ const browser = await playwrightChromium.connectOverCDP(cdpWsUrl);
11179
+ try {
11180
+ const context = browser.contexts()[0] ?? await browser.newContext();
11181
+ const page = context.pages()[0] ?? await context.newPage();
11182
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45e3 });
11183
+ return { url: page.url(), title: await page.title() };
11184
+ } finally {
11185
+ await browser.close().catch(() => {
11186
+ });
11187
+ }
11188
+ }
11189
+ async function readPage(cdpWsUrl) {
11190
+ const browser = await playwrightChromium.connectOverCDP(cdpWsUrl);
11191
+ try {
11192
+ const context = browser.contexts()[0] ?? await browser.newContext();
11193
+ const page = context.pages()[0] ?? await context.newPage();
11194
+ const url = page.url();
11195
+ const title = await page.title().catch(() => "");
11196
+ const data = await page.evaluate(() => {
11197
+ const SEL = 'a[href], button, input, textarea, select, [role="button"], [role="link"], [role="tab"], [onclick]';
11198
+ const out = [];
11199
+ const nodes = Array.from(document.querySelectorAll(SEL)).slice(0, 120);
11200
+ for (const el2 of nodes) {
11201
+ const r = el2.getBoundingClientRect();
11202
+ if (r.width < 1 || r.height < 1) continue;
11203
+ if (r.bottom < 0 || r.top > window.innerHeight) continue;
11204
+ const e = el2;
11205
+ const name = (e.getAttribute("aria-label") || e.placeholder || e.innerText || e.getAttribute("value") || e.getAttribute("title") || "").trim().replace(/\s+/g, " ").slice(0, 80);
11206
+ out.push({
11207
+ role: el2.getAttribute("role") || el2.tagName.toLowerCase(),
11208
+ name,
11209
+ x: Math.round(r.left + r.width / 2),
11210
+ y: Math.round(r.top + r.height / 2)
11211
+ });
11212
+ }
11213
+ const text = (document.body?.innerText || "").replace(/\n{3,}/g, "\n\n").trim().slice(0, 6e3);
11214
+ return { text, els: out };
11215
+ });
11216
+ return {
11217
+ url,
11218
+ title,
11219
+ text: data.text,
11220
+ elements: data.els.map((e, i) => ({ ref: i + 1, role: e.role, name: e.name, x: e.x, y: e.y }))
11221
+ };
11222
+ } finally {
11223
+ await browser.close().catch(() => {
11224
+ });
11225
+ }
11226
+ }
11227
+ async function replayStart(runtimeSessionId) {
11228
+ const k = client();
11229
+ const res = await k.browsers.replays.start(runtimeSessionId);
11230
+ return { replayId: res.replay_id, viewUrl: res.replay_view_url ?? null };
11231
+ }
11232
+ async function replayStop(runtimeSessionId, replayId) {
11233
+ const k = client();
11234
+ await k.browsers.replays.stop(replayId, { id: runtimeSessionId });
11235
+ }
11236
+ async function replayList(runtimeSessionId) {
11237
+ const k = client();
11238
+ const res = await k.browsers.replays.list(runtimeSessionId);
11239
+ return res.map((r) => ({
11240
+ replayId: r.replay_id,
11241
+ viewUrl: r.replay_view_url ?? null,
11242
+ startedAt: r.started_at ?? null,
11243
+ finishedAt: r.finished_at ?? null
11244
+ }));
11245
+ }
11246
+
11247
+ // src/api/browser-agent-routes.ts
11248
+ var auth = createApiKeyAuth();
11249
+ async function charge(sessionId, userId, startedAtMs) {
11250
+ const elapsedMs = Date.now() - startedAtMs;
11251
+ const { active_ms, billed_mc } = await addActiveMs(sessionId, elapsedMs);
11252
+ const owed = browserActiveCostMc(active_ms);
11253
+ const delta = owed - billed_mc;
11254
+ if (delta > 0) {
11255
+ const res = await debitMc(userId, delta, LedgerOperation.BROWSER_SESSION, sessionId);
11256
+ if (res.ok) await setBilledMc(sessionId, owed);
11257
+ }
11258
+ }
11259
+ function publicSession(row) {
11260
+ return {
11261
+ session_id: row.id,
11262
+ status: row.status,
11263
+ label: row.label,
11264
+ created_at: row.created_at,
11265
+ last_action_at: row.last_action_at,
11266
+ closed_at: row.closed_at,
11267
+ active_seconds: Math.round((row.active_ms ?? 0) / 1e3),
11268
+ credits_used: Math.round((row.billed_mc ?? 0) / 10) / 100
11269
+ };
11270
+ }
11271
+ function failure(err) {
11272
+ const msg = err instanceof Error ? err.message : String(err);
11273
+ return { error: sanitizeVendorName(msg) };
11274
+ }
11275
+ async function loadOpenSession(id, userId) {
11276
+ const row = await getSessionRow(id);
11277
+ if (!row) return null;
11278
+ if (row.user_id != null && row.user_id !== userId) return null;
11279
+ return row;
11280
+ }
11281
+ function buildBrowserAgentRoutes() {
11282
+ const app2 = new Hono8();
11283
+ app2.use("*", async (c, next) => {
11284
+ await migrateBrowserAgent();
11285
+ return next();
11286
+ });
11287
+ app2.use("*", auth);
11288
+ app2.post("/sessions", async (c) => {
11289
+ const user = c.get("user");
11290
+ if (Number(user.balance_mc ?? 0) < BROWSER_OPEN_MIN_BALANCE_MC) {
11291
+ return c.json(insufficientBalanceResponse(Number(user.balance_mc ?? 0), BROWSER_OPEN_MIN_BALANCE_MC), 402);
11292
+ }
11293
+ const body = await c.req.json().catch(() => ({}));
11294
+ try {
11295
+ const created = await createSession({
11296
+ timeoutSeconds: typeof body.timeout_seconds === "number" ? body.timeout_seconds : void 0,
11297
+ proxyId: typeof body.proxy_id === "string" ? body.proxy_id : void 0,
11298
+ profileName: typeof body.profile === "string" ? body.profile : void 0,
11299
+ disableDefaultProxy: body.disable_default_proxy === true,
11300
+ viewport: body.viewport && typeof body.viewport === "object" ? body.viewport : void 0
11301
+ });
11302
+ const row = await createSessionRow({
11303
+ runtimeSessionId: created.runtimeSessionId,
11304
+ liveViewUrl: created.liveViewUrl,
11305
+ cdpWsUrl: created.cdpWsUrl,
11306
+ label: typeof body.label === "string" ? body.label : null,
11307
+ userId: user.id
11308
+ });
11309
+ return c.json({ ...publicSession(row), watch_url: `/console/${row.id}` });
11310
+ } catch (err) {
11311
+ return c.json(failure(err), 502);
11312
+ }
11313
+ });
11314
+ app2.get("/sessions", async (c) => {
11315
+ const user = c.get("user");
11316
+ const includeClosed = c.req.query("all") === "1";
11317
+ const rows = await listSessionRows(user.id, includeClosed);
11318
+ return c.json({ sessions: rows.map(publicSession) });
11319
+ });
11320
+ app2.get("/sessions/:id", async (c) => {
11321
+ const user = c.get("user");
11322
+ const row = await loadOpenSession(c.req.param("id"), user.id);
11323
+ if (!row) return c.json({ error: "not found" }, 404);
11324
+ return c.json({ ...publicSession(row), watch_url: `/console/${row.id}` });
11325
+ });
11326
+ app2.get("/sessions/:id/live-view", async (c) => {
11327
+ const user = c.get("user");
11328
+ const row = await loadOpenSession(c.req.param("id"), user.id);
11329
+ if (!row) return c.json({ error: "not found" }, 404);
11330
+ return c.json({ live_view_url: row.live_view_url });
11331
+ });
11332
+ app2.delete("/sessions/:id", async (c) => {
11333
+ const user = c.get("user");
11334
+ const row = await loadOpenSession(c.req.param("id"), user.id);
11335
+ if (!row) return c.json({ error: "not found" }, 404);
11336
+ try {
11337
+ await closeSession(row.runtime_session_id);
11338
+ } catch {
11339
+ }
11340
+ await markSessionClosed(row.id);
11341
+ return c.json({ ok: true });
11342
+ });
11343
+ app2.post("/sessions/:id/goto", async (c) => {
11344
+ const user = c.get("user");
11345
+ const row = await loadOpenSession(c.req.param("id"), user.id);
11346
+ if (!row) return c.json({ error: "not found" }, 404);
11347
+ const body = await c.req.json().catch(() => ({}));
11348
+ const url = typeof body.url === "string" ? body.url : "";
11349
+ if (!url) return c.json({ error: "url is required" }, 400);
11350
+ const t0 = Date.now();
11351
+ try {
11352
+ const result = await goto(row.cdp_ws_url, url);
11353
+ await charge(row.id, user.id, t0);
11354
+ await recordAction({ sessionId: row.id, type: "goto", params: { url }, ok: true });
11355
+ return c.json(result);
11356
+ } catch (err) {
11357
+ await charge(row.id, user.id, t0);
11358
+ await recordAction({ sessionId: row.id, type: "goto", params: { url }, ok: false, error: String(err) });
11359
+ return c.json(failure(err), 502);
11360
+ }
11361
+ });
11362
+ app2.post("/sessions/:id/screenshot", async (c) => {
11363
+ const user = c.get("user");
11364
+ const row = await loadOpenSession(c.req.param("id"), user.id);
11365
+ if (!row) return c.json({ error: "not found" }, 404);
11366
+ const t0 = Date.now();
11367
+ try {
11368
+ const shot = await screenshot(row.runtime_session_id);
11369
+ let page = null;
11370
+ try {
11371
+ page = await readPage(row.cdp_ws_url);
11372
+ } catch {
11373
+ page = null;
11374
+ }
11375
+ await charge(row.id, user.id, t0);
11376
+ await recordAction({ sessionId: row.id, type: "screenshot", params: null, ok: true });
11377
+ return c.json({
11378
+ image_base64: shot.base64,
11379
+ mime_type: shot.mimeType,
11380
+ url: page?.url ?? null,
11381
+ title: page?.title ?? null,
11382
+ elements: page?.elements ?? [],
11383
+ text: page?.text ?? null
11384
+ });
11385
+ } catch (err) {
11386
+ await charge(row.id, user.id, t0);
11387
+ await recordAction({ sessionId: row.id, type: "screenshot", params: null, ok: false, error: String(err) });
11388
+ return c.json(failure(err), 502);
11389
+ }
11390
+ });
11391
+ app2.post("/sessions/:id/read", async (c) => {
11392
+ const user = c.get("user");
11393
+ const row = await loadOpenSession(c.req.param("id"), user.id);
11394
+ if (!row) return c.json({ error: "not found" }, 404);
11395
+ const t0 = Date.now();
11396
+ try {
11397
+ const page = await readPage(row.cdp_ws_url);
11398
+ await charge(row.id, user.id, t0);
11399
+ await recordAction({ sessionId: row.id, type: "read", params: null, ok: true });
11400
+ return c.json(page);
11401
+ } catch (err) {
11402
+ await charge(row.id, user.id, t0);
11403
+ return c.json(failure(err), 502);
11404
+ }
11405
+ });
11406
+ app2.post("/sessions/:id/click", async (c) => {
11407
+ const user = c.get("user");
11408
+ const row = await loadOpenSession(c.req.param("id"), user.id);
11409
+ if (!row) return c.json({ error: "not found" }, 404);
11410
+ const body = await c.req.json().catch(() => ({}));
11411
+ const x = Number(body.x);
11412
+ const y = Number(body.y);
11413
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return c.json({ error: "x and y are required" }, 400);
11414
+ const t0 = Date.now();
11415
+ try {
11416
+ await click(row.runtime_session_id, x, y, {
11417
+ button: body.button === "right" || body.button === "middle" ? body.button : "left",
11418
+ numClicks: typeof body.num_clicks === "number" ? body.num_clicks : void 0
11419
+ });
11420
+ await charge(row.id, user.id, t0);
11421
+ await recordAction({ sessionId: row.id, type: "click", params: { x, y }, ok: true });
11422
+ return c.json({ ok: true });
11423
+ } catch (err) {
11424
+ await charge(row.id, user.id, t0);
11425
+ await recordAction({ sessionId: row.id, type: "click", params: { x, y }, ok: false, error: String(err) });
11426
+ return c.json(failure(err), 502);
11427
+ }
11428
+ });
11429
+ app2.post("/sessions/:id/type", async (c) => {
11430
+ const user = c.get("user");
11431
+ const row = await loadOpenSession(c.req.param("id"), user.id);
11432
+ if (!row) return c.json({ error: "not found" }, 404);
11433
+ const body = await c.req.json().catch(() => ({}));
11434
+ const text = typeof body.text === "string" ? body.text : "";
11435
+ if (!text) return c.json({ error: "text is required" }, 400);
11436
+ const t0 = Date.now();
11437
+ try {
11438
+ await typeText(row.runtime_session_id, text, typeof body.delay === "number" ? body.delay : void 0);
11439
+ await charge(row.id, user.id, t0);
11440
+ await recordAction({ sessionId: row.id, type: "type", params: { length: text.length }, ok: true });
11441
+ return c.json({ ok: true });
11442
+ } catch (err) {
11443
+ await charge(row.id, user.id, t0);
11444
+ return c.json(failure(err), 502);
11445
+ }
11446
+ });
11447
+ app2.post("/sessions/:id/scroll", async (c) => {
11448
+ const user = c.get("user");
11449
+ const row = await loadOpenSession(c.req.param("id"), user.id);
11450
+ if (!row) return c.json({ error: "not found" }, 404);
11451
+ const body = await c.req.json().catch(() => ({}));
11452
+ const x = typeof body.x === "number" ? body.x : 640;
11453
+ const y = typeof body.y === "number" ? body.y : 400;
11454
+ const deltaX = typeof body.delta_x === "number" ? body.delta_x : 0;
11455
+ const deltaY = typeof body.delta_y === "number" ? body.delta_y : 5;
11456
+ const t0 = Date.now();
11457
+ try {
11458
+ await scroll(row.runtime_session_id, x, y, deltaX, deltaY);
11459
+ await charge(row.id, user.id, t0);
11460
+ await recordAction({ sessionId: row.id, type: "scroll", params: { deltaX, deltaY }, ok: true });
11461
+ return c.json({ ok: true });
11462
+ } catch (err) {
11463
+ await charge(row.id, user.id, t0);
11464
+ return c.json(failure(err), 502);
11465
+ }
11466
+ });
11467
+ app2.post("/sessions/:id/press", async (c) => {
11468
+ const user = c.get("user");
11469
+ const row = await loadOpenSession(c.req.param("id"), user.id);
11470
+ if (!row) return c.json({ error: "not found" }, 404);
11471
+ const body = await c.req.json().catch(() => ({}));
11472
+ const keys = Array.isArray(body.keys) ? body.keys.map(String) : [];
11473
+ if (!keys.length) return c.json({ error: "keys is required" }, 400);
11474
+ const t0 = Date.now();
11475
+ try {
11476
+ await pressKeys(row.runtime_session_id, keys);
11477
+ await charge(row.id, user.id, t0);
11478
+ await recordAction({ sessionId: row.id, type: "press", params: { keys }, ok: true });
11479
+ return c.json({ ok: true });
11480
+ } catch (err) {
11481
+ await charge(row.id, user.id, t0);
11482
+ return c.json(failure(err), 502);
11483
+ }
11484
+ });
11485
+ app2.post("/sessions/:id/replay/start", async (c) => {
11486
+ const user = c.get("user");
11487
+ const row = await loadOpenSession(c.req.param("id"), user.id);
11488
+ if (!row) return c.json({ error: "not found" }, 404);
11489
+ const body = await c.req.json().catch(() => ({}));
11490
+ try {
11491
+ const started = await replayStart(row.runtime_session_id);
11492
+ await recordReplayStart({
11493
+ sessionId: row.id,
11494
+ replayId: started.replayId,
11495
+ viewUrl: started.viewUrl,
11496
+ label: typeof body.label === "string" ? body.label : null
11497
+ });
11498
+ return c.json({ replay_id: started.replayId });
11499
+ } catch (err) {
11500
+ return c.json(failure(err), 502);
11501
+ }
11502
+ });
11503
+ app2.post("/sessions/:id/replay/stop", async (c) => {
11504
+ const user = c.get("user");
11505
+ const row = await loadOpenSession(c.req.param("id"), user.id);
11506
+ if (!row) return c.json({ error: "not found" }, 404);
11507
+ const body = await c.req.json().catch(() => ({}));
11508
+ const replayId = typeof body.replay_id === "string" ? body.replay_id : "";
11509
+ if (!replayId) return c.json({ error: "replay_id is required" }, 400);
11510
+ try {
11511
+ await replayStop(row.runtime_session_id, replayId);
11512
+ let viewUrl = null;
11513
+ try {
11514
+ const all = await replayList(row.runtime_session_id);
11515
+ viewUrl = all.find((r) => r.replayId === replayId)?.viewUrl ?? null;
11516
+ } catch {
11517
+ viewUrl = null;
11518
+ }
11519
+ await recordReplayStop(replayId, viewUrl);
11520
+ return c.json({ ok: true });
11521
+ } catch (err) {
11522
+ return c.json(failure(err), 502);
11523
+ }
11524
+ });
11525
+ app2.get("/sessions/:id/replays", async (c) => {
11526
+ const user = c.get("user");
11527
+ const row = await loadOpenSession(c.req.param("id"), user.id);
11528
+ if (!row) return c.json({ error: "not found" }, 404);
11529
+ const rows = await listReplayRows(row.id);
11530
+ return c.json({
11531
+ replays: rows.map((r) => ({
11532
+ replay_id: r.replay_id,
11533
+ view_url: r.view_url,
11534
+ label: r.label,
11535
+ started_at: r.started_at,
11536
+ stopped_at: r.stopped_at
11537
+ }))
11538
+ });
11539
+ });
11540
+ return app2;
11541
+ }
11542
+
11543
+ // src/api/browser-agent-console.ts
11544
+ function renderConsoleHtml(initialSessionId) {
11545
+ const initial = JSON.stringify(initialSessionId ?? "");
11546
+ return `<!DOCTYPE html>
11547
+ <html lang="en">
11548
+ <head>
11549
+ <meta charset="UTF-8" />
11550
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
11551
+ <title>Browser Agent Console</title>
11552
+ <style>
11553
+ :root { color-scheme: dark; }
11554
+ :where(*) { box-sizing: border-box; }
11555
+ body { margin: 0; font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, sans-serif; background: #0b0e14; color: #d7dce5; }
11556
+ header { display: flex; align-items: center; gap: 12px; padding: 10px 16px; border-bottom: 1px solid #1c2230; background: #0f131c; }
11557
+ header h1 { font-size: 15px; margin: 0; font-weight: 600; color: #fff; letter-spacing: .2px; }
11558
+ header .spacer { flex: 1; }
11559
+ input, button, select { font: inherit; }
11560
+ input[type=text], input[type=password], input[type=url] { background: #141925; border: 1px solid #232b3a; color: #e6eaf2; border-radius: 7px; padding: 7px 10px; }
11561
+ button { background: #2b6cff; border: 0; color: #fff; border-radius: 7px; padding: 7px 12px; cursor: pointer; font-weight: 500; }
11562
+ button.ghost { background: #1a2030; color: #cdd5e4; border: 1px solid #28303f; }
11563
+ button:disabled { opacity: .5; cursor: default; }
11564
+ .layout { display: grid; grid-template-columns: 280px 1fr; height: calc(100vh - 53px); }
11565
+ aside { border-right: 1px solid #1c2230; overflow-y: auto; padding: 12px; }
11566
+ aside h2 { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: #6b7689; margin: 4px 4px 10px; }
11567
+ .sess { padding: 9px 10px; border-radius: 8px; border: 1px solid #1c2230; margin-bottom: 8px; cursor: pointer; }
11568
+ .sess:hover { border-color: #2b6cff; }
11569
+ .sess.active { border-color: #2b6cff; background: #131b2e; }
11570
+ .sess .id { font-family: ui-monospace, monospace; font-size: 12px; color: #aeb8cc; }
11571
+ .sess .meta { font-size: 11px; color: #6b7689; margin-top: 3px; }
11572
+ .dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 5px; }
11573
+ .dot.open { background: #36d399; } .dot.closed { background: #5a6677; }
11574
+ main { display: flex; flex-direction: column; overflow: hidden; }
11575
+ .toolbar { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-bottom: 1px solid #1c2230; }
11576
+ .toolbar label { font-size: 13px; color: #aeb8cc; display: flex; align-items: center; gap: 6px; }
11577
+ .stage { flex: 1; position: relative; background: #05070b; overflow: auto; }
11578
+ .stage iframe { width: 100%; height: 100%; border: 0; display: block; }
11579
+ .empty { display: flex; align-items: center; justify-content: center; height: 100%; color: #5a6677; flex-direction: column; gap: 10px; }
11580
+ .replays { border-top: 1px solid #1c2230; padding: 10px 16px; max-height: 200px; overflow-y: auto; }
11581
+ .replays h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: #6b7689; margin: 0 0 8px; }
11582
+ .replay { display: flex; align-items: center; gap: 10px; padding: 6px 0; font-size: 13px; }
11583
+ .replay a { color: #7aa2ff; }
11584
+ .gate { max-width: 380px; margin: 80px auto; padding: 24px; border: 1px solid #1c2230; border-radius: 12px; background: #0f131c; }
11585
+ .gate h2 { margin: 0 0 6px; font-size: 16px; color: #fff; }
11586
+ .gate p { color: #8893a7; margin: 0 0 16px; }
11587
+ .gate input { width: 100%; margin-bottom: 12px; }
11588
+ .gate button { width: 100%; }
11589
+ .muted { color: #6b7689; font-size: 12px; }
11590
+ </style>
11591
+ </head>
11592
+ <body>
11593
+ <div id="app"></div>
11594
+ <script>
11595
+ const INITIAL_SESSION = ${initial};
11596
+ const KEY_STORE = 'browser_agent_api_key';
11597
+ let state = { key: localStorage.getItem(KEY_STORE) || '', sessions: [], current: INITIAL_SESSION || null, readOnly: true, liveUrl: null, replays: [] };
11598
+
11599
+ function api(method, path, body) {
11600
+ return fetch('/agent' + path, {
11601
+ method,
11602
+ headers: { 'Content-Type': 'application/json', 'x-api-key': state.key },
11603
+ body: body ? JSON.stringify(body) : undefined,
11604
+ }).then(async r => ({ ok: r.ok, data: await r.json().catch(() => ({})) }));
11605
+ }
11606
+
11607
+ async function refreshSessions() {
11608
+ const r = await api('GET', '/sessions?all=1');
11609
+ if (r.ok) { state.sessions = r.data.sessions || []; render(); }
11610
+ }
11611
+
11612
+ async function selectSession(id) {
11613
+ state.current = id; state.liveUrl = null; state.replays = [];
11614
+ history.replaceState(null, '', '/console/' + id);
11615
+ render();
11616
+ const live = await api('GET', '/sessions/' + id + '/live-view');
11617
+ state.liveUrl = live.ok ? live.data.live_view_url : null;
11618
+ const reps = await api('GET', '/sessions/' + id + '/replays');
11619
+ state.replays = reps.ok ? (reps.data.replays || []) : [];
11620
+ render();
11621
+ }
11622
+
11623
+ async function openSession() {
11624
+ const r = await api('POST', '/sessions', { label: 'console' });
11625
+ if (r.ok) { await refreshSessions(); selectSession(r.data.session_id); }
11626
+ else alert('Open failed: ' + JSON.stringify(r.data));
11627
+ }
11628
+
11629
+ async function closeCurrent() {
11630
+ if (!state.current) return;
11631
+ await api('DELETE', '/sessions/' + state.current);
11632
+ await refreshSessions();
11633
+ }
11634
+
11635
+ function frameSrc() {
11636
+ if (!state.liveUrl) return null;
11637
+ const sep = state.liveUrl.includes('?') ? '&' : '?';
11638
+ return state.readOnly ? state.liveUrl + sep + 'readOnly=true' : state.liveUrl;
11639
+ }
11640
+
11641
+ function saveKey(v) { state.key = v.trim(); localStorage.setItem(KEY_STORE, state.key); render(); if (state.key) { refreshSessions(); if (state.current) selectSession(state.current); } }
11642
+
11643
+ function h(html) { const t = document.createElement('template'); t.innerHTML = html.trim(); return t.content.firstChild; }
11644
+ function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c])); }
11645
+
11646
+ function render() {
11647
+ const app = document.getElementById('app');
11648
+ app.innerHTML = '';
11649
+ if (!state.key) {
11650
+ app.appendChild(h('<div class="gate"><h2>Browser Agent Console</h2><p>Paste your API key to watch and control browser sessions.</p><input id="k" type="password" placeholder="API key" /><button id="kb">Continue</button></div>'));
11651
+ document.getElementById('kb').onclick = () => saveKey(document.getElementById('k').value);
11652
+ document.getElementById('k').onkeydown = e => { if (e.key === 'Enter') saveKey(e.target.value); };
11653
+ return;
11654
+ }
11655
+ const header = h('<header><h1>Browser Agent</h1><span class="muted">live control + replays</span><span class="spacer"></span><button id="open">+ New Session</button><button class="ghost" id="logout">Forget key</button></header>');
11656
+ app.appendChild(header);
11657
+ document.getElementById('open').onclick = openSession;
11658
+ document.getElementById('logout').onclick = () => saveKey('');
11659
+
11660
+ const layout = h('<div class="layout"></div>');
11661
+ const aside = h('<aside><h2>Sessions</h2></aside>');
11662
+ if (!state.sessions.length) aside.appendChild(h('<div class="muted" style="padding:4px">No sessions yet.</div>'));
11663
+ for (const s of state.sessions) {
11664
+ const el = h('<div class="sess ' + (s.session_id === state.current ? 'active' : '') + '"><div class="id">' + esc(s.session_id) + '</div><div class="meta"><span class="dot ' + esc(s.status) + '"></span>' + esc(s.status) + (s.label ? ' \xB7 ' + esc(s.label) : '') + '</div></div>');
11665
+ el.onclick = () => selectSession(s.session_id);
11666
+ aside.appendChild(el);
11667
+ }
11668
+ layout.appendChild(aside);
11669
+
11670
+ const main = h('<main></main>');
11671
+ if (!state.current) {
11672
+ main.appendChild(h('<div class="empty"><div>Select or open a session to watch.</div></div>'));
11673
+ } else {
11674
+ const tb = h('<div class="toolbar"><label><input type="checkbox" id="ro" ' + (state.readOnly ? 'checked' : '') + ' /> Read-only (uncheck to take control)</label><span class="spacer"></span><button class="ghost" id="reload">Reload view</button><button class="ghost" id="close">Close session</button></div>');
11675
+ main.appendChild(tb);
11676
+ const stage = h('<div class="stage"></div>');
11677
+ const src = frameSrc();
11678
+ if (src) {
11679
+ const f = h('<iframe allow="autoplay; clipboard-read; clipboard-write" src="' + esc(src) + '"></iframe>');
11680
+ stage.appendChild(f);
11681
+ } else {
11682
+ stage.appendChild(h('<div class="empty"><div>Live view unavailable for this session.</div><div class="muted">It may be closed or still starting.</div></div>'));
11683
+ }
11684
+ main.appendChild(stage);
11685
+
11686
+ const rep = h('<div class="replays"><h3>Replays</h3></div>');
11687
+ if (!state.replays.length) rep.appendChild(h('<div class="muted">No replays recorded.</div>'));
11688
+ for (const r of state.replays) {
11689
+ const status = r.stopped_at ? 'ready' : 'recording\u2026';
11690
+ const link = r.view_url ? '<a href="' + esc(r.view_url) + '" target="_blank" rel="noopener">view mp4</a>' : '<span class="muted">' + status + '</span>';
11691
+ rep.appendChild(h('<div class="replay"><span class="muted">' + esc(r.started_at || '') + '</span><span class="spacer"></span>' + link + '</div>'));
11692
+ }
11693
+ main.appendChild(rep);
11694
+
11695
+ layout.appendChild(main);
11696
+ }
11697
+ app.appendChild(layout);
11698
+
11699
+ const ro = document.getElementById('ro');
11700
+ if (ro) ro.onchange = e => { state.readOnly = e.target.checked; render(); };
11701
+ const reload = document.getElementById('reload');
11702
+ if (reload) reload.onclick = () => selectSession(state.current);
11703
+ const close = document.getElementById('close');
11704
+ if (close) close.onclick = closeCurrent;
11705
+ }
11706
+
11707
+ render();
11708
+ if (state.key) { refreshSessions(); if (state.current) selectSession(state.current); }
11709
+ </script>
11710
+ </body>
11711
+ </html>`;
11712
+ }
11713
+
10952
11714
  // src/api/stripe-routes.ts
10953
11715
  import Stripe from "stripe";
10954
- import { Hono as Hono8 } from "hono";
11716
+ import { Hono as Hono9 } from "hono";
10955
11717
  var stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
10956
- var stripeApp = new Hono8();
11718
+ var stripeApp = new Hono9();
10957
11719
  stripeApp.post("/webhooks", async (c) => {
10958
11720
  const sig = c.req.header("stripe-signature");
10959
11721
  const body = await c.req.text();
@@ -11250,7 +12012,7 @@ var requireAllowedOrigin = createMiddleware3(async (c, next) => {
11250
12012
  if (!configuredOrigins().has(origin)) return c.json({ error: "Origin not allowed" }, 403);
11251
12013
  return next();
11252
12014
  });
11253
- var auth = createMiddleware3(async (c, next) => {
12015
+ var auth2 = createMiddleware3(async (c, next) => {
11254
12016
  const key = c.req.header("x-api-key");
11255
12017
  if (!key) return c.json({ error: "Missing API key" }, 401);
11256
12018
  const user = await getUserByApiKey(key);
@@ -11279,7 +12041,7 @@ var sessionAuth = createMiddleware3(async (c, next) => {
11279
12041
  c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
11280
12042
  return next();
11281
12043
  });
11282
- var app = new Hono9();
12044
+ var app = new Hono10();
11283
12045
  var STRIPE_API_VERSION = "2026-02-25.clover";
11284
12046
  function requireStripeSecret() {
11285
12047
  const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
@@ -11489,7 +12251,7 @@ async function checkHarvestLimits(userId, email, extraSlots = 0) {
11489
12251
  if (active >= limit) return { error: `You have ${active} job${active !== 1 ? "s" : ""} running. Your account allows ${limit} concurrent job${limit !== 1 ? "s" : ""}. Wait for one to finish or add a concurrency slot at mcpscraper.dev/billing.` };
11490
12252
  return null;
11491
12253
  }
11492
- app.post("/harvest", auth, async (c) => {
12254
+ app.post("/harvest", auth2, async (c) => {
11493
12255
  const user = c.get("user");
11494
12256
  const raw = await c.req.json().catch(() => ({}));
11495
12257
  const bodyResult = HarvestBodySchema.safeParse(raw);
@@ -11531,7 +12293,7 @@ app.post("/harvest", auth, async (c) => {
11531
12293
  }
11532
12294
  return c.json({ job_id: jobId, status: "pending" }, 202);
11533
12295
  });
11534
- app.post("/harvest/sync", auth, async (c) => {
12296
+ app.post("/harvest/sync", auth2, async (c) => {
11535
12297
  const user = c.get("user");
11536
12298
  const raw = await c.req.json().catch(() => ({}));
11537
12299
  const bodyResult = HarvestBodySchema.safeParse(raw);
@@ -11596,17 +12358,17 @@ app.post("/harvest/sync", auth, async (c) => {
11596
12358
  return c.json({ job_id: jobId, status: "failed", ...response, attempts: sanitizeAttempts(attempts) }, problem.httpStatus);
11597
12359
  }
11598
12360
  });
11599
- app.get("/jobs/:id", auth, async (c) => {
12361
+ app.get("/jobs/:id", auth2, async (c) => {
11600
12362
  const job = await getJob(c.req.param("id"), c.get("user").id);
11601
12363
  if (!job) return c.json({ error: "Job not found" }, 404);
11602
12364
  const attempts = await listHarvestAttempts(job.id, c.get("user").id);
11603
12365
  const safeResult = job.result && typeof job.result === "object" ? sanitizeHarvestResult(job.result) : job.result;
11604
12366
  return c.json({ ...job, result: safeResult, attempts: sanitizeAttempts(attempts) });
11605
12367
  });
11606
- app.get("/jobs", auth, async (c) => {
12368
+ app.get("/jobs", auth2, async (c) => {
11607
12369
  return c.json(await listJobs(c.get("user").id));
11608
12370
  });
11609
- app.get("/history", auth, async (c) => {
12371
+ app.get("/history", auth2, async (c) => {
11610
12372
  const userId = c.get("user").id;
11611
12373
  const [jobs, events] = await Promise.all([
11612
12374
  listJobs(userId),
@@ -11636,7 +12398,7 @@ app.get("/history", auth, async (c) => {
11636
12398
  const rows = [...jobRows, ...eventRows].sort((a, b) => String(b.ts).localeCompare(String(a.ts)));
11637
12399
  return c.json(rows.slice(0, 100));
11638
12400
  });
11639
- app.get("/ledger", auth, async (c) => {
12401
+ app.get("/ledger", auth2, async (c) => {
11640
12402
  return c.json(await getLedger(c.get("user").id, 100));
11641
12403
  });
11642
12404
  app.post("/admin/users", adminAuth, async (c) => {
@@ -11674,11 +12436,11 @@ app.post("/admin/backfill-signup-credits", adminAuth, async (c) => {
11674
12436
  }
11675
12437
  return c.json({ processed, credited, skipped, users_credited });
11676
12438
  });
11677
- app.post("/extract-url", auth, async (c) => {
12439
+ app.post("/extract-url", auth2, async (c) => {
11678
12440
  const raw = await c.req.json().catch(() => ({}));
11679
12441
  const bodyResult = ExtractUrlBodySchema.safeParse(raw);
11680
12442
  if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
11681
- const { url, screenshot, screenshotDevice, extractBranding, downloadMedia, mediaTypes, allowLocal } = bodyResult.data;
12443
+ const { url, screenshot: screenshot2, screenshotDevice, extractBranding, downloadMedia, mediaTypes, allowLocal } = bodyResult.data;
11682
12444
  if (!allowLocal) {
11683
12445
  const checked = await validatePublicHttpUrl(url, { field: "URL" });
11684
12446
  if (checked.error || !checked.parsed) return c.json({ error: checked.error ?? "Invalid URL" }, 400);
@@ -11704,7 +12466,7 @@ app.post("/extract-url", auth, async (c) => {
11704
12466
  const device = screenshotDevice === "mobile" ? "mobile" : "desktop";
11705
12467
  const [result, pageData] = await Promise.all([
11706
12468
  extractKpo({ url: canonicalUrl, kernelApiKey }),
11707
- screenshot || extractBranding ? capturePageData(canonicalUrl, { kernelApiKey, device, screenshot: !!screenshot, branding: !!extractBranding }).catch(() => null) : null
12469
+ screenshot2 || extractBranding ? capturePageData(canonicalUrl, { kernelApiKey, device, screenshot: !!screenshot2, branding: !!extractBranding }).catch(() => null) : null
11708
12470
  ]);
11709
12471
  const screenshotBuf = pageData?.screenshot ?? null;
11710
12472
  const brandingData = pageData?.branding ?? null;
@@ -11722,7 +12484,7 @@ app.post("/extract-url", auth, async (c) => {
11722
12484
  return c.json({ error: msg }, 500);
11723
12485
  }
11724
12486
  });
11725
- app.post("/map-urls", auth, async (c) => {
12487
+ app.post("/map-urls", auth2, async (c) => {
11726
12488
  const raw = await c.req.json().catch(() => ({}));
11727
12489
  const bodyResult = MapUrlsBodySchema.safeParse(raw);
11728
12490
  if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
@@ -11762,7 +12524,7 @@ app.post("/map-urls", auth, async (c) => {
11762
12524
  return c.json({ error: msg }, 500);
11763
12525
  }
11764
12526
  });
11765
- app.post("/extract-site", auth, async (c) => {
12527
+ app.post("/extract-site", auth2, async (c) => {
11766
12528
  const raw = await c.req.json().catch(() => ({}));
11767
12529
  const bodyResult = ExtractSiteBodySchema.safeParse(raw);
11768
12530
  if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
@@ -11884,7 +12646,7 @@ app.post("/billing/concurrency/cancel", requireAllowedOrigin, sessionAuth, async
11884
12646
  await setConcurrencySubId(user.id, null);
11885
12647
  return c.json({ ok: true, concurrency_limit: user.extra_concurrency_slots });
11886
12648
  });
11887
- app.get("/billing/balance", auth, async (c) => {
12649
+ app.get("/billing/balance", auth2, async (c) => {
11888
12650
  const user = c.get("user");
11889
12651
  const balanceMc = await reconcileBalanceMc(user.id);
11890
12652
  const ledger = await getLedger(user.id, 20);
@@ -11896,7 +12658,7 @@ app.get("/billing/balance", auth, async (c) => {
11896
12658
  ledger
11897
12659
  });
11898
12660
  });
11899
- app.post("/billing/credits", auth, async (c) => {
12661
+ app.post("/billing/credits", auth2, async (c) => {
11900
12662
  const user = c.get("user");
11901
12663
  const balanceMc = await reconcileBalanceMc(user.id);
11902
12664
  const body = await c.req.json().catch(() => ({}));
@@ -11925,7 +12687,7 @@ app.get("/cron/tick", async (c) => {
11925
12687
  if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
11926
12688
  return c.json({ error: "Unauthorized" }, 401);
11927
12689
  }
11928
- const { drainQueue } = await import("./worker-AUCXFHEL.js");
12690
+ const { drainQueue } = await import("./worker-KJ4A7WIR.js");
11929
12691
  const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
11930
12692
  const [results, sweepResult] = await Promise.all([
11931
12693
  drainQueue(budget),
@@ -11941,6 +12703,9 @@ app.route("/facebook", facebookAdApp);
11941
12703
  app.route("/maps", mapsApp);
11942
12704
  app.route("/serp-intelligence", serpIntelligenceApp);
11943
12705
  app.route("/mcp", mcpApp);
12706
+ app.route("/agent", buildBrowserAgentRoutes());
12707
+ app.get("/console", (c) => c.html(renderConsoleHtml()));
12708
+ app.get("/console/:id", (c) => c.html(renderConsoleHtml(c.req.param("id"))));
11944
12709
  app.route("/stripe", stripeApp);
11945
12710
  if (!process.env.INNGEST_EVENT_KEY) {
11946
12711
  startSiteAuditWorker();
@@ -12047,4 +12812,4 @@ app.get("/blog/:slug/", (c) => {
12047
12812
  export {
12048
12813
  app
12049
12814
  };
12050
- //# sourceMappingURL=server-QXVVTKJP.js.map
12815
+ //# sourceMappingURL=server-ASCMKUQ5.js.map