mcp-scraper 0.4.4 → 0.4.5

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.
@@ -16934,22 +16934,8 @@ async function provisionMemoryForUser(user) {
16934
16934
  } catch {
16935
16935
  }
16936
16936
  }
16937
- function memoryPlanForUser(user) {
16938
- switch (user.subscription_tier) {
16939
- case "starter":
16940
- return "pro";
16941
- case "growth":
16942
- return "team";
16943
- case "scale":
16944
- return "enterprise";
16945
- default:
16946
- return "free";
16947
- }
16948
- }
16949
16937
  function resolveEffectiveMemoryPlan(user) {
16950
- const tierPlan = memoryPlanForUser(user);
16951
- const memoryProPlan = user.memory_plan === "pro" || user.memory_plan === "team" ? user.memory_plan : "free";
16952
- return MEMORY_PLAN_RANK[tierPlan] >= MEMORY_PLAN_RANK[memoryProPlan] ? tierPlan : memoryProPlan;
16938
+ return user.memory_plan === "pro" || user.memory_plan === "team" ? user.memory_plan : "free";
16953
16939
  }
16954
16940
  async function getOrCreateUserMemoryKey(user) {
16955
16941
  const creds = await getUserMemoryCreds(user.id);
@@ -16999,7 +16985,7 @@ async function syncScheduleEntitlement(user, enabled, quotaPerPeriod) {
16999
16985
  }, {});
17000
16986
  return { ok: res.ok, error: res.error };
17001
16987
  }
17002
- var import_node_crypto6, import_provision_defaults, import_set_schedule_entitlement, MEMORY_BASE_URL, ADMIN_KEY, MEMORY_PLAN_RANK;
16988
+ var import_node_crypto6, import_provision_defaults, import_set_schedule_entitlement, MEMORY_BASE_URL, ADMIN_KEY;
17003
16989
  var init_memory = __esm({
17004
16990
  "src/api/memory.ts"() {
17005
16991
  "use strict";
@@ -17010,7 +16996,6 @@ var init_memory = __esm({
17010
16996
  import_set_schedule_entitlement = require("mcpscraper-memory-tools/tools/schedule/set-schedule-entitlement");
17011
16997
  MEMORY_BASE_URL = () => (process.env.MCP_MEMORY_BASE_URL ?? "https://memory.mcpscraper.dev").replace(/\/$/, "");
17012
16998
  ADMIN_KEY = () => process.env.MCP_MEMORY_ADMIN_KEY?.trim() ?? "";
17013
- MEMORY_PLAN_RANK = { free: 0, pro: 1, team: 2, enterprise: 3 };
17014
16999
  }
17015
17000
  });
17016
17001
 
@@ -27018,7 +27003,7 @@ var PACKAGE_VERSION;
27018
27003
  var init_version = __esm({
27019
27004
  "src/version.ts"() {
27020
27005
  "use strict";
27021
- PACKAGE_VERSION = "0.4.4";
27006
+ PACKAGE_VERSION = "0.4.5";
27022
27007
  }
27023
27008
  });
27024
27009
 
@@ -29277,7 +29262,7 @@ var init_export = __esm({
29277
29262
  });
29278
29263
 
29279
29264
  // src/mcp/browser-agent-tool-schemas.ts
29280
- var import_zod34, NullableString2, BrowserRawObject, BrowserElementOutput, BrowserBaseOutput, BrowserReplayBaseOutput, BrowserOpenInputSchema, BrowserProfileConnectInputSchema, BrowserProfileListInputSchema, BrowserSessionInputSchema, BrowserLocateTargetSchema, BrowserLocateInputSchema, BrowserGotoInputSchema, BrowserClickInputSchema, BrowserTypeInputSchema, BrowserScrollInputSchema, BrowserPressInputSchema, BrowserReplayStopInputSchema, BrowserReplayDownloadInputSchema, BrowserReplayAnnotationSchema, BrowserReplayMarkInputSchema, BrowserReplayAnnotateInputSchema, BrowserListInputSchema, BrowserCaptureFanoutInputSchema, FanoutSourceOutput, BrowserCaptureFanoutOutputSchema, BrowserOpenOutputSchema, BrowserProfileLogin, BrowserProfileConnectOutputSchema, BrowserProfileListOutputSchema, BrowserScreenshotOutputSchema, BrowserReadOutputSchema, BrowserLocateOutputSchema, BrowserActionOutputSchema, BrowserReplayStartOutputSchema, BrowserReplayStopOutputSchema, BrowserListReplaysOutputSchema, BrowserReplayDownloadOutputSchema, BrowserReplayMarkOutputSchema, BrowserReplayAnnotateOutputSchema, BrowserCloseOutputSchema, BrowserListSessionsOutputSchema;
29265
+ var import_zod34, NullableString2, BrowserRawObject, BrowserElementOutput, BrowserBaseOutput, BrowserReplayBaseOutput, BrowserOpenInputSchema, BrowserProfileConnectInputSchema, BrowserProfileListInputSchema, BrowserSessionInputSchema, BrowserLocateTargetSchema, BrowserLocateInputSchema, BrowserGotoInputSchema, BrowserClickInputSchema, BrowserTypeInputSchema, BrowserScrollInputSchema, BrowserPressInputSchema, BrowserReplayStopInputSchema, BrowserReplayDownloadInputSchema, BrowserReplayAnnotationSchema, BrowserReplayMarkInputSchema, BrowserReplayAnnotateInputSchema, BrowserListInputSchema, BrowserCaptureFanoutInputSchema, FanoutSourceOutput, BrowserCaptureFanoutOutputSchema, BrowserOpenOutputSchema, BrowserProfileLogin, BrowserProfileConnectOutputSchema, BrowserProfileListOutputSchema, BrowserExtensionImportInputSchema, BrowserExtensionImportOutputSchema, BrowserExtensionSummary, BrowserExtensionListInputSchema, BrowserExtensionListOutputSchema, BrowserExtensionDeleteInputSchema, BrowserExtensionDeleteOutputSchema, BrowserScreenshotOutputSchema, BrowserReadOutputSchema, BrowserLocateOutputSchema, BrowserActionOutputSchema, BrowserReplayStartOutputSchema, BrowserReplayStopOutputSchema, BrowserListReplaysOutputSchema, BrowserReplayDownloadOutputSchema, BrowserReplayMarkOutputSchema, BrowserReplayAnnotateOutputSchema, BrowserCloseOutputSchema, BrowserListSessionsOutputSchema;
29281
29266
  var init_browser_agent_tool_schemas = __esm({
29282
29267
  "src/mcp/browser-agent-tool-schemas.ts"() {
29283
29268
  "use strict";
@@ -29299,7 +29284,10 @@ var init_browser_agent_tool_schemas = __esm({
29299
29284
  url: import_zod34.z.string().url().optional().describe("Optional URL to navigate to immediately after opening."),
29300
29285
  profile: import_zod34.z.string().optional().describe("Optional saved hosted profile name to load a logged-in session for a site."),
29301
29286
  save_profile_changes: import_zod34.z.boolean().optional().describe("Persist cookies/storage back to the named profile on close. Avoid parallel sessions writing to the same profile."),
29302
- timeout_seconds: import_zod34.z.number().int().min(60).max(259200).optional().describe("Session lifetime before auto-termination. Defaults to 600.")
29287
+ timeout_seconds: import_zod34.z.number().int().min(60).max(259200).optional().describe("Session lifetime before auto-termination. Defaults to 600."),
29288
+ extension_names: import_zod34.z.array(import_zod34.z.string()).optional().describe(
29289
+ "Names of extensions previously added with browser_extension_import (see browser_extension_list for what's available) to load into this session. Loading extensions restarts the browser, adding a few seconds to startup."
29290
+ )
29303
29291
  };
29304
29292
  BrowserProfileConnectInputSchema = {
29305
29293
  email: import_zod34.z.string().optional().describe("Account email for the login. Derives a stable profile name and is recorded as a note. Does NOT import existing cookies \u2014 the user signs in fresh."),
@@ -29526,6 +29514,43 @@ var init_browser_agent_tool_schemas = __esm({
29526
29514
  connections: import_zod34.z.array(BrowserProfileLogin).describe("All site logins saved in this profile, each with its current auth status and note."),
29527
29515
  count: import_zod34.z.number().int().min(0)
29528
29516
  };
29517
+ BrowserExtensionImportInputSchema = {
29518
+ store_url: import_zod34.z.string().url().describe("Chrome Web Store URL of the extension to add, e.g. https://chromewebstore.google.com/detail/<slug>/<id>."),
29519
+ name: import_zod34.z.string().min(1).max(64).describe('Short name to save this extension under, e.g. "ani-ai". Reuse it later in extension_names on browser_open.')
29520
+ };
29521
+ BrowserExtensionImportOutputSchema = {
29522
+ ok: import_zod34.z.boolean(),
29523
+ tool: import_zod34.z.literal("browser_extension_import"),
29524
+ session_id: import_zod34.z.null(),
29525
+ name: import_zod34.z.string().describe("The name this extension was saved under."),
29526
+ source_url: import_zod34.z.string().describe("The store URL this extension was imported from."),
29527
+ size_bytes: import_zod34.z.number().nullable().describe("Size of the extension package in bytes.")
29528
+ };
29529
+ BrowserExtensionSummary = import_zod34.z.object({
29530
+ name: import_zod34.z.string(),
29531
+ source: import_zod34.z.string().describe('Always "store" for extensions added via browser_extension_import.'),
29532
+ source_url: NullableString2,
29533
+ size_bytes: import_zod34.z.number().nullable(),
29534
+ created_at: import_zod34.z.string()
29535
+ });
29536
+ BrowserExtensionListInputSchema = {};
29537
+ BrowserExtensionListOutputSchema = {
29538
+ ok: import_zod34.z.boolean(),
29539
+ tool: import_zod34.z.literal("browser_extension_list"),
29540
+ session_id: import_zod34.z.null(),
29541
+ extensions: import_zod34.z.array(BrowserExtensionSummary).describe("Every extension available to load via extension_names on browser_open."),
29542
+ count: import_zod34.z.number().int().min(0)
29543
+ };
29544
+ BrowserExtensionDeleteInputSchema = {
29545
+ name: import_zod34.z.string().min(1).describe("Name of the extension to remove, as returned by browser_extension_list.")
29546
+ };
29547
+ BrowserExtensionDeleteOutputSchema = {
29548
+ ok: import_zod34.z.boolean(),
29549
+ tool: import_zod34.z.literal("browser_extension_delete"),
29550
+ session_id: import_zod34.z.null(),
29551
+ name: import_zod34.z.string(),
29552
+ deleted: import_zod34.z.boolean()
29553
+ };
29529
29554
  BrowserScreenshotOutputSchema = {
29530
29555
  ...BrowserBaseOutput,
29531
29556
  tool: import_zod34.z.literal("browser_screenshot"),
@@ -30149,6 +30174,71 @@ function registerBrowserAgentMcpTools(server, opts) {
30149
30174
  });
30150
30175
  }
30151
30176
  );
30177
+ server.registerTool(
30178
+ "browser_extension_import",
30179
+ {
30180
+ title: "Add Browser Extension",
30181
+ description: "Add a Chrome extension from its Chrome Web Store page so it can be loaded into browser_open sessions via extension_names. One-time setup per extension \u2014 check what's already added with browser_extension_list first. The extension starts logged out in any session; sign into it once inside a session, pairing with a saved profile (browser_open's profile + save_profile_changes) to keep it signed in on future opens.",
30182
+ inputSchema: BrowserExtensionImportInputSchema,
30183
+ outputSchema: recordOutputSchema("browser_extension_import", BrowserExtensionImportOutputSchema),
30184
+ annotations: annotations("Add Browser Extension")
30185
+ },
30186
+ async (input) => {
30187
+ const res = await req("POST", "/agent/extensions/import", { store_url: input.store_url, name: input.name });
30188
+ if (!res.ok) return errorResult("browser_extension_import", res.data);
30189
+ return structuredResult({
30190
+ ok: true,
30191
+ tool: "browser_extension_import",
30192
+ session_id: null,
30193
+ name: res.data.name,
30194
+ source_url: res.data.source_url,
30195
+ size_bytes: res.data.size_bytes ?? null
30196
+ });
30197
+ }
30198
+ );
30199
+ server.registerTool(
30200
+ "browser_extension_list",
30201
+ {
30202
+ title: "List Browser Extensions",
30203
+ description: "List extensions added via browser_extension_import, for use as extension_names on browser_open. Read-only, no cost.",
30204
+ inputSchema: BrowserExtensionListInputSchema,
30205
+ outputSchema: recordOutputSchema("browser_extension_list", BrowserExtensionListOutputSchema),
30206
+ annotations: annotations("List Browser Extensions", true)
30207
+ },
30208
+ async () => {
30209
+ const res = await req("GET", "/agent/extensions");
30210
+ if (!res.ok) return errorResult("browser_extension_list", res.data);
30211
+ const extensions = Array.isArray(res.data?.extensions) ? res.data.extensions : [];
30212
+ return structuredResult({
30213
+ ok: true,
30214
+ tool: "browser_extension_list",
30215
+ session_id: null,
30216
+ extensions,
30217
+ count: extensions.length
30218
+ });
30219
+ }
30220
+ );
30221
+ server.registerTool(
30222
+ "browser_extension_delete",
30223
+ {
30224
+ title: "Remove Browser Extension",
30225
+ description: "Remove a previously added extension by name so it can no longer be loaded via extension_names.",
30226
+ inputSchema: BrowserExtensionDeleteInputSchema,
30227
+ outputSchema: recordOutputSchema("browser_extension_delete", BrowserExtensionDeleteOutputSchema),
30228
+ annotations: { title: "Remove Browser Extension", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
30229
+ },
30230
+ async (input) => {
30231
+ const res = await req("DELETE", `/agent/extensions/${encodeURIComponent(input.name)}`);
30232
+ if (!res.ok) return errorResult("browser_extension_delete", res.data);
30233
+ return structuredResult({
30234
+ ok: true,
30235
+ tool: "browser_extension_delete",
30236
+ session_id: null,
30237
+ name: input.name,
30238
+ deleted: true
30239
+ });
30240
+ }
30241
+ );
30152
30242
  server.registerTool(
30153
30243
  "browser_open",
30154
30244
  {
@@ -30167,7 +30257,8 @@ function registerBrowserAgentMcpTools(server, opts) {
30167
30257
  ...profile && typeof saveProfileChanges === "boolean" ? { save_profile_changes: saveProfileChanges } : {},
30168
30258
  disable_default_proxy: true,
30169
30259
  timeout_seconds: input.timeout_seconds,
30170
- ...input.url ? { url: input.url } : {}
30260
+ ...input.url ? { url: input.url } : {},
30261
+ ...input.extension_names?.length ? { extension_names: input.extension_names } : {}
30171
30262
  });
30172
30263
  if (!open.ok) return errorResult("browser_open", open.data);
30173
30264
  const session = open.data;
@@ -33435,8 +33526,58 @@ async function migrateBrowserAgent() {
33435
33526
  } catch {
33436
33527
  }
33437
33528
  await db.execute(`CREATE INDEX IF NOT EXISTS browser_auth_connections_profile ON browser_auth_connections(profile)`);
33529
+ await db.execute(`
33530
+ CREATE TABLE IF NOT EXISTS browser_agent_extensions (
33531
+ id TEXT PRIMARY KEY,
33532
+ user_id INTEGER NOT NULL,
33533
+ name TEXT NOT NULL,
33534
+ backend_id TEXT NOT NULL,
33535
+ backend_name TEXT NOT NULL,
33536
+ source TEXT NOT NULL DEFAULT 'store',
33537
+ source_url TEXT,
33538
+ size_bytes INTEGER,
33539
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
33540
+ )
33541
+ `);
33542
+ await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS browser_agent_extensions_user_name ON browser_agent_extensions(user_id, name)`);
33543
+ await db.execute(`CREATE INDEX IF NOT EXISTS browser_agent_extensions_user ON browser_agent_extensions(user_id)`);
33438
33544
  _ready2 = true;
33439
33545
  }
33546
+ async function createExtensionRow(input) {
33547
+ const db = getDb();
33548
+ const id = `bext_${(0, import_node_crypto11.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
33549
+ await db.execute({
33550
+ sql: `INSERT INTO browser_agent_extensions (id, user_id, name, backend_id, backend_name, source, source_url, size_bytes)
33551
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
33552
+ args: [id, input.userId, input.name, input.backendId, input.backendName, input.source, input.sourceUrl, input.sizeBytes]
33553
+ });
33554
+ const row = await getExtensionRow(input.userId, input.name);
33555
+ if (!row) throw new Error("extension insert failed");
33556
+ return row;
33557
+ }
33558
+ async function getExtensionRow(userId, name) {
33559
+ const db = getDb();
33560
+ const res = await db.execute({
33561
+ sql: `SELECT * FROM browser_agent_extensions WHERE user_id = ? AND name = ?`,
33562
+ args: [userId, name]
33563
+ });
33564
+ return res.rows[0] ?? null;
33565
+ }
33566
+ async function listExtensionRows(userId) {
33567
+ const db = getDb();
33568
+ const res = await db.execute({
33569
+ sql: `SELECT * FROM browser_agent_extensions WHERE user_id = ? ORDER BY created_at DESC`,
33570
+ args: [userId]
33571
+ });
33572
+ return res.rows;
33573
+ }
33574
+ async function deleteExtensionRow(userId, name) {
33575
+ const db = getDb();
33576
+ await db.execute({
33577
+ sql: `DELETE FROM browser_agent_extensions WHERE user_id = ? AND name = ?`,
33578
+ args: [userId, name]
33579
+ });
33580
+ }
33440
33581
  async function createAuthConnectionRow(input) {
33441
33582
  const db = getDb();
33442
33583
  const connectionId = `authc_${(0, import_node_crypto11.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
@@ -34353,6 +34494,28 @@ function isProfileConflict(err) {
34353
34494
  const message = err instanceof Error ? err.message : String(err);
34354
34495
  return /\b409\b|conflict|already exists/i.test(message);
34355
34496
  }
34497
+ function isNotFound(err) {
34498
+ const message = err instanceof Error ? err.message : String(err);
34499
+ return /\b404\b|not found/i.test(message);
34500
+ }
34501
+ async function importExtensionFromStore(storeUrl, backendName) {
34502
+ const k = client();
34503
+ const archive = await k.extensions.downloadFromChromeStore({ url: storeUrl });
34504
+ const uploaded = await k.extensions.upload({ file: archive, name: backendName });
34505
+ return {
34506
+ backendId: uploaded.id,
34507
+ backendName: uploaded.name ?? backendName,
34508
+ sizeBytes: typeof uploaded.size_bytes === "number" ? uploaded.size_bytes : null
34509
+ };
34510
+ }
34511
+ async function deleteExtensionBackend(backendName) {
34512
+ const k = client();
34513
+ try {
34514
+ await k.extensions.delete(backendName);
34515
+ } catch (err) {
34516
+ if (!isNotFound(err)) throw err;
34517
+ }
34518
+ }
34356
34519
  async function ensureProfile(k, name) {
34357
34520
  try {
34358
34521
  await k.profiles.create({ name });
@@ -34371,6 +34534,7 @@ async function createSession(opts = {}) {
34371
34534
  const resolvedSaveProfileChanges = opts.saveProfileChanges ?? browserServiceProfileSaveChanges();
34372
34535
  const disableDefaultProxy = opts.disableDefaultProxy ?? !resolvedProxyId;
34373
34536
  const startUrl = opts.startUrl?.trim();
34537
+ const extensionNames = (opts.extensionNames ?? []).map((name) => name.trim()).filter(Boolean);
34374
34538
  if (resolvedProfileName && resolvedSaveProfileChanges === true) {
34375
34539
  await ensureProfile(k, resolvedProfileName);
34376
34540
  }
@@ -34384,7 +34548,8 @@ async function createSession(opts = {}) {
34384
34548
  name: resolvedProfileName,
34385
34549
  ...typeof resolvedSaveProfileChanges === "boolean" ? { save_changes: resolvedSaveProfileChanges } : {}
34386
34550
  }
34387
- } : {}
34551
+ } : {},
34552
+ ...extensionNames.length ? { extensions: extensionNames.map((name) => ({ name })) } : {}
34388
34553
  };
34389
34554
  const browser = await k.browsers.create(createPayload);
34390
34555
  const runtimeSessionId = browser.session_id;
@@ -34781,6 +34946,15 @@ function parseAgentDate(value) {
34781
34946
  const legacyParsed = Date.parse(`${value.replace(" ", "T")}Z`);
34782
34947
  return Number.isFinite(legacyParsed) ? legacyParsed : null;
34783
34948
  }
34949
+ function publicExtension(row) {
34950
+ return {
34951
+ name: row.name,
34952
+ source: row.source,
34953
+ source_url: row.source_url,
34954
+ size_bytes: row.size_bytes,
34955
+ created_at: row.created_at
34956
+ };
34957
+ }
34784
34958
  function replayClock(row) {
34785
34959
  if (!row) return null;
34786
34960
  const startedAtMs = parseAgentDate(row.started_at);
@@ -34927,6 +35101,15 @@ function buildBrowserAgentRoutes() {
34927
35101
  const body = await c.req.json().catch(() => ({}));
34928
35102
  const timeoutSeconds = typeof body.timeout_seconds === "number" ? body.timeout_seconds : void 0;
34929
35103
  const startUrl = typeof body.url === "string" ? body.url : void 0;
35104
+ const extensionNames = Array.isArray(body.extension_names) ? body.extension_names.filter((v) => typeof v === "string") : void 0;
35105
+ let resolvedExtensionBackendNames;
35106
+ if (extensionNames && extensionNames.length) {
35107
+ const names = extensionNames;
35108
+ const rows = await Promise.all(names.map((name) => getExtensionRow(user.id, name)));
35109
+ const missing = names.filter((_, i) => !rows[i]);
35110
+ if (missing.length) return c.json({ error: `extension(s) not found: ${missing.join(", ")}` }, 400);
35111
+ resolvedExtensionBackendNames = rows.map((row) => row.backend_name);
35112
+ }
34930
35113
  const gate = await acquireConcurrencyGate(user, "browser_agent_session", {
34931
35114
  ttlSeconds: browserSessionLockTtlSeconds(timeoutSeconds),
34932
35115
  metadata: { label: typeof body.label === "string" ? body.label : null }
@@ -34941,7 +35124,8 @@ function buildBrowserAgentRoutes() {
34941
35124
  saveProfileChanges: typeof body.save_profile_changes === "boolean" ? body.save_profile_changes : void 0,
34942
35125
  startUrl,
34943
35126
  disableDefaultProxy: typeof body.disable_default_proxy === "boolean" ? body.disable_default_proxy : void 0,
34944
- viewport: body.viewport && typeof body.viewport === "object" ? body.viewport : void 0
35127
+ viewport: body.viewport && typeof body.viewport === "object" ? body.viewport : void 0,
35128
+ extensionNames: resolvedExtensionBackendNames
34945
35129
  });
34946
35130
  runtimeSessionId = created.runtimeSessionId;
34947
35131
  const row = await createSessionRow({
@@ -35244,6 +35428,52 @@ function buildBrowserAgentRoutes() {
35244
35428
  }))
35245
35429
  });
35246
35430
  });
35431
+ app2.post("/extensions/import", async (c) => {
35432
+ const user = c.get("user");
35433
+ const body = await c.req.json().catch(() => ({}));
35434
+ const storeUrl = typeof body.store_url === "string" ? body.store_url.trim() : "";
35435
+ const name = typeof body.name === "string" ? body.name.trim() : "";
35436
+ if (!storeUrl) return c.json({ error: "store_url is required" }, 400);
35437
+ if (!name || !EXTENSION_NAME_RE.test(name)) {
35438
+ return c.json({ error: "name is required (1-64 characters: letters, numbers, spaces, dots, underscores, hyphens)" }, 400);
35439
+ }
35440
+ const existing = await getExtensionRow(user.id, name);
35441
+ if (existing) return c.json({ error: `an extension named "${name}" already exists \u2014 delete it first or pick another name` }, 409);
35442
+ const backendName = `u${user.id}_${(0, import_node_crypto12.randomUUID)().replace(/-/g, "")}`;
35443
+ try {
35444
+ const imported = await importExtensionFromStore(storeUrl, backendName);
35445
+ const row = await createExtensionRow({
35446
+ userId: user.id,
35447
+ name,
35448
+ backendId: imported.backendId,
35449
+ backendName: imported.backendName,
35450
+ source: "store",
35451
+ sourceUrl: storeUrl,
35452
+ sizeBytes: imported.sizeBytes
35453
+ });
35454
+ return c.json(publicExtension(row));
35455
+ } catch (err) {
35456
+ return c.json(failure(err), 502);
35457
+ }
35458
+ });
35459
+ app2.get("/extensions", async (c) => {
35460
+ const user = c.get("user");
35461
+ const rows = await listExtensionRows(user.id);
35462
+ return c.json({ extensions: rows.map(publicExtension) });
35463
+ });
35464
+ app2.delete("/extensions/:name", async (c) => {
35465
+ const user = c.get("user");
35466
+ const name = c.req.param("name");
35467
+ const row = await getExtensionRow(user.id, name);
35468
+ if (!row) return c.json({ error: "not found" }, 404);
35469
+ try {
35470
+ await deleteExtensionBackend(row.backend_name);
35471
+ } catch (err) {
35472
+ return c.json(failure(err), 502);
35473
+ }
35474
+ await deleteExtensionRow(user.id, name);
35475
+ return c.json({ ok: true });
35476
+ });
35247
35477
  app2.get("/sessions/:id/replays/:replayId/download", async (c) => {
35248
35478
  const user = c.get("user");
35249
35479
  const row = await loadOpenSession(c.req.param("id"), user.id);
@@ -35268,10 +35498,11 @@ function buildBrowserAgentRoutes() {
35268
35498
  });
35269
35499
  return app2;
35270
35500
  }
35271
- var import_hono17, auth, DEFAULT_BROWSER_SESSION_LOCK_TTL_SECONDS;
35501
+ var import_node_crypto12, import_hono17, auth, DEFAULT_BROWSER_SESSION_LOCK_TTL_SECONDS, EXTENSION_NAME_RE;
35272
35502
  var init_browser_agent_routes = __esm({
35273
35503
  "src/api/browser-agent-routes.ts"() {
35274
35504
  "use strict";
35505
+ import_node_crypto12 = require("crypto");
35275
35506
  import_hono17 = require("hono");
35276
35507
  init_api_auth();
35277
35508
  init_errors();
@@ -35282,6 +35513,7 @@ var init_browser_agent_routes = __esm({
35282
35513
  init_concurrency_gates();
35283
35514
  auth = createApiKeyAuth();
35284
35515
  DEFAULT_BROWSER_SESSION_LOCK_TTL_SECONDS = 2 * 60 * 60;
35516
+ EXTENSION_NAME_RE = /^[\w .-]{1,64}$/;
35285
35517
  }
35286
35518
  });
35287
35519
 
@@ -35640,7 +35872,6 @@ var init_stripe_routes = __esm({
35640
35872
  const live = sub.status === "active" || sub.status === "trialing";
35641
35873
  const newTier = live ? tier.tier : null;
35642
35874
  await setSubscriptionTier(user.id, newTier, live ? tier.concurrency : 0, live ? sub.id : null);
35643
- await syncMemoryKeyPlan(user, resolveEffectiveMemoryPlan({ ...user, subscription_tier: newTier }));
35644
35875
  } else if (priceId === CONCURRENCY_PRICE_ID && event.type === "customer.subscription.created") {
35645
35876
  await setExtraConcurrencySlots(user.id, user.extra_concurrency_slots + 1);
35646
35877
  await setConcurrencySubId(user.id, sub.id);
@@ -35666,7 +35897,6 @@ var init_stripe_routes = __esm({
35666
35897
  if (!user) return c.json({ received: true });
35667
35898
  if (priceId && priceId in SUBSCRIPTION_TIERS) {
35668
35899
  await setSubscriptionTier(user.id, null, 0, null);
35669
- await syncMemoryKeyPlan(user, resolveEffectiveMemoryPlan({ ...user, subscription_tier: null }));
35670
35900
  } else if (priceId === CONCURRENCY_PRICE_ID) {
35671
35901
  await setExtraConcurrencySlots(user.id, Math.max(0, user.extra_concurrency_slots - 1));
35672
35902
  await setConcurrencySubId(user.id, null);
@@ -35690,7 +35920,7 @@ async function getKeys() {
35690
35920
  const privateKey = await (0, import_jose2.importPKCS8)(pem, "RS256", { extractable: true });
35691
35921
  const full = await (0, import_jose2.exportJWK)(privateKey);
35692
35922
  const publicJwk = { kty: full.kty, n: full.n, e: full.e };
35693
- const kid = (0, import_node_crypto12.createHash)("sha256").update(JSON.stringify({ e: publicJwk.e, kty: publicJwk.kty, n: publicJwk.n })).digest("base64url").slice(0, 16);
35923
+ const kid = (0, import_node_crypto13.createHash)("sha256").update(JSON.stringify({ e: publicJwk.e, kty: publicJwk.kty, n: publicJwk.n })).digest("base64url").slice(0, 16);
35694
35924
  publicJwk.kid = kid;
35695
35925
  publicJwk.alg = "RS256";
35696
35926
  publicJwk.use = "sig";
@@ -35869,23 +36099,23 @@ async function validateAuthRequest(p) {
35869
36099
  }
35870
36100
  function pkceMatches(verifier, challenge) {
35871
36101
  if (!verifier) return false;
35872
- const computed = (0, import_node_crypto12.createHash)("sha256").update(verifier).digest("base64url");
36102
+ const computed = (0, import_node_crypto13.createHash)("sha256").update(verifier).digest("base64url");
35873
36103
  return computed === challenge;
35874
36104
  }
35875
36105
  async function mintAccessToken(identity, scope, plan, audience) {
35876
36106
  const { privateKey, kid } = await getKeys();
35877
- return new import_jose2.SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti((0, import_node_crypto12.randomUUID)()).setExpirationTime(`${ACCESS_TTL_SECONDS}s`).sign(privateKey);
36107
+ return new import_jose2.SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti((0, import_node_crypto13.randomUUID)()).setExpirationTime(`${ACCESS_TTL_SECONDS}s`).sign(privateKey);
35878
36108
  }
35879
36109
  function tokenErrorResponse(c, error, description, status) {
35880
36110
  return c.json({ error, error_description: description }, status);
35881
36111
  }
35882
- var import_hono19, import_cookie, import_node_crypto12, import_jose2, ISSUER, RESOURCE, SCRAPER_RESOURCE, MEMORY_SCOPES, SCRAPER_SCOPES, SUPPORTED_SCOPES, ACCESS_TTL_SECONDS, REFRESH_TTL_SECONDS, CODE_TTL_SECONDS, ROTATION_GRACE_SECONDS, secureCookies, sessionCookieOptions, cachedKeys, oauthApp;
36112
+ var import_hono19, import_cookie, import_node_crypto13, import_jose2, ISSUER, RESOURCE, SCRAPER_RESOURCE, MEMORY_SCOPES, SCRAPER_SCOPES, SUPPORTED_SCOPES, ACCESS_TTL_SECONDS, REFRESH_TTL_SECONDS, CODE_TTL_SECONDS, ROTATION_GRACE_SECONDS, secureCookies, sessionCookieOptions, cachedKeys, oauthApp;
35883
36113
  var init_oauth_routes = __esm({
35884
36114
  "src/api/oauth-routes.ts"() {
35885
36115
  "use strict";
35886
36116
  import_hono19 = require("hono");
35887
36117
  import_cookie = require("hono/cookie");
35888
- import_node_crypto12 = require("crypto");
36118
+ import_node_crypto13 = require("crypto");
35889
36119
  import_jose2 = require("jose");
35890
36120
  init_session();
35891
36121
  init_db();
@@ -35971,7 +36201,7 @@ var init_oauth_routes = __esm({
35971
36201
  }
35972
36202
  }
35973
36203
  const clientName = typeof body.client_name === "string" ? body.client_name : null;
35974
- const clientId = `client_${(0, import_node_crypto12.randomBytes)(16).toString("hex")}`;
36204
+ const clientId = `client_${(0, import_node_crypto13.randomBytes)(16).toString("hex")}`;
35975
36205
  await registerClient(clientId, redirectUris, clientName);
35976
36206
  console.log("[oauth-dcr] register OK client_id=%s redirect_uris=%s", clientId, JSON.stringify(redirectUris));
35977
36207
  return c.json({
@@ -36024,7 +36254,7 @@ var init_oauth_routes = __esm({
36024
36254
  if (action === "deny") return redirectWithError(p.redirect_uri, p.state, "access_denied");
36025
36255
  if (action !== "approve") return c.text("unsupported action", 400);
36026
36256
  const scope = negotiateScope(p.scope, user, p.resource);
36027
- const code = `code_${(0, import_node_crypto12.randomBytes)(32).toString("base64url")}`;
36257
+ const code = `code_${(0, import_node_crypto13.randomBytes)(32).toString("base64url")}`;
36028
36258
  const expiresAt = new Date(Date.now() + CODE_TTL_SECONDS * 1e3).toISOString();
36029
36259
  await putCode({
36030
36260
  code,
@@ -36063,7 +36293,7 @@ var init_oauth_routes = __esm({
36063
36293
  const plan = user ? resolvePlan(user) : "free";
36064
36294
  const audience = record.resource ?? RESOURCE();
36065
36295
  const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
36066
- const refreshToken = `rt_${(0, import_node_crypto12.randomBytes)(40).toString("base64url")}`;
36296
+ const refreshToken = `rt_${(0, import_node_crypto13.randomBytes)(40).toString("base64url")}`;
36067
36297
  await putRefresh({
36068
36298
  refresh_token: refreshToken,
36069
36299
  client_id: clientId,
@@ -36093,7 +36323,7 @@ var init_oauth_routes = __esm({
36093
36323
  const plan = user ? resolvePlan(user) : "free";
36094
36324
  const audience = record.resource ?? RESOURCE();
36095
36325
  const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
36096
- const nextRefresh = `rt_${(0, import_node_crypto12.randomBytes)(40).toString("base64url")}`;
36326
+ const nextRefresh = `rt_${(0, import_node_crypto13.randomBytes)(40).toString("base64url")}`;
36097
36327
  await rotateRefresh(refreshToken, {
36098
36328
  refresh_token: nextRefresh,
36099
36329
  client_id: record.client_id,