mcp-scraper 0.1.9 → 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.
@@ -6,10 +6,12 @@ import {
6
6
  configureReportSaving,
7
7
  harvestTimeoutBudget,
8
8
  liveWebToolAnnotations
9
- } from "./chunk-JNC32DMS.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
  });
@@ -10952,11 +10955,767 @@ mcpApp.all("/", async (c) => {
10952
10955
  }
10953
10956
  });
10954
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
+
10955
11714
  // src/api/stripe-routes.ts
10956
11715
  import Stripe from "stripe";
10957
- import { Hono as Hono8 } from "hono";
11716
+ import { Hono as Hono9 } from "hono";
10958
11717
  var stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
10959
- var stripeApp = new Hono8();
11718
+ var stripeApp = new Hono9();
10960
11719
  stripeApp.post("/webhooks", async (c) => {
10961
11720
  const sig = c.req.header("stripe-signature");
10962
11721
  const body = await c.req.text();
@@ -11253,7 +12012,7 @@ var requireAllowedOrigin = createMiddleware3(async (c, next) => {
11253
12012
  if (!configuredOrigins().has(origin)) return c.json({ error: "Origin not allowed" }, 403);
11254
12013
  return next();
11255
12014
  });
11256
- var auth = createMiddleware3(async (c, next) => {
12015
+ var auth2 = createMiddleware3(async (c, next) => {
11257
12016
  const key = c.req.header("x-api-key");
11258
12017
  if (!key) return c.json({ error: "Missing API key" }, 401);
11259
12018
  const user = await getUserByApiKey(key);
@@ -11282,7 +12041,7 @@ var sessionAuth = createMiddleware3(async (c, next) => {
11282
12041
  c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
11283
12042
  return next();
11284
12043
  });
11285
- var app = new Hono9();
12044
+ var app = new Hono10();
11286
12045
  var STRIPE_API_VERSION = "2026-02-25.clover";
11287
12046
  function requireStripeSecret() {
11288
12047
  const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
@@ -11492,7 +12251,7 @@ async function checkHarvestLimits(userId, email, extraSlots = 0) {
11492
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.` };
11493
12252
  return null;
11494
12253
  }
11495
- app.post("/harvest", auth, async (c) => {
12254
+ app.post("/harvest", auth2, async (c) => {
11496
12255
  const user = c.get("user");
11497
12256
  const raw = await c.req.json().catch(() => ({}));
11498
12257
  const bodyResult = HarvestBodySchema.safeParse(raw);
@@ -11534,7 +12293,7 @@ app.post("/harvest", auth, async (c) => {
11534
12293
  }
11535
12294
  return c.json({ job_id: jobId, status: "pending" }, 202);
11536
12295
  });
11537
- app.post("/harvest/sync", auth, async (c) => {
12296
+ app.post("/harvest/sync", auth2, async (c) => {
11538
12297
  const user = c.get("user");
11539
12298
  const raw = await c.req.json().catch(() => ({}));
11540
12299
  const bodyResult = HarvestBodySchema.safeParse(raw);
@@ -11599,17 +12358,17 @@ app.post("/harvest/sync", auth, async (c) => {
11599
12358
  return c.json({ job_id: jobId, status: "failed", ...response, attempts: sanitizeAttempts(attempts) }, problem.httpStatus);
11600
12359
  }
11601
12360
  });
11602
- app.get("/jobs/:id", auth, async (c) => {
12361
+ app.get("/jobs/:id", auth2, async (c) => {
11603
12362
  const job = await getJob(c.req.param("id"), c.get("user").id);
11604
12363
  if (!job) return c.json({ error: "Job not found" }, 404);
11605
12364
  const attempts = await listHarvestAttempts(job.id, c.get("user").id);
11606
12365
  const safeResult = job.result && typeof job.result === "object" ? sanitizeHarvestResult(job.result) : job.result;
11607
12366
  return c.json({ ...job, result: safeResult, attempts: sanitizeAttempts(attempts) });
11608
12367
  });
11609
- app.get("/jobs", auth, async (c) => {
12368
+ app.get("/jobs", auth2, async (c) => {
11610
12369
  return c.json(await listJobs(c.get("user").id));
11611
12370
  });
11612
- app.get("/history", auth, async (c) => {
12371
+ app.get("/history", auth2, async (c) => {
11613
12372
  const userId = c.get("user").id;
11614
12373
  const [jobs, events] = await Promise.all([
11615
12374
  listJobs(userId),
@@ -11639,7 +12398,7 @@ app.get("/history", auth, async (c) => {
11639
12398
  const rows = [...jobRows, ...eventRows].sort((a, b) => String(b.ts).localeCompare(String(a.ts)));
11640
12399
  return c.json(rows.slice(0, 100));
11641
12400
  });
11642
- app.get("/ledger", auth, async (c) => {
12401
+ app.get("/ledger", auth2, async (c) => {
11643
12402
  return c.json(await getLedger(c.get("user").id, 100));
11644
12403
  });
11645
12404
  app.post("/admin/users", adminAuth, async (c) => {
@@ -11677,11 +12436,11 @@ app.post("/admin/backfill-signup-credits", adminAuth, async (c) => {
11677
12436
  }
11678
12437
  return c.json({ processed, credited, skipped, users_credited });
11679
12438
  });
11680
- app.post("/extract-url", auth, async (c) => {
12439
+ app.post("/extract-url", auth2, async (c) => {
11681
12440
  const raw = await c.req.json().catch(() => ({}));
11682
12441
  const bodyResult = ExtractUrlBodySchema.safeParse(raw);
11683
12442
  if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
11684
- const { url, screenshot, screenshotDevice, extractBranding, downloadMedia, mediaTypes, allowLocal } = bodyResult.data;
12443
+ const { url, screenshot: screenshot2, screenshotDevice, extractBranding, downloadMedia, mediaTypes, allowLocal } = bodyResult.data;
11685
12444
  if (!allowLocal) {
11686
12445
  const checked = await validatePublicHttpUrl(url, { field: "URL" });
11687
12446
  if (checked.error || !checked.parsed) return c.json({ error: checked.error ?? "Invalid URL" }, 400);
@@ -11707,7 +12466,7 @@ app.post("/extract-url", auth, async (c) => {
11707
12466
  const device = screenshotDevice === "mobile" ? "mobile" : "desktop";
11708
12467
  const [result, pageData] = await Promise.all([
11709
12468
  extractKpo({ url: canonicalUrl, kernelApiKey }),
11710
- 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
11711
12470
  ]);
11712
12471
  const screenshotBuf = pageData?.screenshot ?? null;
11713
12472
  const brandingData = pageData?.branding ?? null;
@@ -11725,7 +12484,7 @@ app.post("/extract-url", auth, async (c) => {
11725
12484
  return c.json({ error: msg }, 500);
11726
12485
  }
11727
12486
  });
11728
- app.post("/map-urls", auth, async (c) => {
12487
+ app.post("/map-urls", auth2, async (c) => {
11729
12488
  const raw = await c.req.json().catch(() => ({}));
11730
12489
  const bodyResult = MapUrlsBodySchema.safeParse(raw);
11731
12490
  if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
@@ -11765,7 +12524,7 @@ app.post("/map-urls", auth, async (c) => {
11765
12524
  return c.json({ error: msg }, 500);
11766
12525
  }
11767
12526
  });
11768
- app.post("/extract-site", auth, async (c) => {
12527
+ app.post("/extract-site", auth2, async (c) => {
11769
12528
  const raw = await c.req.json().catch(() => ({}));
11770
12529
  const bodyResult = ExtractSiteBodySchema.safeParse(raw);
11771
12530
  if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
@@ -11887,7 +12646,7 @@ app.post("/billing/concurrency/cancel", requireAllowedOrigin, sessionAuth, async
11887
12646
  await setConcurrencySubId(user.id, null);
11888
12647
  return c.json({ ok: true, concurrency_limit: user.extra_concurrency_slots });
11889
12648
  });
11890
- app.get("/billing/balance", auth, async (c) => {
12649
+ app.get("/billing/balance", auth2, async (c) => {
11891
12650
  const user = c.get("user");
11892
12651
  const balanceMc = await reconcileBalanceMc(user.id);
11893
12652
  const ledger = await getLedger(user.id, 20);
@@ -11899,7 +12658,7 @@ app.get("/billing/balance", auth, async (c) => {
11899
12658
  ledger
11900
12659
  });
11901
12660
  });
11902
- app.post("/billing/credits", auth, async (c) => {
12661
+ app.post("/billing/credits", auth2, async (c) => {
11903
12662
  const user = c.get("user");
11904
12663
  const balanceMc = await reconcileBalanceMc(user.id);
11905
12664
  const body = await c.req.json().catch(() => ({}));
@@ -11928,7 +12687,7 @@ app.get("/cron/tick", async (c) => {
11928
12687
  if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
11929
12688
  return c.json({ error: "Unauthorized" }, 401);
11930
12689
  }
11931
- const { drainQueue } = await import("./worker-AUCXFHEL.js");
12690
+ const { drainQueue } = await import("./worker-KJ4A7WIR.js");
11932
12691
  const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
11933
12692
  const [results, sweepResult] = await Promise.all([
11934
12693
  drainQueue(budget),
@@ -11944,6 +12703,9 @@ app.route("/facebook", facebookAdApp);
11944
12703
  app.route("/maps", mapsApp);
11945
12704
  app.route("/serp-intelligence", serpIntelligenceApp);
11946
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"))));
11947
12709
  app.route("/stripe", stripeApp);
11948
12710
  if (!process.env.INNGEST_EVENT_KEY) {
11949
12711
  startSiteAuditWorker();
@@ -12050,4 +12812,4 @@ app.get("/blog/:slug/", (c) => {
12050
12812
  export {
12051
12813
  app
12052
12814
  };
12053
- //# sourceMappingURL=server-MTXAJG5J.js.map
12815
+ //# sourceMappingURL=server-ASCMKUQ5.js.map