mcp-scraper 0.3.42 → 0.3.45

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 (39) hide show
  1. package/dist/bin/api-server.cjs +1558 -1312
  2. package/dist/bin/api-server.cjs.map +1 -1
  3. package/dist/bin/api-server.js +3 -3
  4. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  5. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  6. package/dist/bin/mcp-scraper-cli.js +1 -1
  7. package/dist/bin/mcp-scraper-install.cjs +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-install.js +1 -1
  10. package/dist/bin/mcp-stdio-server.cjs +283 -224
  11. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-stdio-server.js +2 -2
  13. package/dist/bin/paa-harvest.cjs.map +1 -1
  14. package/dist/bin/paa-harvest.js +2 -2
  15. package/dist/{chunk-RXJHXBGG.js → chunk-BRVX6RDN.js} +21 -4
  16. package/dist/chunk-BRVX6RDN.js.map +1 -0
  17. package/dist/{chunk-KPF64WST.js → chunk-E7BZFMWB.js} +284 -225
  18. package/dist/chunk-E7BZFMWB.js.map +1 -0
  19. package/dist/{chunk-DVRPXPYH.js → chunk-FUVQR6GK.js} +14 -3
  20. package/dist/chunk-FUVQR6GK.js.map +1 -0
  21. package/dist/chunk-NVTFLLEG.js +7 -0
  22. package/dist/chunk-NVTFLLEG.js.map +1 -0
  23. package/dist/{chunk-GMTS35L6.js → chunk-RXNHY3TF.js} +2 -2
  24. package/dist/{db-LYQENFPW.js → db-H3S3M6KK.js} +2 -2
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.js +2 -2
  27. package/dist/{server-CZT7EULY.js → server-MKUN6NQZ.js} +513 -370
  28. package/dist/server-MKUN6NQZ.js.map +1 -0
  29. package/dist/{worker-FNSBPP7Y.js → worker-4CVMSUZR.js} +4 -4
  30. package/package.json +4 -2
  31. package/dist/chunk-DVRPXPYH.js.map +0 -1
  32. package/dist/chunk-KPF64WST.js.map +0 -1
  33. package/dist/chunk-OUZZNW3I.js +0 -7
  34. package/dist/chunk-OUZZNW3I.js.map +0 -1
  35. package/dist/chunk-RXJHXBGG.js.map +0 -1
  36. package/dist/server-CZT7EULY.js.map +0 -1
  37. /package/dist/{chunk-GMTS35L6.js.map → chunk-RXNHY3TF.js.map} +0 -0
  38. /package/dist/{db-LYQENFPW.js.map → db-H3S3M6KK.js.map} +0 -0
  39. /package/dist/{worker-FNSBPP7Y.js.map → worker-4CVMSUZR.js.map} +0 -0
@@ -16,7 +16,7 @@ import {
16
16
  renderLinkReport,
17
17
  sanitizeAttempts,
18
18
  sanitizeHarvestResult
19
- } from "./chunk-KPF64WST.js";
19
+ } from "./chunk-E7BZFMWB.js";
20
20
  import {
21
21
  csvRecords,
22
22
  listWorkflowDefinitions,
@@ -29,7 +29,7 @@ import {
29
29
  workflowStepCount,
30
30
  workflowSupportsSteps
31
31
  } from "./chunk-NNEIXK5L.js";
32
- import "./chunk-OUZZNW3I.js";
32
+ import "./chunk-NVTFLLEG.js";
33
33
  import {
34
34
  BROWSER_OPEN_MIN_BALANCE_MC,
35
35
  CONCURRENCY_PRICE_ID,
@@ -38,6 +38,8 @@ import {
38
38
  LedgerOperation,
39
39
  MC_COSTS,
40
40
  MC_PER_CREDIT,
41
+ MC_PER_USD,
42
+ MEMORY_AI_MARGIN_MULTIPLE,
41
43
  MEMORY_PLANS,
42
44
  MEMORY_PLAN_QUOTA,
43
45
  SCHEDULING_PLANS,
@@ -50,7 +52,7 @@ import {
50
52
  harvestProblemResponse,
51
53
  insufficientBalanceResponse,
52
54
  serializeHarvestProblem
53
- } from "./chunk-RXJHXBGG.js";
55
+ } from "./chunk-BRVX6RDN.js";
54
56
  import {
55
57
  BrowserDriver,
56
58
  MapsSelectors,
@@ -63,7 +65,7 @@ import {
63
65
  recordVendorUsage,
64
66
  resolveKernelProxyId,
65
67
  runWithCostContext
66
- } from "./chunk-GMTS35L6.js";
68
+ } from "./chunk-RXNHY3TF.js";
67
69
  import {
68
70
  CaptchaError,
69
71
  RECAPTCHA_INSTRUCTIONS,
@@ -159,7 +161,7 @@ import {
159
161
  setSubscriptionTier,
160
162
  setUserMemoryCreds,
161
163
  verifyPassword
162
- } from "./chunk-DVRPXPYH.js";
164
+ } from "./chunk-FUVQR6GK.js";
163
165
 
164
166
  // src/blog/registry.ts
165
167
  var posts = [
@@ -5257,7 +5259,7 @@ async function extractSite(opts) {
5257
5259
  }
5258
5260
 
5259
5261
  // src/api/server.ts
5260
- import { Hono as Hono18 } from "hono";
5262
+ import { Hono as Hono20 } from "hono";
5261
5263
  import { serve as serveInngest } from "inngest/hono";
5262
5264
 
5263
5265
  // src/inngest/client.ts
@@ -8287,8 +8289,28 @@ siteAuditApp.get("/jobs", async (c) => {
8287
8289
  return c.json(jobs);
8288
8290
  });
8289
8291
 
8290
- // src/api/youtube-routes.ts
8292
+ // src/api/internal-memory-routes.ts
8291
8293
  import { Hono as Hono2 } from "hono";
8294
+ var internalMemoryApp = new Hono2();
8295
+ internalMemoryApp.post("/ai-debit", async (c) => {
8296
+ const secret2 = c.req.header("x-internal-secret");
8297
+ if (!secret2 || secret2 !== process.env.INTERNAL_MEMORY_DEBIT_SECRET) {
8298
+ return c.json({ error: "unauthorized" }, 401);
8299
+ }
8300
+ const body = await c.req.json().catch(() => ({}));
8301
+ if (!body.identity || typeof body.costUsd !== "number" || body.costUsd < 0) {
8302
+ return c.json({ error: "invalid_request" }, 400);
8303
+ }
8304
+ const user = await getUserByEmail(body.identity.trim().toLowerCase());
8305
+ if (!user) return c.json({ ok: false, reason: "no_user" }, 200);
8306
+ const mc = Math.round(body.costUsd * MEMORY_AI_MARGIN_MULTIPLE * MC_PER_USD);
8307
+ if (mc <= 0) return c.json({ ok: true, mc: 0, balance_mc: null }, 200);
8308
+ const { ok, balance_mc } = await debitMc(user.id, mc, LedgerOperation.MEMORY_AI, `${body.source ?? "ai"}:${body.op ?? ""}`);
8309
+ return c.json({ ok, mc, balance_mc, insufficient: !ok }, 200);
8310
+ });
8311
+
8312
+ // src/api/youtube-routes.ts
8313
+ import { Hono as Hono3 } from "hono";
8292
8314
 
8293
8315
  // src/youtube/youtube-harvest.ts
8294
8316
  import { promises as fs } from "fs";
@@ -9280,7 +9302,7 @@ async function extendConcurrencyGate(lockId, ttlSeconds = DEFAULT_LOCK_TTL_SECON
9280
9302
  }
9281
9303
 
9282
9304
  // src/api/youtube-routes.ts
9283
- var youtubeApp = new Hono2();
9305
+ var youtubeApp = new Hono3();
9284
9306
  youtubeApp.post("/harvest", createApiKeyAuth(), async (c) => {
9285
9307
  const raw = await c.req.json().catch(() => ({}));
9286
9308
  const parsed = YoutubeHarvestBodySchema.safeParse(raw);
@@ -9439,14 +9461,14 @@ function fmtTs(secs) {
9439
9461
  }
9440
9462
 
9441
9463
  // src/api/screenshot-routes.ts
9442
- import { Hono as Hono3 } from "hono";
9464
+ import { Hono as Hono4 } from "hono";
9443
9465
  import { z as z12 } from "zod";
9444
9466
  var ScreenshotBodySchema = z12.object({
9445
9467
  url: z12.string().trim().min(1, "url is required"),
9446
9468
  device: z12.string().trim().optional(),
9447
9469
  allowLocal: z12.boolean().optional()
9448
9470
  });
9449
- var screenshotApp = new Hono3();
9471
+ var screenshotApp = new Hono4();
9450
9472
  screenshotApp.use("*", createApiKeyAuth());
9451
9473
  screenshotApp.post("/", async (c) => {
9452
9474
  const raw = await c.req.json().catch(() => ({}));
@@ -9503,7 +9525,7 @@ screenshotApp.post("/", async (c) => {
9503
9525
  });
9504
9526
 
9505
9527
  // src/api/facebook-ad-routes.ts
9506
- import { Hono as Hono4 } from "hono";
9528
+ import { Hono as Hono5 } from "hono";
9507
9529
  import { z as z13 } from "zod";
9508
9530
 
9509
9531
  // src/extractor/FacebookAdExtractor.ts
@@ -10502,7 +10524,7 @@ async function kernelLaunchOptsResidential() {
10502
10524
  }
10503
10525
  return { headless: true, kernelApiKey: browserServiceApiKey(), kernelProxyId: proxyId, viewport: { width: 1280, height: 900 }, locale: "en-US" };
10504
10526
  }
10505
- var facebookAdApp = new Hono4();
10527
+ var facebookAdApp = new Hono5();
10506
10528
  facebookAdApp.post("/ad", createApiKeyAuth(), async (c) => {
10507
10529
  const raw = await c.req.json().catch(() => ({}));
10508
10530
  const parsed = FacebookAdBodySchema.safeParse(raw);
@@ -10835,7 +10857,7 @@ facebookAdApp.post("/media", createApiKeyAuth(), async (c) => {
10835
10857
  });
10836
10858
 
10837
10859
  // src/api/google-ads-routes.ts
10838
- import { Hono as Hono5 } from "hono";
10860
+ import { Hono as Hono6 } from "hono";
10839
10861
  import { z as z14 } from "zod";
10840
10862
 
10841
10863
  // src/extractor/GoogleAdsExtractor.ts
@@ -11044,7 +11066,7 @@ async function kernelLaunchOptsResidential2() {
11044
11066
  function isBlockedMessage(msg) {
11045
11067
  return msg.toLowerCase().includes("blocked") || msg.toLowerCase().includes("captcha");
11046
11068
  }
11047
- var googleAdsApp = new Hono5();
11069
+ var googleAdsApp = new Hono6();
11048
11070
  googleAdsApp.post("/search", createApiKeyAuth(), async (c) => {
11049
11071
  const raw = await c.req.json().catch(() => ({}));
11050
11072
  const parsed = GoogleAdsSearchBodySchema.safeParse(raw);
@@ -11156,7 +11178,7 @@ googleAdsApp.post("/transcribe", createApiKeyAuth(), async (c) => {
11156
11178
  });
11157
11179
 
11158
11180
  // src/api/instagram-routes.ts
11159
- import { Hono as Hono6 } from "hono";
11181
+ import { Hono as Hono7 } from "hono";
11160
11182
  import { z as z15 } from "zod";
11161
11183
  import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync3, statSync, writeFileSync as writeFileSync2 } from "fs";
11162
11184
  import { homedir as homedir3 } from "os";
@@ -11546,7 +11568,7 @@ var InstagramMediaDownloadBodySchema = z15.object({
11546
11568
  includeTranscript: z15.boolean().default(false),
11547
11569
  mux: z15.boolean().default(true)
11548
11570
  });
11549
- var instagramApp = new Hono6();
11571
+ var instagramApp = new Hono7();
11550
11572
  function invalidRequest3(message) {
11551
11573
  return { error_code: "invalid_request", message };
11552
11574
  }
@@ -11820,7 +11842,7 @@ instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
11820
11842
  });
11821
11843
 
11822
11844
  // src/api/reddit-routes.ts
11823
- import { Hono as Hono7 } from "hono";
11845
+ import { Hono as Hono8 } from "hono";
11824
11846
  import { z as z16 } from "zod";
11825
11847
  var RedditThreadBodySchema = z16.object({
11826
11848
  url: z16.string().trim().min(1, "url is required"),
@@ -11880,7 +11902,7 @@ var PARSE_REDDIT = `(() => {
11880
11902
  });
11881
11903
  return { title: title, author: author, score: score, postBody: postBody, blocked: blocked, numComments: comments.length, comments: comments };
11882
11904
  })()`;
11883
- var redditApp = new Hono7();
11905
+ var redditApp = new Hono8();
11884
11906
  redditApp.post("/thread", createApiKeyAuth(), async (c) => {
11885
11907
  const raw = await c.req.json().catch(() => ({}));
11886
11908
  const parsed = RedditThreadBodySchema.safeParse(raw);
@@ -11914,11 +11936,11 @@ redditApp.post("/thread", createApiKeyAuth(), async (c) => {
11914
11936
  locale: "en-US"
11915
11937
  });
11916
11938
  const page = driver.getPage();
11917
- await page.goto(oldUrl, { waitUntil: "domcontentloaded", timeout: 45e3 });
11939
+ await page.goto(oldUrl, { waitUntil: "domcontentloaded", timeout: attempt === 0 ? 3e4 : 45e3 });
11918
11940
  await page.waitForTimeout(2500);
11919
11941
  const data = await page.evaluate(PARSE_REDDIT);
11920
11942
  if (!data.blocked && (data.postBody || data.comments.length > 0)) {
11921
- result = { ...data, sourceUrl: body.url, oldRedditUrl: oldUrl, attempts: attempt + 1 };
11943
+ result = { ...data, sourceUrl: body.url, oldRedditUrl: oldUrl, attempts: attempt + 1, proxied: Boolean(proxyId) };
11922
11944
  }
11923
11945
  } catch {
11924
11946
  } finally {
@@ -11947,8 +11969,231 @@ redditApp.post("/thread", createApiKeyAuth(), async (c) => {
11947
11969
  }
11948
11970
  });
11949
11971
 
11972
+ // src/api/video-routes.ts
11973
+ import { Hono as Hono9 } from "hono";
11974
+ import { z as z17 } from "zod";
11975
+
11976
+ // src/api/memory.ts
11977
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
11978
+
11979
+ // src/api/session.ts
11980
+ import { createHmac, timingSafeEqual } from "crypto";
11981
+ var isProduction = () => process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
11982
+ function getSessionSecret() {
11983
+ const configured = process.env.SESSION_SECRET?.trim();
11984
+ if (configured) return configured;
11985
+ if (isProduction()) throw new Error("SESSION_SECRET is not set \u2014 add it to your Vercel env vars (see src/api/env.ts)");
11986
+ return "dev-secret-change-me";
11987
+ }
11988
+ var secret = () => getSessionSecret();
11989
+ function safeEqualHex(a, b) {
11990
+ if (a.length !== b.length) return false;
11991
+ try {
11992
+ return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
11993
+ } catch {
11994
+ return false;
11995
+ }
11996
+ }
11997
+ function signSession(userId) {
11998
+ const payload = String(userId);
11999
+ const sig = createHmac("sha256", secret()).update(payload).digest("hex");
12000
+ return `${payload}.${sig}`;
12001
+ }
12002
+ function verifySession(token) {
12003
+ const dot = token.lastIndexOf(".");
12004
+ if (dot === -1) return null;
12005
+ const payload = token.slice(0, dot);
12006
+ const sig = token.slice(dot + 1);
12007
+ const expected = createHmac("sha256", secret()).update(payload).digest("hex");
12008
+ if (!safeEqualHex(sig, expected)) return null;
12009
+ const id = parseInt(payload);
12010
+ return isNaN(id) ? null : id;
12011
+ }
12012
+
12013
+ // src/api/memory.ts
12014
+ var MEMORY_BASE_URL = () => (process.env.MCP_MEMORY_BASE_URL ?? "https://mcp-memory-omega.vercel.app").replace(/\/$/, "");
12015
+ var ADMIN_KEY = () => process.env.MCP_MEMORY_ADMIN_KEY?.trim() ?? "";
12016
+ function isMemoryOperator(email) {
12017
+ if (!email) return false;
12018
+ const ops = (process.env.MEMORY_OPERATOR_IDENTITIES ?? "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
12019
+ return ops.includes(email.trim().toLowerCase());
12020
+ }
12021
+ function encKey() {
12022
+ return scryptSync(getSessionSecret(), "mcp-memory-key-v1", 32);
12023
+ }
12024
+ function encryptMemoryKey(secret2) {
12025
+ const iv = randomBytes(12);
12026
+ const cipher = createCipheriv("aes-256-gcm", encKey(), iv);
12027
+ const enc = Buffer.concat([cipher.update(secret2, "utf8"), cipher.final()]);
12028
+ const tag = cipher.getAuthTag();
12029
+ return `${iv.toString("base64")}:${tag.toString("base64")}:${enc.toString("base64")}`;
12030
+ }
12031
+ function decryptMemoryKey(stored) {
12032
+ try {
12033
+ const [ivB, tagB, dataB] = stored.split(":");
12034
+ const decipher = createDecipheriv("aes-256-gcm", encKey(), Buffer.from(ivB, "base64"));
12035
+ decipher.setAuthTag(Buffer.from(tagB, "base64"));
12036
+ return Buffer.concat([decipher.update(Buffer.from(dataB, "base64")), decipher.final()]).toString("utf8");
12037
+ } catch {
12038
+ return null;
12039
+ }
12040
+ }
12041
+ async function memoryCall(toolName, args, userMemoryKey) {
12042
+ try {
12043
+ const res = await fetch(`${MEMORY_BASE_URL()}/api/mcp/memoryServer/tools/${toolName}/execute`, {
12044
+ method: "POST",
12045
+ headers: { "content-type": "application/json" },
12046
+ body: JSON.stringify({ data: { ...args, apiKey: userMemoryKey } })
12047
+ });
12048
+ const body = await res.json().catch(() => null);
12049
+ if (!res.ok) return { ok: false, error: body?.error ?? `memory ${toolName} failed (${res.status})` };
12050
+ if (body?.result) return body.result;
12051
+ return { ok: false, error: body?.error ?? `memory ${toolName} returned no result` };
12052
+ } catch (err) {
12053
+ return { ok: false, error: err?.message ?? "memory call failed" };
12054
+ }
12055
+ }
12056
+ function personalVault(user) {
12057
+ return `mcp-${user.id}`;
12058
+ }
12059
+ function memoryIdentity(user) {
12060
+ return user.email;
12061
+ }
12062
+ function resolveMemoryIdentity(user) {
12063
+ return memoryIdentity(user);
12064
+ }
12065
+ function memoryPlanForReads(user) {
12066
+ if (isMemoryOperator(user.email)) return "unlimited";
12067
+ if (user.memory_plan === "pro" || user.memory_plan === "team") return user.memory_plan;
12068
+ return "free";
12069
+ }
12070
+ async function provisionMemoryForUser(user) {
12071
+ try {
12072
+ const { key } = await getOrCreateUserMemoryKey(user);
12073
+ if (key) await setMemoryProvisioned(user.id, true);
12074
+ } catch {
12075
+ }
12076
+ }
12077
+ async function getOrCreateUserMemoryKey(user) {
12078
+ const creds = await getUserMemoryCreds(user.id);
12079
+ if (creds.memory_key) {
12080
+ const decrypted = decryptMemoryKey(creds.memory_key);
12081
+ if (decrypted) return { key: decrypted };
12082
+ }
12083
+ const admin = ADMIN_KEY();
12084
+ if (!admin) return { key: null, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
12085
+ const identity = memoryIdentity(user);
12086
+ const plan = isMemoryOperator(user.email) ? "unlimited" : (await getMemoryPlan(user.id)).plan;
12087
+ const provisioned = await memoryCall(
12088
+ "provisionDefaultsTool",
12089
+ { granteeIdentity: identity, issueKey: true, plan },
12090
+ admin
12091
+ );
12092
+ if (!provisioned.ok || !provisioned.secret) {
12093
+ return { key: null, error: provisioned.error ?? "failed to provision default vaults" };
12094
+ }
12095
+ await setUserMemoryCreds(user.id, encryptMemoryKey(provisioned.secret), identity);
12096
+ return { key: provisioned.secret };
12097
+ }
12098
+ async function syncMemoryKeyPlan(user, plan) {
12099
+ const effectivePlan = isMemoryOperator(user.email) ? "unlimited" : plan;
12100
+ const { key, error } = await getOrCreateUserMemoryKey(user);
12101
+ if (!key) return { ok: false, error: error ?? "memory unavailable" };
12102
+ const listed = await memoryCall(
12103
+ "listKeysTool",
12104
+ {},
12105
+ key
12106
+ );
12107
+ if (!listed.ok || !listed.keys?.length) return { ok: false, error: listed.error ?? "no memory keys found" };
12108
+ const target = listed.keys[0];
12109
+ if (target.plan === effectivePlan) return { ok: true };
12110
+ const res = await memoryCall("setScopeTool", { keyId: target.keyId, plan: effectivePlan }, key);
12111
+ return { ok: res.ok, error: res.error };
12112
+ }
12113
+ async function syncScheduleEntitlement(user, enabled, quotaPerPeriod) {
12114
+ const admin = ADMIN_KEY();
12115
+ if (!admin) return { ok: false, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
12116
+ const identity = resolveMemoryIdentity(user);
12117
+ const res = await memoryCall("setScheduleEntitlementTool", {
12118
+ granteeIdentity: identity,
12119
+ enabled,
12120
+ quotaPerPeriod,
12121
+ mcpScraperApiKey: user.api_key
12122
+ }, admin);
12123
+ return { ok: res.ok, error: res.error };
12124
+ }
12125
+
12126
+ // src/api/video-routes.ts
12127
+ var videoApp = new Hono9();
12128
+ var AnalyzeBodySchema = z17.object({
12129
+ sourceUrl: z17.string().trim().url("sourceUrl must be a direct video file URL"),
12130
+ intervalS: z17.number().min(1).max(30).optional(),
12131
+ maxFrames: z17.number().int().min(1).max(120).optional(),
12132
+ detail: z17.enum(["fast", "standard", "deep"]).optional(),
12133
+ vault: z17.string().trim().min(1).optional()
12134
+ });
12135
+ function invalidRequest5(message) {
12136
+ return { error_code: "invalid_request", message };
12137
+ }
12138
+ videoApp.post("/analyze", createApiKeyAuth(), async (c) => {
12139
+ const parsed = AnalyzeBodySchema.safeParse(await c.req.json().catch(() => ({})));
12140
+ if (!parsed.success) return c.json(invalidRequest5(parsed.error.issues[0]?.message ?? "invalid request"), 400);
12141
+ const body = parsed.data;
12142
+ const user = c.get("user");
12143
+ const { key: memKey, error: memErr } = await getOrCreateUserMemoryKey(user);
12144
+ if (!memKey) return c.json({ error_code: "memory_unavailable", message: memErr ?? "memory unavailable" }, 502);
12145
+ const { ok, balance_mc } = await debitMc(user.id, MC_COSTS.video_analysis, LedgerOperation.VIDEO_ANALYSIS, body.sourceUrl);
12146
+ if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.video_analysis), 402);
12147
+ const started = await memoryCall(
12148
+ "videoAnalyzeStartTool",
12149
+ {
12150
+ sourceUrl: body.sourceUrl,
12151
+ intervalS: body.intervalS ?? 2,
12152
+ maxFrames: body.maxFrames ?? 120,
12153
+ detail: body.detail ?? "standard",
12154
+ vault: body.vault ?? "Library"
12155
+ },
12156
+ memKey
12157
+ );
12158
+ if (!started.ok || !started.runId) {
12159
+ await creditMc(user.id, MC_COSTS.video_analysis, LedgerOperation.VIDEO_ANALYSIS_REFUND, `start-failed:${body.sourceUrl}`);
12160
+ return c.json({ error_code: "video_start_failed", message: started.error ?? "could not start analysis" }, 502);
12161
+ }
12162
+ return c.json({
12163
+ ok: true,
12164
+ runId: started.runId,
12165
+ status: "queued",
12166
+ message: 'Analysis started. Poll GET /video/status?runId=... every few seconds until status is "done".'
12167
+ });
12168
+ });
12169
+ videoApp.post("/status", createApiKeyAuth(), async (c) => {
12170
+ const rawBody = await c.req.json().catch(() => ({}));
12171
+ const runId = rawBody.runId?.trim();
12172
+ if (!runId) return c.json(invalidRequest5("runId is required"), 400);
12173
+ const user = c.get("user");
12174
+ const { key: memKey, error: memErr } = await getOrCreateUserMemoryKey(user);
12175
+ if (!memKey) return c.json({ error_code: "memory_unavailable", message: memErr ?? "memory unavailable" }, 502);
12176
+ const st = await memoryCall("videoAnalyzeStatusTool", { runId }, memKey);
12177
+ if (!st.ok) return c.json({ error_code: "not_found", message: st.error ?? "run not found" }, 404);
12178
+ if (st.status === "failed") {
12179
+ const refundOp = `${LedgerOperation.VIDEO_ANALYSIS_REFUND}:${runId}`;
12180
+ if (!await ledgerExistsForOperation(user.id, refundOp)) {
12181
+ await creditMc(user.id, MC_COSTS.video_analysis, refundOp, `run-failed:${runId}`);
12182
+ }
12183
+ }
12184
+ return c.json({
12185
+ ok: true,
12186
+ runId,
12187
+ status: st.status ?? "unknown",
12188
+ progress: st.progress ?? null,
12189
+ frameCount: st.frameCount ?? null,
12190
+ ...st.status === "done" ? { artifactPath: st.artifactPath ?? null, report: st.report ?? null } : {},
12191
+ ...st.error ? { error: st.error } : {}
12192
+ });
12193
+ });
12194
+
11950
12195
  // src/api/maps-routes.ts
11951
- import { Hono as Hono8 } from "hono";
12196
+ import { Hono as Hono10 } from "hono";
11952
12197
 
11953
12198
  // src/extractor/MapsNavigator.ts
11954
12199
  var MapsNavigator = class {
@@ -12123,7 +12368,7 @@ var MapsReviewCollector = class {
12123
12368
  };
12124
12369
 
12125
12370
  // src/extractor/MapsExtractor.ts
12126
- import { z as z17 } from "zod";
12371
+ import { z as z18 } from "zod";
12127
12372
  var MapsExtractor = class {
12128
12373
  constructor(driver) {
12129
12374
  this.driver = driver;
@@ -12309,7 +12554,7 @@ var MapsExtractor = class {
12309
12554
  });
12310
12555
  return rows;
12311
12556
  }, { hoursTable: MapsSelectors.hoursTable, hoursTableAlt: MapsSelectors.hoursTableAlt });
12312
- const result = z17.array(RawMapsHoursRowSchema).safeParse(raw);
12557
+ const result = z18.array(RawMapsHoursRowSchema).safeParse(raw);
12313
12558
  if (!result.success) {
12314
12559
  console.warn("[MapsExtractor] hours parse failed", result.error.flatten());
12315
12560
  return [];
@@ -12373,7 +12618,7 @@ var MapsExtractor = class {
12373
12618
  });
12374
12619
  return results;
12375
12620
  });
12376
- const result = z17.array(RawMapsAboutAttributeSchema).safeParse(raw);
12621
+ const result = z18.array(RawMapsAboutAttributeSchema).safeParse(raw);
12377
12622
  if (!result.success) {
12378
12623
  console.warn("[MapsExtractor] about parse failed", result.error.flatten());
12379
12624
  return [];
@@ -12749,7 +12994,7 @@ function mapsErrorResponse(c, err, errorCode) {
12749
12994
  attempts: rotationError?.attempts ?? void 0
12750
12995
  }, retryable ? 503 : 500);
12751
12996
  }
12752
- var mapsApp = new Hono8();
12997
+ var mapsApp = new Hono10();
12753
12998
  mapsApp.post("/search", createApiKeyAuth(), async (c) => {
12754
12999
  const user = c.get("user");
12755
13000
  const body = await c.req.json().catch(() => ({}));
@@ -12887,12 +13132,12 @@ mapsApp.post("/place", createApiKeyAuth(), async (c) => {
12887
13132
  });
12888
13133
 
12889
13134
  // src/api/directory-routes.ts
12890
- import { Hono as Hono9 } from "hono";
13135
+ import { Hono as Hono11 } from "hono";
12891
13136
 
12892
13137
  // src/directory/directory-workflow.ts
12893
13138
  import { mkdir as mkdir2, writeFile } from "fs/promises";
12894
13139
  import { join as join6 } from "path";
12895
- import { z as z18 } from "zod";
13140
+ import { z as z19 } from "zod";
12896
13141
 
12897
13142
  // src/directory/location-db.ts
12898
13143
  import { access, readFile } from "fs/promises";
@@ -13053,24 +13298,24 @@ async function resolveDirectoryMarkets(options) {
13053
13298
  }
13054
13299
 
13055
13300
  // src/directory/directory-workflow.ts
13056
- var DirectoryWorkflowOptionsSchema = z18.object({
13057
- query: z18.string().min(1),
13058
- state: z18.string().min(2).default("TN"),
13059
- minPopulation: z18.number().int().min(0).default(1e5),
13060
- populationYear: z18.union(POPULATION_YEARS.map((year) => z18.literal(year))).default(2025),
13061
- maxCities: z18.number().int().min(1).max(100).default(25),
13062
- maxResultsPerCity: z18.number().int().min(1).max(50).default(50),
13063
- concurrency: z18.number().int().min(1).max(5).default(5),
13064
- includeZipGroups: z18.boolean().default(true),
13065
- usZipsCsvPath: z18.string().optional(),
13066
- saveCsv: z18.boolean().default(true),
13067
- gl: z18.string().length(2).default("us"),
13068
- hl: z18.string().length(2).default("en"),
13069
- proxyMode: z18.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
13070
- proxyZip: z18.string().regex(/^\d{5}$/).optional(),
13071
- debug: z18.boolean().default(false),
13072
- headless: z18.boolean().default(true),
13073
- kernelApiKey: z18.string().optional()
13301
+ var DirectoryWorkflowOptionsSchema = z19.object({
13302
+ query: z19.string().min(1),
13303
+ state: z19.string().min(2).default("TN"),
13304
+ minPopulation: z19.number().int().min(0).default(1e5),
13305
+ populationYear: z19.union(POPULATION_YEARS.map((year) => z19.literal(year))).default(2025),
13306
+ maxCities: z19.number().int().min(1).max(100).default(25),
13307
+ maxResultsPerCity: z19.number().int().min(1).max(50).default(50),
13308
+ concurrency: z19.number().int().min(1).max(5).default(5),
13309
+ includeZipGroups: z19.boolean().default(true),
13310
+ usZipsCsvPath: z19.string().optional(),
13311
+ saveCsv: z19.boolean().default(true),
13312
+ gl: z19.string().length(2).default("us"),
13313
+ hl: z19.string().length(2).default("en"),
13314
+ proxyMode: z19.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
13315
+ proxyZip: z19.string().regex(/^\d{5}$/).optional(),
13316
+ debug: z19.boolean().default(false),
13317
+ headless: z19.boolean().default(true),
13318
+ kernelApiKey: z19.string().optional()
13074
13319
  });
13075
13320
  function errorMessage(err) {
13076
13321
  return err instanceof Error ? err.message : String(err);
@@ -13291,7 +13536,7 @@ async function runDirectoryWorkflowFromPlan(options, plan) {
13291
13536
  }
13292
13537
 
13293
13538
  // src/api/directory-routes.ts
13294
- var directoryApp = new Hono9();
13539
+ var directoryApp = new Hono11();
13295
13540
  directoryApp.post("/run", createApiKeyAuth(), async (c) => {
13296
13541
  const user = c.get("user");
13297
13542
  const body = await c.req.json().catch(() => ({}));
@@ -13362,37 +13607,37 @@ directoryApp.post("/run", createApiKeyAuth(), async (c) => {
13362
13607
  });
13363
13608
 
13364
13609
  // src/api/workflow-routes.ts
13365
- import { createHmac } from "crypto";
13610
+ import { createHmac as createHmac2 } from "crypto";
13366
13611
  import { readFile as readFile2 } from "fs/promises";
13367
- import { Hono as Hono10 } from "hono";
13368
- import { z as z19 } from "zod";
13369
- var workflowApp = new Hono10();
13370
- var WorkflowInputSchema = z19.record(z19.unknown()).default({});
13371
- var WorkflowIdSchema = z19.string().min(1);
13372
- var CadenceSchema = z19.enum(["daily", "weekly", "monthly"]);
13373
- var ScheduleStatusSchema = z19.enum(["active", "paused"]);
13374
- var RunBodySchema = z19.object({
13612
+ import { Hono as Hono12 } from "hono";
13613
+ import { z as z20 } from "zod";
13614
+ var workflowApp = new Hono12();
13615
+ var WorkflowInputSchema = z20.record(z20.unknown()).default({});
13616
+ var WorkflowIdSchema = z20.string().min(1);
13617
+ var CadenceSchema = z20.enum(["daily", "weekly", "monthly"]);
13618
+ var ScheduleStatusSchema = z20.enum(["active", "paused"]);
13619
+ var RunBodySchema = z20.object({
13375
13620
  workflowId: WorkflowIdSchema,
13376
13621
  input: WorkflowInputSchema,
13377
- webhookUrl: z19.string().url().optional()
13622
+ webhookUrl: z20.string().url().optional()
13378
13623
  });
13379
- var ScheduleCreateSchema = z19.object({
13624
+ var ScheduleCreateSchema = z20.object({
13380
13625
  workflowId: WorkflowIdSchema,
13381
- name: z19.string().min(1).max(120).optional(),
13626
+ name: z20.string().min(1).max(120).optional(),
13382
13627
  input: WorkflowInputSchema,
13383
13628
  cadence: CadenceSchema.default("weekly"),
13384
- timezone: z19.string().min(1).max(64).default("UTC"),
13385
- webhookUrl: z19.string().url().optional(),
13386
- nextRunAt: z19.string().datetime().optional()
13629
+ timezone: z20.string().min(1).max(64).default("UTC"),
13630
+ webhookUrl: z20.string().url().optional(),
13631
+ nextRunAt: z20.string().datetime().optional()
13387
13632
  });
13388
- var SchedulePatchSchema = z19.object({
13389
- name: z19.string().min(1).max(120).optional(),
13633
+ var SchedulePatchSchema = z20.object({
13634
+ name: z20.string().min(1).max(120).optional(),
13390
13635
  status: ScheduleStatusSchema.optional(),
13391
13636
  input: WorkflowInputSchema.optional(),
13392
13637
  cadence: CadenceSchema.optional(),
13393
- timezone: z19.string().min(1).max(64).optional(),
13394
- webhookUrl: z19.string().url().nullable().optional(),
13395
- nextRunAt: z19.string().datetime().nullable().optional()
13638
+ timezone: z20.string().min(1).max(64).optional(),
13639
+ webhookUrl: z20.string().url().nullable().optional(),
13640
+ nextRunAt: z20.string().datetime().nullable().optional()
13396
13641
  });
13397
13642
  function hostedWorkflowOutputDir() {
13398
13643
  return workflowOutputBaseDir(process.env.MCP_SCRAPER_WORKFLOW_OUTPUT_DIR?.trim() || "/tmp/mcp-scraper-workflows");
@@ -13450,7 +13695,7 @@ async function readManifestFromSummary(summary) {
13450
13695
  function webhookSignature(body, timestamp) {
13451
13696
  const secret2 = process.env.MCP_SCRAPER_WEBHOOK_SECRET?.trim();
13452
13697
  if (!secret2) return null;
13453
- return createHmac("sha256", secret2).update(`${timestamp}.${body}`).digest("hex");
13698
+ return createHmac2("sha256", secret2).update(`${timestamp}.${body}`).digest("hex");
13454
13699
  }
13455
13700
  async function deliverWorkflowWebhook(input) {
13456
13701
  if (!input.webhookUrl) return;
@@ -13896,7 +14141,7 @@ workflowApp.post("/cron/dispatch", async (c) => {
13896
14141
  });
13897
14142
 
13898
14143
  // src/api/serp-intelligence-routes.ts
13899
- import { Hono as Hono11 } from "hono";
14144
+ import { Hono as Hono13 } from "hono";
13900
14145
 
13901
14146
  // src/serp-intelligence/page-snapshot-extractor.ts
13902
14147
  import { createHash as createHash2 } from "crypto";
@@ -14208,7 +14453,7 @@ async function capturePageSnapshots(targets, options = {}) {
14208
14453
  }
14209
14454
 
14210
14455
  // src/serp-intelligence/schemas.ts
14211
- import { z as z20 } from "zod";
14456
+ import { z as z21 } from "zod";
14212
14457
  var SerpIntelligenceDeviceValues = ["desktop", "mobile"];
14213
14458
  var SerpIntelligenceProxyModeValues = ["location", "configured", "none"];
14214
14459
  var SerpIntelligenceAttemptOutcomeValues = [
@@ -14270,171 +14515,171 @@ function isPublicHttpUrl(value) {
14270
14515
  return false;
14271
14516
  }
14272
14517
  }
14273
- var SerpIntelligencePublicHttpUrlSchema = z20.string().url().refine(isPublicHttpUrl, "url must be a public HTTP or HTTPS URL");
14274
- var SerpIntelligenceCaptureBodySchema = z20.object({
14275
- query: z20.string().trim().min(1, "query is required"),
14276
- location: z20.string().trim().min(1).optional(),
14277
- gl: z20.string().trim().length(2).default("us"),
14278
- hl: z20.string().trim().length(2).default("en"),
14279
- device: z20.enum(SerpIntelligenceDeviceValues).default("desktop"),
14280
- proxyMode: z20.enum(SerpIntelligenceProxyModeValues).default(DEFAULT_PROXY_MODE),
14281
- proxyZip: z20.string().regex(/^\d{5}$/).optional(),
14282
- pages: z20.number().int().min(1).max(2).default(1),
14283
- debug: z20.boolean().default(false),
14284
- includePageSnapshots: z20.boolean().default(false),
14285
- pageSnapshotLimit: z20.number().int().min(0).max(10).default(0)
14518
+ var SerpIntelligencePublicHttpUrlSchema = z21.string().url().refine(isPublicHttpUrl, "url must be a public HTTP or HTTPS URL");
14519
+ var SerpIntelligenceCaptureBodySchema = z21.object({
14520
+ query: z21.string().trim().min(1, "query is required"),
14521
+ location: z21.string().trim().min(1).optional(),
14522
+ gl: z21.string().trim().length(2).default("us"),
14523
+ hl: z21.string().trim().length(2).default("en"),
14524
+ device: z21.enum(SerpIntelligenceDeviceValues).default("desktop"),
14525
+ proxyMode: z21.enum(SerpIntelligenceProxyModeValues).default(DEFAULT_PROXY_MODE),
14526
+ proxyZip: z21.string().regex(/^\d{5}$/).optional(),
14527
+ pages: z21.number().int().min(1).max(2).default(1),
14528
+ debug: z21.boolean().default(false),
14529
+ includePageSnapshots: z21.boolean().default(false),
14530
+ pageSnapshotLimit: z21.number().int().min(0).max(10).default(0)
14286
14531
  }).strict();
14287
- var SerpIntelligencePageSnapshotRequestSchema = z20.object({
14532
+ var SerpIntelligencePageSnapshotRequestSchema = z21.object({
14288
14533
  url: SerpIntelligencePublicHttpUrlSchema,
14289
- sourceKind: z20.enum(SerpPageSnapshotSourceKindValues).default("configured_target"),
14290
- sourcePosition: z20.number().int().min(1).optional()
14534
+ sourceKind: z21.enum(SerpPageSnapshotSourceKindValues).default("configured_target"),
14535
+ sourcePosition: z21.number().int().min(1).optional()
14291
14536
  }).strict();
14292
- var SerpIntelligencePageSnapshotsBodySchema = z20.object({
14293
- urls: z20.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25),
14294
- targets: z20.array(SerpIntelligencePageSnapshotRequestSchema).min(1).max(25).optional(),
14295
- maxConcurrency: z20.number().int().min(1).max(5).default(2),
14296
- timeoutMs: z20.number().int().min(1e3).max(6e4).default(15e3),
14297
- debug: z20.boolean().default(false)
14537
+ var SerpIntelligencePageSnapshotsBodySchema = z21.object({
14538
+ urls: z21.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25),
14539
+ targets: z21.array(SerpIntelligencePageSnapshotRequestSchema).min(1).max(25).optional(),
14540
+ maxConcurrency: z21.number().int().min(1).max(5).default(2),
14541
+ timeoutMs: z21.number().int().min(1e3).max(6e4).default(15e3),
14542
+ debug: z21.boolean().default(false)
14298
14543
  }).strict();
14299
- var SerpIntelligenceAICitationSchema = z20.object({
14300
- text: z20.string(),
14301
- href: z20.string()
14544
+ var SerpIntelligenceAICitationSchema = z21.object({
14545
+ text: z21.string(),
14546
+ href: z21.string()
14302
14547
  }).strict();
14303
- var SerpIntelligenceOrganicResultSchema = z20.object({
14304
- position: z20.number().int().min(1),
14305
- title: z20.string(),
14306
- url: z20.string(),
14307
- domain: z20.string(),
14308
- cite: z20.string().nullable(),
14309
- snippet: z20.string().nullable(),
14310
- isRedditStyle: z20.boolean(),
14311
- inlineRating: z20.object({
14312
- value: z20.string(),
14313
- count: z20.string()
14548
+ var SerpIntelligenceOrganicResultSchema = z21.object({
14549
+ position: z21.number().int().min(1),
14550
+ title: z21.string(),
14551
+ url: z21.string(),
14552
+ domain: z21.string(),
14553
+ cite: z21.string().nullable(),
14554
+ snippet: z21.string().nullable(),
14555
+ isRedditStyle: z21.boolean(),
14556
+ inlineRating: z21.object({
14557
+ value: z21.string(),
14558
+ count: z21.string()
14314
14559
  }).strict().nullable()
14315
14560
  }).strict();
14316
- var SerpIntelligenceLocationEvidenceSchema = z20.object({
14317
- status: z20.enum(SerpIntelligenceLocalizationStatusValues),
14318
- expected: z20.object({
14319
- city: z20.string(),
14320
- regionCode: z20.string().nullable(),
14321
- canonicalLocation: z20.string()
14561
+ var SerpIntelligenceLocationEvidenceSchema = z21.object({
14562
+ status: z21.enum(SerpIntelligenceLocalizationStatusValues),
14563
+ expected: z21.object({
14564
+ city: z21.string(),
14565
+ regionCode: z21.string().nullable(),
14566
+ canonicalLocation: z21.string()
14322
14567
  }).strict().nullable(),
14323
- candidates: z20.array(z20.object({
14324
- city: z20.string(),
14325
- regionCode: z20.string(),
14326
- count: z20.number().int().min(0),
14327
- examples: z20.array(z20.string())
14568
+ candidates: z21.array(z21.object({
14569
+ city: z21.string(),
14570
+ regionCode: z21.string(),
14571
+ count: z21.number().int().min(0),
14572
+ examples: z21.array(z21.string())
14328
14573
  }).strict())
14329
14574
  }).strict();
14330
- var SerpIntelligenceHarvestResultSchema = z20.object({
14331
- seed: z20.string(),
14332
- location: z20.string().nullable(),
14333
- extractedAt: z20.string(),
14334
- totalQuestions: z20.number().int().min(0),
14335
- surface: z20.enum(["web", "aim", "unknown"]),
14336
- aiOverview: z20.object({
14337
- detected: z20.boolean(),
14338
- text: z20.string().nullable(),
14339
- citations: z20.array(SerpIntelligenceAICitationSchema),
14340
- expanded: z20.boolean().optional(),
14341
- fullyExpanded: z20.boolean().optional(),
14342
- sections: z20.array(z20.string()).optional()
14575
+ var SerpIntelligenceHarvestResultSchema = z21.object({
14576
+ seed: z21.string(),
14577
+ location: z21.string().nullable(),
14578
+ extractedAt: z21.string(),
14579
+ totalQuestions: z21.number().int().min(0),
14580
+ surface: z21.enum(["web", "aim", "unknown"]),
14581
+ aiOverview: z21.object({
14582
+ detected: z21.boolean(),
14583
+ text: z21.string().nullable(),
14584
+ citations: z21.array(SerpIntelligenceAICitationSchema),
14585
+ expanded: z21.boolean().optional(),
14586
+ fullyExpanded: z21.boolean().optional(),
14587
+ sections: z21.array(z21.string()).optional()
14343
14588
  }).strict(),
14344
- aiMode: z20.object({
14345
- detected: z20.boolean(),
14346
- text: z20.string().nullable(),
14347
- citations: z20.array(SerpIntelligenceAICitationSchema)
14589
+ aiMode: z21.object({
14590
+ detected: z21.boolean(),
14591
+ text: z21.string().nullable(),
14592
+ citations: z21.array(SerpIntelligenceAICitationSchema)
14348
14593
  }).strict(),
14349
- tree: z20.array(z20.unknown()),
14350
- flat: z20.array(z20.unknown()),
14351
- videos: z20.array(z20.unknown()),
14352
- forums: z20.array(z20.unknown()),
14353
- organicResults: z20.array(SerpIntelligenceOrganicResultSchema),
14354
- localPack: z20.array(z20.unknown()),
14355
- entityIds: z20.object({
14356
- entities: z20.array(z20.object({
14357
- name: z20.string(),
14358
- kgId: z20.string().nullable(),
14359
- cid: z20.string().nullable(),
14360
- gcid: z20.string().nullable()
14594
+ tree: z21.array(z21.unknown()),
14595
+ flat: z21.array(z21.unknown()),
14596
+ videos: z21.array(z21.unknown()),
14597
+ forums: z21.array(z21.unknown()),
14598
+ organicResults: z21.array(SerpIntelligenceOrganicResultSchema),
14599
+ localPack: z21.array(z21.unknown()),
14600
+ entityIds: z21.object({
14601
+ entities: z21.array(z21.object({
14602
+ name: z21.string(),
14603
+ kgId: z21.string().nullable(),
14604
+ cid: z21.string().nullable(),
14605
+ gcid: z21.string().nullable()
14361
14606
  }).strict()),
14362
- kgIds: z20.array(z20.string()),
14363
- cids: z20.array(z20.string()),
14364
- gcids: z20.array(z20.string())
14607
+ kgIds: z21.array(z21.string()),
14608
+ cids: z21.array(z21.string()),
14609
+ gcids: z21.array(z21.string())
14365
14610
  }).strict(),
14366
- stats: z20.object({
14367
- seed: z20.string(),
14368
- totalQuestions: z20.number().int().min(0),
14369
- maxDepthReached: z20.number().int().min(0),
14370
- durationMs: z20.number().min(0),
14371
- errorCount: z20.number().int().min(0)
14611
+ stats: z21.object({
14612
+ seed: z21.string(),
14613
+ totalQuestions: z21.number().int().min(0),
14614
+ maxDepthReached: z21.number().int().min(0),
14615
+ durationMs: z21.number().min(0),
14616
+ errorCount: z21.number().int().min(0)
14372
14617
  }).strict(),
14373
- diagnostics: z20.object({
14374
- completionStatus: z20.enum(["paa_found", "no_paa", "serp_only"]),
14375
- problem: z20.null(),
14376
- warnings: z20.array(z20.unknown()).optional(),
14377
- debug: z20.object({
14618
+ diagnostics: z21.object({
14619
+ completionStatus: z21.enum(["paa_found", "no_paa", "serp_only"]),
14620
+ problem: z21.null(),
14621
+ warnings: z21.array(z21.unknown()).optional(),
14622
+ debug: z21.object({
14378
14623
  locationEvidence: SerpIntelligenceLocationEvidenceSchema.optional()
14379
14624
  }).passthrough().optional()
14380
14625
  }).passthrough(),
14381
- whatPeopleSaying: z20.array(z20.unknown())
14626
+ whatPeopleSaying: z21.array(z21.unknown())
14382
14627
  }).strict();
14383
- var SerpIntelligenceCaptureAttemptSchema = z20.object({
14384
- attemptNumber: z20.number().int().min(1),
14385
- outcome: z20.enum(SerpIntelligenceAttemptOutcomeValues),
14386
- startedAt: z20.string().optional(),
14387
- completedAt: z20.string().optional(),
14388
- durationMs: z20.number().min(0).optional(),
14389
- problemCode: z20.string().optional(),
14390
- message: z20.string().optional(),
14391
- kernelSessionId: z20.string().nullable().optional(),
14392
- cleanupSucceeded: z20.boolean().nullable().optional()
14628
+ var SerpIntelligenceCaptureAttemptSchema = z21.object({
14629
+ attemptNumber: z21.number().int().min(1),
14630
+ outcome: z21.enum(SerpIntelligenceAttemptOutcomeValues),
14631
+ startedAt: z21.string().optional(),
14632
+ completedAt: z21.string().optional(),
14633
+ durationMs: z21.number().min(0).optional(),
14634
+ problemCode: z21.string().optional(),
14635
+ message: z21.string().optional(),
14636
+ kernelSessionId: z21.string().nullable().optional(),
14637
+ cleanupSucceeded: z21.boolean().nullable().optional()
14393
14638
  }).strict();
14394
- var SerpPageSnapshotCaptureSchema = z20.object({
14639
+ var SerpPageSnapshotCaptureSchema = z21.object({
14395
14640
  url: SerpIntelligencePublicHttpUrlSchema,
14396
14641
  requestedUrl: SerpIntelligencePublicHttpUrlSchema,
14397
14642
  finalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
14398
- sourceKind: z20.enum(SerpPageSnapshotSourceKindValues),
14399
- sourcePosition: z20.number().int().min(1).nullable(),
14400
- status: z20.enum(SerpPageFetchStatusValues),
14401
- fetchedVia: z20.enum(SerpPageFetchedViaValues).nullable(),
14402
- httpStatus: z20.number().int().min(100).max(599).nullable(),
14403
- contentType: z20.string().nullable(),
14404
- title: z20.string().nullable(),
14643
+ sourceKind: z21.enum(SerpPageSnapshotSourceKindValues),
14644
+ sourcePosition: z21.number().int().min(1).nullable(),
14645
+ status: z21.enum(SerpPageFetchStatusValues),
14646
+ fetchedVia: z21.enum(SerpPageFetchedViaValues).nullable(),
14647
+ httpStatus: z21.number().int().min(100).max(599).nullable(),
14648
+ contentType: z21.string().nullable(),
14649
+ title: z21.string().nullable(),
14405
14650
  canonicalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
14406
- metaDescription: z20.string().nullable(),
14407
- headings: z20.array(z20.object({
14408
- level: z20.number().int().min(1).max(6),
14409
- text: z20.string()
14651
+ metaDescription: z21.string().nullable(),
14652
+ headings: z21.array(z21.object({
14653
+ level: z21.number().int().min(1).max(6),
14654
+ text: z21.string()
14410
14655
  }).strict()).default([]),
14411
- artifact: z20.object({
14412
- htmlBlobUrl: z20.string().url().nullable(),
14413
- textBlobUrl: z20.string().url().nullable(),
14414
- markdownBlobUrl: z20.string().url().nullable(),
14415
- screenshotBlobUrl: z20.string().url().nullable(),
14416
- contentSha256: z20.string().nullable(),
14417
- capturedAt: z20.string().nullable()
14656
+ artifact: z21.object({
14657
+ htmlBlobUrl: z21.string().url().nullable(),
14658
+ textBlobUrl: z21.string().url().nullable(),
14659
+ markdownBlobUrl: z21.string().url().nullable(),
14660
+ screenshotBlobUrl: z21.string().url().nullable(),
14661
+ contentSha256: z21.string().nullable(),
14662
+ capturedAt: z21.string().nullable()
14418
14663
  }).strict(),
14419
- error: z20.object({
14420
- code: z20.string(),
14421
- message: z20.string()
14664
+ error: z21.object({
14665
+ code: z21.string(),
14666
+ message: z21.string()
14422
14667
  }).strict().nullable()
14423
14668
  }).strict();
14424
- var SerpIntelligenceCaptureResponseSchema = z20.object({
14669
+ var SerpIntelligenceCaptureResponseSchema = z21.object({
14425
14670
  harvestResult: SerpIntelligenceHarvestResultSchema,
14426
- attempts: z20.array(SerpIntelligenceCaptureAttemptSchema),
14671
+ attempts: z21.array(SerpIntelligenceCaptureAttemptSchema),
14427
14672
  locationEvidence: SerpIntelligenceLocationEvidenceSchema.nullable(),
14428
- pageSnapshotArtifacts: z20.array(SerpPageSnapshotCaptureSchema),
14429
- billing: z20.object({
14430
- creditsUsed: z20.number().min(0).optional(),
14431
- requestId: z20.string().optional(),
14432
- jobId: z20.string().optional()
14673
+ pageSnapshotArtifacts: z21.array(SerpPageSnapshotCaptureSchema),
14674
+ billing: z21.object({
14675
+ creditsUsed: z21.number().min(0).optional(),
14676
+ requestId: z21.string().optional(),
14677
+ jobId: z21.string().optional()
14433
14678
  }).strict().optional()
14434
14679
  }).strict();
14435
- var SerpIntelligencePageSnapshotsResponseSchema = z20.object({
14436
- pageSnapshotArtifacts: z20.array(SerpPageSnapshotCaptureSchema),
14437
- attempts: z20.array(SerpIntelligenceCaptureAttemptSchema).default([])
14680
+ var SerpIntelligencePageSnapshotsResponseSchema = z21.object({
14681
+ pageSnapshotArtifacts: z21.array(SerpPageSnapshotCaptureSchema),
14682
+ attempts: z21.array(SerpIntelligenceCaptureAttemptSchema).default([])
14438
14683
  }).strict();
14439
14684
 
14440
14685
  // src/serp-intelligence/serp-capture-service.ts
@@ -14606,7 +14851,7 @@ var SERP_INTELLIGENCE_RATE_LIMIT = 60;
14606
14851
  var SERP_INTELLIGENCE_RATE_WINDOW_SECONDS = 60;
14607
14852
  var POST_CAPTURE_ROUTE_LABEL = "POST /capture";
14608
14853
  var POST_PAGE_SNAPSHOTS_ROUTE_LABEL = "POST /page-snapshots";
14609
- var serpIntelligenceApp = new Hono11();
14854
+ var serpIntelligenceApp = new Hono13();
14610
14855
  serpIntelligenceApp.use("*", createApiKeyAuth());
14611
14856
  function structuredError(input) {
14612
14857
  return {
@@ -14775,7 +15020,7 @@ serpIntelligenceApp.post("/page-snapshots", async (c) => {
14775
15020
  });
14776
15021
 
14777
15022
  // src/mcp/mcp-routes.ts
14778
- import { Hono as Hono12 } from "hono";
15023
+ import { Hono as Hono14 } from "hono";
14779
15024
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
14780
15025
 
14781
15026
  // src/mcp/mcp-oauth.ts
@@ -14859,7 +15104,7 @@ async function requireMcpCallerKey(c) {
14859
15104
  if (!user) return mcpAuthError();
14860
15105
  return callerKey;
14861
15106
  }
14862
- var mcpApp = new Hono12();
15107
+ var mcpApp = new Hono14();
14863
15108
  mcpApp.all("/", async (c) => {
14864
15109
  try {
14865
15110
  const keyOrError = await requireMcpCallerKey(c);
@@ -14883,7 +15128,7 @@ mcpApp.all("/", async (c) => {
14883
15128
  });
14884
15129
 
14885
15130
  // src/api/browser-agent-routes.ts
14886
- import { Hono as Hono13 } from "hono";
15131
+ import { Hono as Hono15 } from "hono";
14887
15132
 
14888
15133
  // src/api/browser-agent-db.ts
14889
15134
  import { randomUUID as randomUUID2 } from "crypto";
@@ -16317,7 +16562,7 @@ async function loadOpenSession(id, userId) {
16317
16562
  return row;
16318
16563
  }
16319
16564
  function buildBrowserAgentRoutes() {
16320
- const app2 = new Hono13();
16565
+ const app2 = new Hono15();
16321
16566
  app2.use("*", async (c, next) => {
16322
16567
  await migrateBrowserAgent();
16323
16568
  return next();
@@ -17023,161 +17268,9 @@ if (state.key) { refreshSessions(); if (state.current) selectSession(state.curre
17023
17268
 
17024
17269
  // src/api/stripe-routes.ts
17025
17270
  import Stripe from "stripe";
17026
- import { Hono as Hono14 } from "hono";
17027
-
17028
- // src/api/memory.ts
17029
- import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
17030
-
17031
- // src/api/session.ts
17032
- import { createHmac as createHmac2, timingSafeEqual } from "crypto";
17033
- var isProduction = () => process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
17034
- function getSessionSecret() {
17035
- const configured = process.env.SESSION_SECRET?.trim();
17036
- if (configured) return configured;
17037
- if (isProduction()) throw new Error("SESSION_SECRET is not set \u2014 add it to your Vercel env vars (see src/api/env.ts)");
17038
- return "dev-secret-change-me";
17039
- }
17040
- var secret = () => getSessionSecret();
17041
- function safeEqualHex(a, b) {
17042
- if (a.length !== b.length) return false;
17043
- try {
17044
- return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
17045
- } catch {
17046
- return false;
17047
- }
17048
- }
17049
- function signSession(userId) {
17050
- const payload = String(userId);
17051
- const sig = createHmac2("sha256", secret()).update(payload).digest("hex");
17052
- return `${payload}.${sig}`;
17053
- }
17054
- function verifySession(token) {
17055
- const dot = token.lastIndexOf(".");
17056
- if (dot === -1) return null;
17057
- const payload = token.slice(0, dot);
17058
- const sig = token.slice(dot + 1);
17059
- const expected = createHmac2("sha256", secret()).update(payload).digest("hex");
17060
- if (!safeEqualHex(sig, expected)) return null;
17061
- const id = parseInt(payload);
17062
- return isNaN(id) ? null : id;
17063
- }
17064
-
17065
- // src/api/memory.ts
17066
- var MEMORY_BASE_URL = () => (process.env.MCP_MEMORY_BASE_URL ?? "https://mcp-memory-omega.vercel.app").replace(/\/$/, "");
17067
- var ADMIN_KEY = () => process.env.MCP_MEMORY_ADMIN_KEY?.trim() ?? "";
17068
- function isMemoryOperator(email) {
17069
- if (!email) return false;
17070
- const ops = (process.env.MEMORY_OPERATOR_IDENTITIES ?? "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
17071
- return ops.includes(email.trim().toLowerCase());
17072
- }
17073
- function encKey() {
17074
- return scryptSync(getSessionSecret(), "mcp-memory-key-v1", 32);
17075
- }
17076
- function encryptMemoryKey(secret2) {
17077
- const iv = randomBytes(12);
17078
- const cipher = createCipheriv("aes-256-gcm", encKey(), iv);
17079
- const enc = Buffer.concat([cipher.update(secret2, "utf8"), cipher.final()]);
17080
- const tag = cipher.getAuthTag();
17081
- return `${iv.toString("base64")}:${tag.toString("base64")}:${enc.toString("base64")}`;
17082
- }
17083
- function decryptMemoryKey(stored) {
17084
- try {
17085
- const [ivB, tagB, dataB] = stored.split(":");
17086
- const decipher = createDecipheriv("aes-256-gcm", encKey(), Buffer.from(ivB, "base64"));
17087
- decipher.setAuthTag(Buffer.from(tagB, "base64"));
17088
- return Buffer.concat([decipher.update(Buffer.from(dataB, "base64")), decipher.final()]).toString("utf8");
17089
- } catch {
17090
- return null;
17091
- }
17092
- }
17093
- async function memoryCall(toolName, args, userMemoryKey) {
17094
- try {
17095
- const res = await fetch(`${MEMORY_BASE_URL()}/api/mcp/memoryServer/tools/${toolName}/execute`, {
17096
- method: "POST",
17097
- headers: { "content-type": "application/json" },
17098
- body: JSON.stringify({ data: { ...args, apiKey: userMemoryKey } })
17099
- });
17100
- const body = await res.json().catch(() => null);
17101
- if (!res.ok) return { ok: false, error: body?.error ?? `memory ${toolName} failed (${res.status})` };
17102
- if (body?.result) return body.result;
17103
- return { ok: false, error: body?.error ?? `memory ${toolName} returned no result` };
17104
- } catch (err) {
17105
- return { ok: false, error: err?.message ?? "memory call failed" };
17106
- }
17107
- }
17108
- function personalVault(user) {
17109
- return `mcp-${user.id}`;
17110
- }
17111
- function memoryIdentity(user) {
17112
- return user.email;
17113
- }
17114
- function resolveMemoryIdentity(user) {
17115
- return memoryIdentity(user);
17116
- }
17117
- function memoryPlanForReads(user) {
17118
- if (isMemoryOperator(user.email)) return "unlimited";
17119
- if (user.memory_plan === "pro" || user.memory_plan === "team") return user.memory_plan;
17120
- return "free";
17121
- }
17122
- async function provisionMemoryForUser(user) {
17123
- try {
17124
- const { key } = await getOrCreateUserMemoryKey(user);
17125
- if (key) await setMemoryProvisioned(user.id, true);
17126
- } catch {
17127
- }
17128
- }
17129
- async function getOrCreateUserMemoryKey(user) {
17130
- const creds = await getUserMemoryCreds(user.id);
17131
- if (creds.memory_key) {
17132
- const decrypted = decryptMemoryKey(creds.memory_key);
17133
- if (decrypted) return { key: decrypted };
17134
- }
17135
- const admin = ADMIN_KEY();
17136
- if (!admin) return { key: null, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
17137
- const identity = memoryIdentity(user);
17138
- const plan = isMemoryOperator(user.email) ? "unlimited" : (await getMemoryPlan(user.id)).plan;
17139
- const provisioned = await memoryCall(
17140
- "provisionDefaultsTool",
17141
- { granteeIdentity: identity, issueKey: true, plan },
17142
- admin
17143
- );
17144
- if (!provisioned.ok || !provisioned.secret) {
17145
- return { key: null, error: provisioned.error ?? "failed to provision default vaults" };
17146
- }
17147
- await setUserMemoryCreds(user.id, encryptMemoryKey(provisioned.secret), identity);
17148
- return { key: provisioned.secret };
17149
- }
17150
- async function syncMemoryKeyPlan(user, plan) {
17151
- const effectivePlan = isMemoryOperator(user.email) ? "unlimited" : plan;
17152
- const { key, error } = await getOrCreateUserMemoryKey(user);
17153
- if (!key) return { ok: false, error: error ?? "memory unavailable" };
17154
- const listed = await memoryCall(
17155
- "listKeysTool",
17156
- {},
17157
- key
17158
- );
17159
- if (!listed.ok || !listed.keys?.length) return { ok: false, error: listed.error ?? "no memory keys found" };
17160
- const target = listed.keys[0];
17161
- if (target.plan === effectivePlan) return { ok: true };
17162
- const res = await memoryCall("setScopeTool", { keyId: target.keyId, plan: effectivePlan }, key);
17163
- return { ok: res.ok, error: res.error };
17164
- }
17165
- async function syncScheduleEntitlement(user, enabled, quotaPerPeriod) {
17166
- const admin = ADMIN_KEY();
17167
- if (!admin) return { ok: false, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
17168
- const identity = resolveMemoryIdentity(user);
17169
- const res = await memoryCall("setScheduleEntitlementTool", {
17170
- granteeIdentity: identity,
17171
- enabled,
17172
- quotaPerPeriod,
17173
- mcpScraperApiKey: user.api_key
17174
- }, admin);
17175
- return { ok: res.ok, error: res.error };
17176
- }
17177
-
17178
- // src/api/stripe-routes.ts
17271
+ import { Hono as Hono16 } from "hono";
17179
17272
  var stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
17180
- var stripeApp = new Hono14();
17273
+ var stripeApp = new Hono16();
17181
17274
  stripeApp.post("/webhooks", async (c) => {
17182
17275
  const sig = c.req.header("stripe-signature");
17183
17276
  const body = await c.req.text();
@@ -17277,7 +17370,7 @@ async function resolveUser(customerId, emailFallback) {
17277
17370
  }
17278
17371
 
17279
17372
  // src/api/oauth-routes.ts
17280
- import { Hono as Hono15 } from "hono";
17373
+ import { Hono as Hono17 } from "hono";
17281
17374
  import { getCookie, setCookie } from "hono/cookie";
17282
17375
  import { createHash as createHash3, randomBytes as randomBytes2, randomUUID as randomUUID3 } from "crypto";
17283
17376
  import { importPKCS8, exportJWK, SignJWT } from "jose";
@@ -17294,6 +17387,7 @@ function isScraperResource(resource) {
17294
17387
  var ACCESS_TTL_SECONDS = 3600;
17295
17388
  var REFRESH_TTL_SECONDS = 60 * 60 * 24 * 30;
17296
17389
  var CODE_TTL_SECONDS = 60;
17390
+ var ROTATION_GRACE_SECONDS = 120;
17297
17391
  var secureCookies = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
17298
17392
  var sessionCookieOptions = {
17299
17393
  httpOnly: true,
@@ -17445,7 +17539,7 @@ function redirectWithError(redirectUri, state, error) {
17445
17539
  if (state) u.searchParams.set("state", state);
17446
17540
  return new Response(null, { status: 302, headers: { Location: u.toString() } });
17447
17541
  }
17448
- var oauthApp = new Hono15();
17542
+ var oauthApp = new Hono17();
17449
17543
  oauthApp.use("*", async (c, next) => {
17450
17544
  c.header("Access-Control-Allow-Origin", "*");
17451
17545
  c.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
@@ -17669,6 +17763,13 @@ oauthApp.post("/oauth/token", async (c) => {
17669
17763
  const refreshToken = get("refresh_token") ?? "";
17670
17764
  const clientId = get("client_id") ?? "";
17671
17765
  const record = await getRefresh(refreshToken);
17766
+ if (record && record.revoked && record.replaced_by && record.rotated_at && Date.now() - new Date(record.rotated_at).getTime() < ROTATION_GRACE_SECONDS * 1e3 && (!clientId || record.client_id === clientId)) {
17767
+ const graceUser = await getUserByEmail(record.identity);
17768
+ const gracePlan = graceUser ? resolvePlan(graceUser) : "free";
17769
+ const graceAudience = record.resource ?? RESOURCE();
17770
+ const graceAccess = await mintAccessToken(record.identity, record.scope, gracePlan, graceAudience);
17771
+ return c.json({ access_token: graceAccess, token_type: "Bearer", expires_in: ACCESS_TTL_SECONDS, refresh_token: record.replaced_by, scope: record.scope });
17772
+ }
17672
17773
  if (!record || record.revoked) return tokenErrorResponse(c, "invalid_grant", "Refresh token is invalid or revoked", 400);
17673
17774
  if (new Date(record.expires_at).getTime() < Date.now()) return tokenErrorResponse(c, "invalid_grant", "Refresh token has expired", 400);
17674
17775
  if (clientId && record.client_id !== clientId) return tokenErrorResponse(c, "invalid_grant", "client_id mismatch", 400);
@@ -17692,8 +17793,8 @@ oauthApp.post("/oauth/token", async (c) => {
17692
17793
  });
17693
17794
 
17694
17795
  // src/api/chat-routes.ts
17695
- import { Hono as Hono16 } from "hono";
17696
- var chatApp = new Hono16();
17796
+ import { Hono as Hono18 } from "hono";
17797
+ var chatApp = new Hono18();
17697
17798
  function tokenOk(token) {
17698
17799
  return !!token && token.startsWith("mk_") && token.length > 10;
17699
17800
  }
@@ -18363,8 +18464,8 @@ loadChannels();
18363
18464
  }
18364
18465
 
18365
18466
  // src/api/schedule-routes.ts
18366
- import { Hono as Hono17 } from "hono";
18367
- var scheduleApp = new Hono17();
18467
+ import { Hono as Hono19 } from "hono";
18468
+ var scheduleApp = new Hono19();
18368
18469
  function tokenOk2(token) {
18369
18470
  return !!token && token.startsWith("mk_") && token.length > 10;
18370
18471
  }
@@ -19119,7 +19220,7 @@ var sessionAuth = createMiddleware3(async (c, next) => {
19119
19220
  c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
19120
19221
  return next();
19121
19222
  });
19122
- var app = new Hono18();
19223
+ var app = new Hono20();
19123
19224
  var STRIPE_API_VERSION = "2026-02-25.clover";
19124
19225
  function requireStripeSecret() {
19125
19226
  const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
@@ -19164,6 +19265,7 @@ function opForCostPath(p) {
19164
19265
  if (p.startsWith("/instagram/")) return "instagram";
19165
19266
  if (p === "/reddit/thread") return "reddit_thread";
19166
19267
  if (p.startsWith("/api/internal/site-architecture-auditor")) return "audit_site";
19268
+ if (p.startsWith("/api/internal/memory")) return "memory_ai";
19167
19269
  if (p.startsWith("/agent/")) return "browser_session";
19168
19270
  return null;
19169
19271
  }
@@ -19241,6 +19343,27 @@ app.post("/auth/logout", requireAllowedOrigin, (c) => {
19241
19343
  deleteCookie(c, "session", { path: "/", secure: secureCookies2, sameSite: "Strict" });
19242
19344
  return c.json({ ok: true });
19243
19345
  });
19346
+ app.post("/account/delete", requireAllowedOrigin, sessionAuth, async (c) => {
19347
+ const user = c.get("sessionUser");
19348
+ const secret2 = requireStripeSecret();
19349
+ if (secret2 && user.stripe_customer_id) {
19350
+ const stripeClient = new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
19351
+ const subs = await stripeClient.subscriptions.list({ customer: user.stripe_customer_id, status: "all", limit: 100 });
19352
+ for (const sub of subs.data) {
19353
+ if (sub.status === "active" || sub.status === "trialing" || sub.status === "past_due") {
19354
+ try {
19355
+ await stripeClient.subscriptions.cancel(sub.id);
19356
+ } catch (err) {
19357
+ console.error("[account/delete] failed to cancel subscription", sub.id, err instanceof Error ? err.message : String(err));
19358
+ return c.json({ error: "Could not cancel an active subscription \u2014 please try again or contact support." }, 500);
19359
+ }
19360
+ }
19361
+ }
19362
+ }
19363
+ await deactivateUser(user.id);
19364
+ deleteCookie(c, "session", { path: "/", secure: secureCookies2, sameSite: "Strict" });
19365
+ return c.json({ ok: true });
19366
+ });
19244
19367
  app.post("/auth/forgot-password", requireAllowedOrigin, async (c) => {
19245
19368
  const { email } = await c.req.json();
19246
19369
  const normalizedEmail = email?.trim().toLowerCase();
@@ -20263,6 +20386,24 @@ app.post("/billing/subscribe", requireAllowedOrigin, sessionAuth, async (c) => {
20263
20386
  return c.json({ error: message }, 500);
20264
20387
  }
20265
20388
  });
20389
+ app.post("/billing/portal", requireAllowedOrigin, sessionAuth, async (c) => {
20390
+ try {
20391
+ const user = c.get("sessionUser");
20392
+ if (!user.stripe_customer_id) return c.json({ error: "No billing account yet \u2014 subscribe first." }, 409);
20393
+ const secret2 = requireStripeSecret();
20394
+ if (!secret2) return c.json({ error: "Stripe is not configured." }, 503);
20395
+ const stripeClient = new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
20396
+ const session = await stripeClient.billingPortal.sessions.create({
20397
+ customer: user.stripe_customer_id,
20398
+ return_url: `${appOrigin()}/billing`
20399
+ });
20400
+ return c.json({ url: session.url });
20401
+ } catch (err) {
20402
+ const message = err instanceof Error ? err.message : "Unable to open billing portal.";
20403
+ console.error("[billing/portal]", message);
20404
+ return c.json({ error: message }, 500);
20405
+ }
20406
+ });
20266
20407
  app.post("/billing/concurrency/terminal-checkout", auth2, async (c) => {
20267
20408
  try {
20268
20409
  const user = c.get("user");
@@ -20431,7 +20572,7 @@ app.get("/cron/tick", async (c) => {
20431
20572
  if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
20432
20573
  return c.json({ error: "Unauthorized" }, 401);
20433
20574
  }
20434
- const { drainQueue } = await import("./worker-FNSBPP7Y.js");
20575
+ const { drainQueue } = await import("./worker-4CVMSUZR.js");
20435
20576
  const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
20436
20577
  const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
20437
20578
  const [results, sweepResult, reapResult, expiredResult, blobCleanup] = await Promise.all([
@@ -20445,12 +20586,14 @@ app.get("/cron/tick", async (c) => {
20445
20586
  });
20446
20587
  app.on(["GET", "POST", "PUT"], "/api/inngest", serveInngest({ client: inngest, functions: [siteAuditFn, siteExtractFn] }));
20447
20588
  app.route("/api/internal/site-architecture-auditor", siteAuditApp);
20589
+ app.route("/api/internal/memory", internalMemoryApp);
20448
20590
  app.route("/youtube", youtubeApp);
20449
20591
  app.route("/screenshot", screenshotApp);
20450
20592
  app.route("/facebook", facebookAdApp);
20451
20593
  app.route("/google-ads", googleAdsApp);
20452
20594
  app.route("/instagram", instagramApp);
20453
20595
  app.route("/reddit", redditApp);
20596
+ app.route("/video", videoApp);
20454
20597
  app.route("/maps", mapsApp);
20455
20598
  app.route("/directory", directoryApp);
20456
20599
  app.route("/workflows", workflowApp);
@@ -20582,4 +20725,4 @@ app.get("/blog/:slug/", (c) => {
20582
20725
  export {
20583
20726
  app
20584
20727
  };
20585
- //# sourceMappingURL=server-CZT7EULY.js.map
20728
+ //# sourceMappingURL=server-MKUN6NQZ.js.map