mcp-scraper 0.47.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +2 -2
  2. package/dist/analytics-repository-WWXVU3VW.js +119 -0
  3. package/dist/bin/api-server.cjs +7429 -1887
  4. package/dist/bin/api-server.cjs.map +1 -1
  5. package/dist/bin/api-server.js +8 -4
  6. package/dist/bin/api-server.js.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  8. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-cli.js +1 -1
  10. package/dist/bin/mcp-scraper-install.cjs +2 -2
  11. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  12. package/dist/bin/mcp-scraper-install.js +2 -2
  13. package/dist/bin/mcp-stdio-server.cjs +1812 -1028
  14. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  15. package/dist/bin/mcp-stdio-server.js +5 -5
  16. package/dist/bin/paa-harvest.cjs.map +1 -1
  17. package/dist/bin/paa-harvest.js +3 -3
  18. package/dist/{chunk-MW3ET532.js → chunk-6DPIE262.js} +2 -2
  19. package/dist/chunk-IAXQIHNS.js +7 -0
  20. package/dist/chunk-IAXQIHNS.js.map +1 -0
  21. package/dist/{chunk-JH7A4HA7.js → chunk-MFE6RMIA.js} +2 -2
  22. package/dist/{chunk-5S6LYQQO.js → chunk-MFNGUM4L.js} +2 -2
  23. package/dist/chunk-MFNGUM4L.js.map +1 -0
  24. package/dist/{chunk-IWYYZXBR.js → chunk-PODASLGT.js} +2 -2
  25. package/dist/{chunk-XCGQBGDA.js → chunk-PTABZABJ.js} +2081 -1262
  26. package/dist/chunk-PTABZABJ.js.map +1 -0
  27. package/dist/{chunk-QSUGPYGQ.js → chunk-QAH7WMYC.js} +2 -2
  28. package/dist/{chunk-QSUGPYGQ.js.map → chunk-QAH7WMYC.js.map} +1 -1
  29. package/dist/{chunk-TV6KPZ6J.js → chunk-R66PJOZW.js} +2 -2
  30. package/dist/chunk-YW2LXLDU.js +2865 -0
  31. package/dist/chunk-YW2LXLDU.js.map +1 -0
  32. package/dist/{chunk-GV5EGE5M.js → chunk-ZEMHL36J.js} +2 -2
  33. package/dist/{db-W3RPRILW.js → db-A4YUPV4Z.js} +2 -2
  34. package/dist/{extract-bundle-DXWWQER6.js → extract-bundle-2HONVC5T.js} +3 -3
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.js +3 -3
  37. package/dist/{location-data-repository-2FJVMTZ2.js → location-data-repository-YWFJGNIP.js} +3 -3
  38. package/dist/{server-OHMZJRSB.js → server-EVTCL3KB.js} +2124 -269
  39. package/dist/server-EVTCL3KB.js.map +1 -0
  40. package/dist/{site-extract-repository-UULQAGO4.js → site-extract-repository-STNOI5OH.js} +3 -3
  41. package/dist/site-extract-repository-STNOI5OH.js.map +1 -0
  42. package/dist/{worker-3QX7LG62.js → worker-4MHZS3OB.js} +5 -5
  43. package/package.json +3 -1
  44. package/dist/chunk-5S6LYQQO.js.map +0 -1
  45. package/dist/chunk-H4NWAF5B.js +0 -7
  46. package/dist/chunk-H4NWAF5B.js.map +0 -1
  47. package/dist/chunk-XCGQBGDA.js.map +0 -1
  48. package/dist/server-OHMZJRSB.js.map +0 -1
  49. /package/dist/{db-W3RPRILW.js.map → analytics-repository-WWXVU3VW.js.map} +0 -0
  50. /package/dist/{chunk-MW3ET532.js.map → chunk-6DPIE262.js.map} +0 -0
  51. /package/dist/{chunk-JH7A4HA7.js.map → chunk-MFE6RMIA.js.map} +0 -0
  52. /package/dist/{chunk-IWYYZXBR.js.map → chunk-PODASLGT.js.map} +0 -0
  53. /package/dist/{chunk-TV6KPZ6J.js.map → chunk-R66PJOZW.js.map} +0 -0
  54. /package/dist/{chunk-GV5EGE5M.js.map → chunk-ZEMHL36J.js.map} +0 -0
  55. /package/dist/{location-data-repository-2FJVMTZ2.js.map → db-A4YUPV4Z.js.map} +0 -0
  56. /package/dist/{extract-bundle-DXWWQER6.js.map → extract-bundle-2HONVC5T.js.map} +0 -0
  57. /package/dist/{site-extract-repository-UULQAGO4.js.map → location-data-repository-YWFJGNIP.js.map} +0 -0
  58. /package/dist/{worker-3QX7LG62.js.map → worker-4MHZS3OB.js.map} +0 -0
@@ -10,14 +10,68 @@ import {
10
10
  importHostedCensusPlacesCsv,
11
11
  importHostedZipGroupsCsv,
12
12
  listActiveHostedLocationDatasets
13
- } from "./chunk-TV6KPZ6J.js";
13
+ } from "./chunk-R66PJOZW.js";
14
+ import {
15
+ AnalyticsRepositoryError,
16
+ analyticsAcquisition,
17
+ analyticsBusinessMetrics,
18
+ analyticsChannelBreakdown,
19
+ analyticsContent,
20
+ analyticsConversions,
21
+ analyticsDimensions,
22
+ analyticsEventCounts,
23
+ analyticsForecast,
24
+ analyticsHealth,
25
+ analyticsOverview,
26
+ analyticsPaths,
27
+ analyticsTimeseries,
28
+ archiveAnalyticsActivationDestination,
29
+ archiveAnalyticsCampaignLink,
30
+ claimAnalyticsFormDeliveryJobs,
31
+ completeAnalyticsFormDelivery,
32
+ createAnalyticsActivationDestination,
33
+ createAnalyticsCampaignLink,
34
+ createAnalyticsConversion,
35
+ createAnalyticsCrmImport,
36
+ createAnalyticsExport,
37
+ createAnalyticsForm,
38
+ createAnalyticsPixel,
39
+ createAnalyticsSite,
40
+ deferAnalyticsFormDelivery,
41
+ deleteAnalyticsSite,
42
+ getAnalyticsPersonJourney,
43
+ getAnalyticsPool,
44
+ getPublicAnalyticsForm,
45
+ getSessionSecret,
46
+ ingestAnalyticsEvents,
47
+ isAnalyticsFormPlacementApproved,
48
+ linkAnalyticsFormIdentity,
49
+ listAnalyticsActivationDestinations,
50
+ listAnalyticsCampaignLinks,
51
+ listAnalyticsCrmImports,
52
+ listAnalyticsForms,
53
+ listAnalyticsPeople,
54
+ listAnalyticsPixels,
55
+ listAnalyticsSites,
56
+ queueAnalyticsActivation,
57
+ queueAnalyticsFormDelivery,
58
+ recordAnalyticsFormSubmission,
59
+ refreshAnalyticsDailyRollupsIfDue,
60
+ resolveAnalyticsCampaignLink,
61
+ setAnalyticsPixelDomainState,
62
+ signSession,
63
+ updateAnalyticsBusinessModel,
64
+ updateAnalyticsPixel,
65
+ upsertAnalyticsAdSpend,
66
+ verifySession
67
+ } from "./chunk-YW2LXLDU.js";
14
68
  import {
15
69
  classifyHarvestProblem,
16
70
  createHarvestAttemptRecorder,
17
71
  deliverWebhook,
18
72
  harvestProblemResponse,
19
73
  serializeHarvestProblem
20
- } from "./chunk-GV5EGE5M.js";
74
+ } from "./chunk-ZEMHL36J.js";
21
75
  import {
22
76
  AIModeSelectors,
23
77
  AIOverviewSelectors,
@@ -44,9 +98,10 @@ import {
44
98
  resolveKernelProxyId,
45
99
  serpArrivalUrl,
46
100
  withRuntimeUserAgent
47
- } from "./chunk-JH7A4HA7.js";
101
+ } from "./chunk-MFE6RMIA.js";
48
102
  import {
49
103
  ApiErrorResponseSchema,
104
+ ArtifactTemplatePresetKeySchema,
50
105
  CommonsClaimPublicationInputSchema,
51
106
  CommonsPreparePublicationInputSchema,
52
107
  CommonsPublishEditorialInputSchema,
@@ -62,6 +117,9 @@ import {
62
117
  InternalScheduledArtifactRenderRequestSchema,
63
118
  ListScheduledRunsQuerySchema,
64
119
  LocalSourcebookSchemaTypeSchema,
120
+ NEWSROOM_PUBLISHER_TEMPLATE_PRESET,
121
+ NewsroomPublisherTemplateConfigSchema,
122
+ NewsroomPublisherV1InputSchema,
65
123
  PERSONAL_AUTHORITY_TEMPLATE_PRESET,
66
124
  PersonalAuthorityTemplateConfigSchema,
67
125
  PersonalAuthorityV1InputSchema,
@@ -97,7 +155,7 @@ import {
97
155
  resolveDeploymentProfile,
98
156
  resolveLocalSourcebookSchemaType,
99
157
  transcribeMediaUrl
100
- } from "./chunk-XCGQBGDA.js";
158
+ } from "./chunk-PTABZABJ.js";
101
159
  import {
102
160
  auditImageUrls,
103
161
  auditImages,
@@ -125,7 +183,7 @@ import {
125
183
  recordKernelSession,
126
184
  recordVendorUsage,
127
185
  runWithCostContext
128
- } from "./chunk-IWYYZXBR.js";
186
+ } from "./chunk-PODASLGT.js";
129
187
  import {
130
188
  listWorkflowDefinitions,
131
189
  runWorkflow,
@@ -153,7 +211,7 @@ import {
153
211
  } from "./chunk-3ZUBQQPQ.js";
154
212
  import {
155
213
  PACKAGE_VERSION
156
- } from "./chunk-H4NWAF5B.js";
214
+ } from "./chunk-IAXQIHNS.js";
157
215
  import {
158
216
  abandonExtractSettlement,
159
217
  countSuccessfulPages,
@@ -176,7 +234,7 @@ import {
176
234
  setExtractJobTotal,
177
235
  settleExtractJob,
178
236
  terminalExtractJobStatus
179
- } from "./chunk-MW3ET532.js";
237
+ } from "./chunk-6DPIE262.js";
180
238
  import {
181
239
  BROWSER_OPEN_MIN_BALANCE_MC,
182
240
  CONCURRENCY_PRICE_ID,
@@ -333,7 +391,7 @@ import {
333
391
  startHarvestAttempt,
334
392
  upsertPageSnapshot,
335
393
  verifyPassword
336
- } from "./chunk-5S6LYQQO.js";
394
+ } from "./chunk-MFNGUM4L.js";
337
395
 
338
396
  // src/blog/registry.ts
339
397
  var posts = [
@@ -6271,7 +6329,7 @@ async function extractSite(opts) {
6271
6329
  }
6272
6330
 
6273
6331
  // src/api/server.ts
6274
- import { Hono as Hono35 } from "hono";
6332
+ import { Hono as Hono36 } from "hono";
6275
6333
  import { serve as serveInngest } from "inngest/hono";
6276
6334
 
6277
6335
  // src/inngest/client.ts
@@ -8679,7 +8737,7 @@ var siteExtractFn = inngest.createFunction(
8679
8737
  });
8680
8738
  }) : null;
8681
8739
  const finalized = await step.run("finalize", async () => {
8682
- const { assembleExtractArtifacts } = await import("./extract-bundle-DXWWQER6.js");
8740
+ const { assembleExtractArtifacts } = await import("./extract-bundle-2HONVC5T.js");
8683
8741
  const current = await getExtractJob(jobId2);
8684
8742
  if (!current) throw new Error("extract job disappeared before finalization");
8685
8743
  const stored = await assembleExtractArtifacts(current, { branding, imageAudit });
@@ -9621,7 +9679,7 @@ async function resolveUsZipsPath(requestedPath) {
9621
9679
  async function loadZipGroups(stateAbbr, requestedPath, warnings, sourceMode) {
9622
9680
  const mode = resolvedLocationDataMode({ zipSource: sourceMode });
9623
9681
  if (mode === "hosted") {
9624
- const { getHostedZipGroups } = await import("./location-data-repository-2FJVMTZ2.js");
9682
+ const { getHostedZipGroups } = await import("./location-data-repository-YWFJGNIP.js");
9625
9683
  const hosted = await getHostedZipGroups(stateAbbr);
9626
9684
  if (!hosted.dataset) {
9627
9685
  throw new DirectoryLocationError(
@@ -9684,7 +9742,7 @@ async function resolveDirectoryMarkets(options, config = {}) {
9684
9742
  const sourceUrl = censusStateUrl(state.fips);
9685
9743
  const warnings = [];
9686
9744
  if (resolvedLocationDataMode(config) === "hosted") {
9687
- const { queryHostedLocationMarkets } = await import("./location-data-repository-2FJVMTZ2.js");
9745
+ const { queryHostedLocationMarkets } = await import("./location-data-repository-YWFJGNIP.js");
9688
9746
  const hosted = await queryHostedLocationMarkets({
9689
9747
  state: state.abbr,
9690
9748
  minPopulation: options.minPopulation,
@@ -14250,8 +14308,8 @@ siteAuditApp.get("/jobs", async (c) => {
14250
14308
  import { Hono as Hono2 } from "hono";
14251
14309
  var internalMemoryApp = new Hono2();
14252
14310
  internalMemoryApp.post("/ai-debit", async (c) => {
14253
- const secret2 = c.req.header("x-internal-secret");
14254
- if (!secret2 || secret2 !== process.env.INTERNAL_MEMORY_DEBIT_SECRET) {
14311
+ const secret = c.req.header("x-internal-secret");
14312
+ if (!secret || secret !== process.env.INTERNAL_MEMORY_DEBIT_SECRET) {
14255
14313
  return c.json({ error: "unauthorized" }, 401);
14256
14314
  }
14257
14315
  const body = await c.req.json().catch(() => ({}));
@@ -14342,9 +14400,9 @@ var UnifiedBillingError = class extends Error {
14342
14400
  details;
14343
14401
  };
14344
14402
  function billingSecret() {
14345
- const secret2 = process.env.INTERNAL_BILLING_SECRET?.trim() || process.env.INTERNAL_MEMORY_DEBIT_SECRET?.trim();
14346
- if (!secret2) throw new UnifiedBillingError("unauthorized", "internal billing secret is not configured", 503);
14347
- return secret2;
14403
+ const secret = process.env.INTERNAL_BILLING_SECRET?.trim() || process.env.INTERNAL_MEMORY_DEBIT_SECRET?.trim();
14404
+ if (!secret) throw new UnifiedBillingError("unauthorized", "internal billing secret is not configured", 503);
14405
+ return secret;
14348
14406
  }
14349
14407
  function normalizeIdentity(identity) {
14350
14408
  const normalized = identity.trim().toLowerCase();
@@ -14764,42 +14822,6 @@ async function voidScheduledRunAuthorization(args) {
14764
14822
 
14765
14823
  // src/api/memory.ts
14766
14824
  import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
14767
-
14768
- // src/api/session.ts
14769
- import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
14770
- var isProduction = () => process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
14771
- function getSessionSecret() {
14772
- const configured = process.env.SESSION_SECRET?.trim();
14773
- if (configured) return configured;
14774
- if (isProduction()) throw new Error("SESSION_SECRET is not set \u2014 add it to your Vercel env vars (see src/api/env.ts)");
14775
- return "dev-secret-change-me";
14776
- }
14777
- var secret = () => getSessionSecret();
14778
- function safeEqualHex(a, b) {
14779
- if (a.length !== b.length) return false;
14780
- try {
14781
- return timingSafeEqual2(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
14782
- } catch {
14783
- return false;
14784
- }
14785
- }
14786
- function signSession(userId) {
14787
- const payload = String(userId);
14788
- const sig = createHmac2("sha256", secret()).update(payload).digest("hex");
14789
- return `${payload}.${sig}`;
14790
- }
14791
- function verifySession(token4) {
14792
- const dot = token4.lastIndexOf(".");
14793
- if (dot === -1) return null;
14794
- const payload = token4.slice(0, dot);
14795
- const sig = token4.slice(dot + 1);
14796
- const expected = createHmac2("sha256", secret()).update(payload).digest("hex");
14797
- if (!safeEqualHex(sig, expected)) return null;
14798
- const id = parseInt(payload);
14799
- return isNaN(id) ? null : id;
14800
- }
14801
-
14802
- // src/api/memory.ts
14803
14825
  import { provisionDefaultsTool } from "mcpscraper-memory-tools/tools/vaults/provision-defaults";
14804
14826
  import { setScheduleEntitlementTool } from "mcpscraper-memory-tools/tools/schedule/set-schedule-entitlement";
14805
14827
  var MEMORY_BASE_URL = () => (process.env.MCP_MEMORY_BASE_URL ?? "https://memory.mcpscraper.dev").replace(/\/$/, "");
@@ -14817,10 +14839,10 @@ function isMemoryOperator(email) {
14817
14839
  function encKey() {
14818
14840
  return scryptSync(getSessionSecret(), "mcp-memory-key-v1", 32);
14819
14841
  }
14820
- function encryptMemoryKey(secret2) {
14842
+ function encryptMemoryKey(secret) {
14821
14843
  const iv = randomBytes(12);
14822
14844
  const cipher = createCipheriv("aes-256-gcm", encKey(), iv);
14823
- const enc = Buffer.concat([cipher.update(secret2, "utf8"), cipher.final()]);
14845
+ const enc = Buffer.concat([cipher.update(secret, "utf8"), cipher.final()]);
14824
14846
  const tag = cipher.getAuthTag();
14825
14847
  return `${iv.toString("base64")}:${tag.toString("base64")}:${enc.toString("base64")}`;
14826
14848
  }
@@ -16437,15 +16459,15 @@ function mediaResolverConfigured() {
16437
16459
  var RESOLVE_TIMEOUT_MS = 24e4;
16438
16460
  async function resolveYouTubeAudio(videoId) {
16439
16461
  const base = mediaResolverUrl();
16440
- const secret2 = mediaResolverSecret();
16441
- if (!base || !secret2) return null;
16462
+ const secret = mediaResolverSecret();
16463
+ if (!base || !secret) return null;
16442
16464
  const controller = new AbortController();
16443
16465
  const timer = setTimeout(() => controller.abort(), RESOLVE_TIMEOUT_MS);
16444
16466
  const startedMs = Date.now();
16445
16467
  try {
16446
16468
  const resp = await fetch(`${base.replace(/\/+$/, "")}/resolve-audio`, {
16447
16469
  method: "POST",
16448
- headers: { "content-type": "application/json", "x-frames-secret": secret2 },
16470
+ headers: { "content-type": "application/json", "x-frames-secret": secret },
16449
16471
  body: JSON.stringify({ videoUrl: `https://www.youtube.com/watch?v=${videoId}` }),
16450
16472
  signal: controller.signal
16451
16473
  });
@@ -22227,7 +22249,7 @@ async function reconcileDirectoryWorkflowSettlements(limit = 25) {
22227
22249
  }
22228
22250
 
22229
22251
  // src/api/workflow-routes.ts
22230
- import { createHmac as createHmac3 } from "crypto";
22252
+ import { createHmac as createHmac2 } from "crypto";
22231
22253
  import { readFile as readFile2 } from "fs/promises";
22232
22254
  import { Hono as Hono19 } from "hono";
22233
22255
  import { z as z28 } from "zod";
@@ -22313,9 +22335,9 @@ async function readManifestFromSummary(summary) {
22313
22335
  }
22314
22336
  }
22315
22337
  function webhookSignature(body, timestamp) {
22316
- const secret2 = process.env.MCP_SCRAPER_WEBHOOK_SECRET?.trim();
22317
- if (!secret2) return null;
22318
- return createHmac3("sha256", secret2).update(`${timestamp}.${body}`).digest("hex");
22338
+ const secret = process.env.MCP_SCRAPER_WEBHOOK_SECRET?.trim();
22339
+ if (!secret) return null;
22340
+ return createHmac2("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
22319
22341
  }
22320
22342
  async function deliverWorkflowWebhook(input) {
22321
22343
  if (!input.webhookUrl) return;
@@ -22753,8 +22775,8 @@ async function dispatchDueWorkflowSchedules(apiUrl, limit = 3) {
22753
22775
  return { dispatched: results.length, results };
22754
22776
  }
22755
22777
  workflowApp.post("/cron/dispatch", async (c) => {
22756
- const secret2 = c.req.header("authorization");
22757
- if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
22778
+ const secret = c.req.header("authorization");
22779
+ if (!process.env.CRON_SECRET || secret !== `Bearer ${process.env.CRON_SECRET}`) {
22758
22780
  return c.json({ error: "Unauthorized" }, 401);
22759
22781
  }
22760
22782
  return c.json(await dispatchDueWorkflowSchedules(originFromUrl(c.req.url)));
@@ -33561,18 +33583,1700 @@ function xmlEscape(value) {
33561
33583
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
33562
33584
  }
33563
33585
 
33564
- // src/api/scheduled-result-routes.ts
33586
+ // src/api/analytics-routes.ts
33565
33587
  import { Hono as Hono32 } from "hono";
33588
+ import { createMiddleware as createMiddleware4 } from "hono/factory";
33589
+ import { z as z34 } from "zod";
33590
+ import { createHash as createHash15 } from "crypto";
33591
+ import Papa2 from "papaparse";
33592
+
33593
+ // src/api/analytics-entitlement.ts
33594
+ import {
33595
+ createHash as createHash14,
33596
+ createHmac as createHmac3,
33597
+ randomBytes as randomBytes3,
33598
+ timingSafeEqual as timingSafeEqual2
33599
+ } from "crypto";
33566
33600
  import { z as z33 } from "zod";
33601
+ var THORBIT_PRODUCT_URL = "https://thorbit.ai";
33602
+ var REFRESH_INTERVAL_MS = 15 * 60 * 1e3;
33603
+ var ELIGIBLE_GRACE_MS = 24 * 60 * 60 * 1e3;
33604
+ var TRIAL_LENGTH_MS = 30 * 24 * 60 * 60 * 1e3;
33605
+ var ThorbitEntitlementSchema = z33.object({
33606
+ product: z33.literal("x_ray_pixel"),
33607
+ eligible: z33.boolean(),
33608
+ reason: z33.string().min(1).max(120),
33609
+ organizationId: z33.string().min(1).max(64),
33610
+ planId: z33.string().max(32).nullable(),
33611
+ subscriptionStatus: z33.string().max(20).nullable(),
33612
+ monthlyRecurringRevenueCents: z33.number().int().min(0),
33613
+ minimumMonthlyRecurringRevenueCents: z33.number().int().min(5e3),
33614
+ checkedAt: z33.string().datetime()
33615
+ }).strict();
33616
+ var AssertionSchema = z33.object({
33617
+ version: z33.literal(1),
33618
+ issuer: z33.literal("thorbit"),
33619
+ audience: z33.literal("mcp-scraper:x-ray"),
33620
+ issuedAt: z33.number().int().positive(),
33621
+ expiresAt: z33.number().int().positive(),
33622
+ entitlement: ThorbitEntitlementSchema
33623
+ }).strict();
33624
+ function thorbitBaseUrl() {
33625
+ return (process.env.THORBIT_X_RAY_BASE_URL?.trim() || THORBIT_PRODUCT_URL).replace(/\/$/, "");
33626
+ }
33627
+ function bridgeSecret() {
33628
+ return process.env.THORBIT_X_RAY_BRIDGE_SECRET?.trim() ?? "";
33629
+ }
33630
+ function bridgeConfigured() {
33631
+ return bridgeSecret().length >= 32;
33632
+ }
33633
+ function hint(value) {
33634
+ return value.length <= 8 ? value : `${value.slice(0, 4)}\u2026${value.slice(-4)}`;
33635
+ }
33636
+ function viewFromRow(row, options = {}) {
33637
+ const now = Date.now();
33638
+ const withinGrace = row ? Date.parse(row.access_expires_at) > now : false;
33639
+ const trialActive = Boolean(row && Date.parse(row.trial_ends_at) > now);
33640
+ const paidAccess = Boolean(row?.eligible && withinGrace);
33641
+ const access2 = paidAccess || trialActive;
33642
+ const status = paidAccess ? options.stale ? "stale" : "eligible" : trialActive ? "trial" : !row || row.connection_source === "trial" ? "trial_expired" : options.bridgeError ? "bridge_unavailable" : "ineligible";
33643
+ const trialStartedAt = row?.trial_started_at ?? new Date(now).toISOString();
33644
+ const trialEndsAt = row?.trial_ends_at ?? new Date(now).toISOString();
33645
+ return {
33646
+ product: "x_ray_pixel",
33647
+ productName: "Thorbit X-Ray Pixel",
33648
+ access: access2,
33649
+ status,
33650
+ reason: trialActive ? "trial_active" : row?.reason ?? "trial_expired",
33651
+ planId: row?.plan_id ?? null,
33652
+ subscriptionStatus: row?.subscription_status ?? null,
33653
+ monthlyRecurringRevenueCents: Number(row?.monthly_recurring_revenue_cents ?? 0),
33654
+ minimumMonthlyRecurringRevenueCents: Number(row?.minimum_monthly_recurring_revenue_cents ?? 5e3),
33655
+ verifiedAt: row?.verified_at ?? null,
33656
+ connectionSource: row?.connection_source === "trial" ? null : row?.connection_source ?? null,
33657
+ thorbitOrganizationHint: row?.thorbit_org_id ? hint(row.thorbit_org_id) : null,
33658
+ trialStartedAt,
33659
+ trialEndsAt,
33660
+ trialDaysRemaining: Math.max(0, Math.ceil((Date.parse(trialEndsAt) - now) / (24 * 60 * 60 * 1e3))),
33661
+ bridgeConfigured: bridgeConfigured(),
33662
+ billing: {
33663
+ standalonePriceUsdMonthly: 50,
33664
+ includedWithThorbitSubscriptionAtOrAboveUsdMonthly: 50,
33665
+ consumesMcpScraperCredits: false
33666
+ },
33667
+ owner: {
33668
+ name: "Thorbit",
33669
+ productUrl: THORBIT_PRODUCT_URL,
33670
+ host: "MCP Scraper"
33671
+ }
33672
+ };
33673
+ }
33674
+ async function storedEntitlement(userId) {
33675
+ const result = await getAnalyticsPool().query(
33676
+ "SELECT * FROM analytics_thorbit_entitlements WHERE user_id = $1",
33677
+ [userId]
33678
+ );
33679
+ return result.rows[0] ?? null;
33680
+ }
33681
+ async function ensureTrial(userId) {
33682
+ const startedAt = /* @__PURE__ */ new Date();
33683
+ const endsAt = new Date(startedAt.getTime() + TRIAL_LENGTH_MS);
33684
+ const result = await getAnalyticsPool().query(
33685
+ `INSERT INTO analytics_thorbit_entitlements(
33686
+ user_id, thorbit_org_id, connection_source, eligible, reason, plan_id,
33687
+ subscription_status, monthly_recurring_revenue_cents,
33688
+ minimum_monthly_recurring_revenue_cents, verified_at, refresh_after,
33689
+ access_expires_at, trial_started_at, trial_ends_at, last_refresh_error, updated_at
33690
+ ) VALUES ($1,NULL,'trial',false,'trial_active',NULL,NULL,0,5000,$2,$3,$3,$2,$3,NULL,now())
33691
+ ON CONFLICT(user_id) DO UPDATE SET user_id=excluded.user_id
33692
+ RETURNING *`,
33693
+ [userId, startedAt.toISOString(), endsAt.toISOString()]
33694
+ );
33695
+ return result.rows[0];
33696
+ }
33697
+ async function persistEntitlement(userId, source, entitlement) {
33698
+ const checked = new Date(entitlement.checkedAt);
33699
+ const refreshAfter = new Date(checked.getTime() + REFRESH_INTERVAL_MS);
33700
+ const accessExpiresAt = new Date(
33701
+ checked.getTime() + (entitlement.eligible ? ELIGIBLE_GRACE_MS : REFRESH_INTERVAL_MS)
33702
+ );
33703
+ const result = await getAnalyticsPool().query(
33704
+ `INSERT INTO analytics_thorbit_entitlements(
33705
+ user_id, thorbit_org_id, connection_source, eligible, reason, plan_id,
33706
+ subscription_status, monthly_recurring_revenue_cents,
33707
+ minimum_monthly_recurring_revenue_cents, verified_at, refresh_after,
33708
+ access_expires_at, last_refresh_error, updated_at
33709
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,NULL,now())
33710
+ ON CONFLICT(user_id) DO UPDATE SET
33711
+ thorbit_org_id=excluded.thorbit_org_id,
33712
+ connection_source=excluded.connection_source,
33713
+ eligible=excluded.eligible,
33714
+ reason=excluded.reason,
33715
+ plan_id=excluded.plan_id,
33716
+ subscription_status=excluded.subscription_status,
33717
+ monthly_recurring_revenue_cents=excluded.monthly_recurring_revenue_cents,
33718
+ minimum_monthly_recurring_revenue_cents=excluded.minimum_monthly_recurring_revenue_cents,
33719
+ verified_at=excluded.verified_at,
33720
+ refresh_after=excluded.refresh_after,
33721
+ access_expires_at=excluded.access_expires_at,
33722
+ last_refresh_error=NULL,
33723
+ updated_at=now()
33724
+ RETURNING *`,
33725
+ [
33726
+ userId,
33727
+ entitlement.organizationId,
33728
+ source,
33729
+ entitlement.eligible,
33730
+ entitlement.reason,
33731
+ entitlement.planId,
33732
+ entitlement.subscriptionStatus,
33733
+ entitlement.monthlyRecurringRevenueCents,
33734
+ entitlement.minimumMonthlyRecurringRevenueCents,
33735
+ checked.toISOString(),
33736
+ refreshAfter.toISOString(),
33737
+ accessExpiresAt.toISOString()
33738
+ ]
33739
+ );
33740
+ return result.rows[0];
33741
+ }
33742
+ async function requestEntitlement(method, options) {
33743
+ const headers = { accept: "application/json" };
33744
+ let body;
33745
+ if (method === "POST") {
33746
+ headers.authorization = `Bearer ${options.apiKey ?? ""}`;
33747
+ } else {
33748
+ headers.authorization = `Bearer ${bridgeSecret()}`;
33749
+ headers["content-type"] = "application/json";
33750
+ body = JSON.stringify({ organizationId: options.organizationId });
33751
+ }
33752
+ const response = await fetch(`${thorbitBaseUrl()}/api/v1/x-ray/entitlement`, {
33753
+ method,
33754
+ headers,
33755
+ body,
33756
+ signal: AbortSignal.timeout(8e3)
33757
+ });
33758
+ const payload = await response.json().catch(() => null);
33759
+ if (!response.ok) {
33760
+ const message = payload && typeof payload === "object" && "error" in payload ? String(payload.error) : `Thorbit entitlement check failed (${response.status})`;
33761
+ throw new Error(message);
33762
+ }
33763
+ const parsed = z33.object({ entitlement: ThorbitEntitlementSchema }).safeParse(payload);
33764
+ if (!parsed.success) throw new Error("Thorbit returned an invalid entitlement assertion");
33765
+ return parsed.data.entitlement;
33766
+ }
33767
+ async function resolveAnalyticsEntitlement(userId) {
33768
+ const row = await storedEntitlement(userId) ?? await ensureTrial(userId);
33769
+ if (row.connection_source === "trial" || !row.thorbit_org_id) return viewFromRow(row);
33770
+ if (Date.parse(row.refresh_after) > Date.now()) return viewFromRow(row);
33771
+ if (!bridgeConfigured()) return viewFromRow(row, { stale: true, bridgeError: true });
33772
+ try {
33773
+ const entitlement = await requestEntitlement("PUT", {
33774
+ organizationId: row.thorbit_org_id
33775
+ });
33776
+ return viewFromRow(
33777
+ await persistEntitlement(userId, row.connection_source, entitlement)
33778
+ );
33779
+ } catch (error) {
33780
+ await getAnalyticsPool().query(
33781
+ "UPDATE analytics_thorbit_entitlements SET last_refresh_error=$2, updated_at=now() WHERE user_id=$1",
33782
+ [userId, error instanceof Error ? error.message.slice(0, 240) : "refresh_failed"]
33783
+ );
33784
+ return viewFromRow(row, { stale: true, bridgeError: true });
33785
+ }
33786
+ }
33787
+ async function analyticsOwnerHasCurrentEntitlement(userId) {
33788
+ const result = await getAnalyticsPool().query(
33789
+ `SELECT EXISTS(
33790
+ SELECT 1 FROM analytics_thorbit_entitlements
33791
+ WHERE user_id=$1 AND (
33792
+ (eligible=true AND access_expires_at > now()) OR trial_ends_at > now()
33793
+ )
33794
+ ) AS entitled`,
33795
+ [userId]
33796
+ );
33797
+ return Boolean(result.rows[0]?.entitled);
33798
+ }
33799
+ async function connectAnalyticsWithThorbitApiKey(userId, apiKey) {
33800
+ const entitlement = await requestEntitlement("POST", { apiKey });
33801
+ return viewFromRow(await persistEntitlement(userId, "api_key", entitlement));
33802
+ }
33803
+ function dashboardCallbackUrl() {
33804
+ const value = process.env.ANALYTICS_DASHBOARD_URL?.trim() || (process.env.NODE_ENV === "production" ? "https://mcpscraper.dev/dashboard/analytics" : "http://localhost:3000/dashboard/analytics");
33805
+ return new URL(value).toString();
33806
+ }
33807
+ async function createThorbitConnectUrl(userId) {
33808
+ if (!bridgeConfigured()) throw new Error("Thorbit X-Ray account bridge is not configured");
33809
+ const state = randomBytes3(32).toString("base64url");
33810
+ const stateHash = createHash14("sha256").update(state).digest("hex");
33811
+ await getAnalyticsPool().query(
33812
+ `INSERT INTO analytics_thorbit_connect_states(state_hash,user_id,expires_at)
33813
+ VALUES ($1,$2,now()+interval '10 minutes')
33814
+ ON CONFLICT(state_hash) DO NOTHING`,
33815
+ [stateHash, userId]
33816
+ );
33817
+ const url = new URL(`${thorbitBaseUrl()}/api/v1/x-ray/entitlement`);
33818
+ url.searchParams.set("redirect_uri", dashboardCallbackUrl());
33819
+ url.searchParams.set("state", state);
33820
+ return url.toString();
33821
+ }
33822
+ function verifyThorbitAssertion(token4) {
33823
+ const [encoded, signature] = token4.split(".");
33824
+ if (!encoded || !signature || !bridgeConfigured()) throw new Error("Invalid Thorbit assertion");
33825
+ const expected = createHmac3("sha256", bridgeSecret()).update(encoded).digest();
33826
+ const actual = Buffer.from(signature, "base64url");
33827
+ if (actual.length !== expected.length || !timingSafeEqual2(actual, expected)) {
33828
+ throw new Error("Invalid Thorbit assertion signature");
33829
+ }
33830
+ const parsed = AssertionSchema.safeParse(
33831
+ JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"))
33832
+ );
33833
+ if (!parsed.success || parsed.data.expiresAt <= Math.floor(Date.now() / 1e3)) {
33834
+ throw new Error("Expired or invalid Thorbit assertion");
33835
+ }
33836
+ return parsed.data.entitlement;
33837
+ }
33838
+ async function consumeThorbitConnectCallback(input) {
33839
+ const stateHash = createHash14("sha256").update(input.state).digest("hex");
33840
+ const client2 = await getAnalyticsPool().connect();
33841
+ try {
33842
+ await client2.query("BEGIN");
33843
+ const state = await client2.query(
33844
+ `UPDATE analytics_thorbit_connect_states
33845
+ SET consumed_at=now()
33846
+ WHERE state_hash=$1 AND user_id=$2 AND consumed_at IS NULL AND expires_at > now()
33847
+ RETURNING user_id`,
33848
+ [stateHash, input.userId]
33849
+ );
33850
+ if (!state.rowCount) throw new Error("Thorbit connection state is invalid or expired");
33851
+ const entitlement = verifyThorbitAssertion(input.assertion);
33852
+ await client2.query("COMMIT");
33853
+ return viewFromRow(
33854
+ await persistEntitlement(input.userId, "thorbit_session", entitlement)
33855
+ );
33856
+ } catch (error) {
33857
+ await client2.query("ROLLBACK");
33858
+ throw error;
33859
+ } finally {
33860
+ client2.release();
33861
+ }
33862
+ }
33863
+ async function disconnectAnalyticsEntitlement(userId) {
33864
+ await getAnalyticsPool().query(
33865
+ `UPDATE analytics_thorbit_entitlements
33866
+ SET thorbit_org_id=NULL,
33867
+ connection_source='trial',
33868
+ eligible=false,
33869
+ reason=CASE WHEN trial_ends_at > now() THEN 'trial_active' ELSE 'trial_expired' END,
33870
+ plan_id=NULL,
33871
+ subscription_status=NULL,
33872
+ monthly_recurring_revenue_cents=0,
33873
+ last_refresh_error=NULL,
33874
+ updated_at=now()
33875
+ WHERE user_id=$1`,
33876
+ [userId]
33877
+ );
33878
+ }
33879
+
33880
+ // src/api/analytics-routes.ts
33881
+ var analyticsApp = new Hono32();
33882
+ var auth3 = createApiKeyAuth();
33883
+ var entitlementGuard = createMiddleware4(async (c, next) => {
33884
+ const entitlement = await resolveAnalyticsEntitlement(Number(c.get("user").id));
33885
+ if (!entitlement.access) {
33886
+ return c.json(
33887
+ {
33888
+ ok: false,
33889
+ code: "thorbit_entitlement_required",
33890
+ error: "Thorbit X-Ray Pixel requires an active $50+ Thorbit subscription.",
33891
+ entitlement
33892
+ },
33893
+ 402
33894
+ );
33895
+ }
33896
+ await next();
33897
+ });
33898
+ var ThorbitApiKeySchema = z34.object({ apiKey: z34.string().trim().min(20).max(512) }).strict();
33899
+ var ThorbitCallbackSchema = z34.object({
33900
+ state: z34.string().min(20).max(300),
33901
+ assertion: z34.string().min(40).max(8e3)
33902
+ }).strict();
33903
+ var SiteInputSchema = z34.object({
33904
+ name: z34.string().trim().min(1).max(120),
33905
+ slug: z34.string().trim().min(1).max(80).optional(),
33906
+ timezone: z34.string().trim().min(1).max(80).optional()
33907
+ }).strict();
33908
+ var PixelInputSchema = z34.object({
33909
+ name: z34.string().trim().min(1).max(120),
33910
+ environment: z34.string().trim().min(1).max(60).optional()
33911
+ }).strict();
33912
+ var PixelUpdateSchema = z34.object({
33913
+ name: z34.string().trim().min(1).max(120).optional(),
33914
+ environment: z34.string().trim().min(1).max(60).optional(),
33915
+ status: z34.enum(["active", "disabled"]).optional()
33916
+ }).strict().refine(
33917
+ (value) => Object.keys(value).length > 0,
33918
+ "Provide at least one change."
33919
+ );
33920
+ var EventSchema = z34.object({
33921
+ eventId: z34.string().trim().min(8).max(160),
33922
+ eventName: z34.string().trim().regex(/^[a-z][a-z0-9_]{1,79}$/),
33923
+ occurredAt: z34.string().datetime(),
33924
+ visitorId: z34.string().trim().max(160).optional(),
33925
+ sessionId: z34.string().trim().max(160).optional(),
33926
+ path: z34.string().trim().max(2e3).optional(),
33927
+ canonicalUrl: z34.string().url().max(3e3).optional(),
33928
+ title: z34.string().trim().max(500).optional(),
33929
+ referrer: z34.string().trim().max(3e3).optional(),
33930
+ source: z34.string().trim().max(180).optional(),
33931
+ medium: z34.string().trim().max(180).optional(),
33932
+ campaign: z34.string().trim().max(240).optional(),
33933
+ clickIds: z34.record(z34.string(), z34.string().trim().max(500)).optional(),
33934
+ deviceClass: z34.enum(["desktop", "tablet", "mobile", "unknown"]).optional(),
33935
+ properties: z34.record(z34.string(), z34.unknown()).optional()
33936
+ }).strict();
33937
+ var IngestionSchema = z34.object({
33938
+ pixelId: z34.string().trim().min(8).max(160),
33939
+ events: z34.array(EventSchema).min(1).max(20)
33940
+ }).strict();
33941
+ var ConversionSchema = z34.object({
33942
+ siteId: z34.string().uuid(),
33943
+ pixelId: z34.string().uuid().optional(),
33944
+ sessionId: z34.string().trim().max(160).optional(),
33945
+ sourceEventId: z34.string().trim().min(8).max(200),
33946
+ conversionKind: z34.string().trim().regex(/^[a-z][a-z0-9_]{1,79}$/),
33947
+ valueMinor: z34.number().int().min(0).max(2147483647).optional(),
33948
+ currency: z34.string().trim().regex(/^[A-Za-z]{3}$/).optional(),
33949
+ occurredAt: z34.string().datetime()
33950
+ }).strict();
33951
+ var ExportSchema = z34.object({
33952
+ report: z34.enum(["overview", "acquisition", "content", "conversions"]).default("overview"),
33953
+ format: z34.enum(["csv", "json", "markdown"]).default("csv"),
33954
+ idempotencyKey: z34.string().trim().min(8).max(160).optional()
33955
+ }).strict();
33956
+ var CampaignLinkSchema = z34.object({
33957
+ pixelId: z34.string().uuid().optional(),
33958
+ name: z34.string().trim().min(1).max(120),
33959
+ destinationUrl: z34.string().url().max(3e3),
33960
+ shortCode: z34.string().trim().min(3).max(48).optional(),
33961
+ source: z34.string().trim().min(1).max(180),
33962
+ medium: z34.string().trim().min(1).max(180),
33963
+ campaign: z34.string().trim().min(1).max(240),
33964
+ term: z34.string().trim().max(240).optional(),
33965
+ content: z34.string().trim().max(240).optional(),
33966
+ adGroup: z34.string().trim().max(240).optional(),
33967
+ adName: z34.string().trim().max(240).optional(),
33968
+ creativeId: z34.string().trim().max(240).optional()
33969
+ }).strict();
33970
+ var FormFieldSchema = z34.object({
33971
+ id: z34.string().trim().regex(/^[a-z][a-z0-9_]{0,47}$/),
33972
+ type: z34.enum([
33973
+ "text",
33974
+ "email",
33975
+ "tel",
33976
+ "textarea",
33977
+ "select",
33978
+ "checkbox",
33979
+ "radio",
33980
+ "number"
33981
+ ]),
33982
+ label: z34.string().trim().min(1).max(120),
33983
+ required: z34.boolean().default(false),
33984
+ options: z34.array(z34.string().trim().min(1).max(120)).max(30).optional()
33985
+ }).strict();
33986
+ var FormInputSchema = z34.object({
33987
+ pixelId: z34.string().uuid(),
33988
+ name: z34.string().trim().min(1).max(120),
33989
+ fields: z34.array(FormFieldSchema).min(1).max(30).refine(
33990
+ (fields) => new Set(fields.map((field) => field.id)).size === fields.length,
33991
+ "Field IDs must be unique."
33992
+ ),
33993
+ brand: z34.object({
33994
+ primaryColor: z34.string().regex(/^#[0-9a-f]{6}$/i),
33995
+ backgroundColor: z34.string().regex(/^#[0-9a-f]{6}$/i),
33996
+ textColor: z34.string().regex(/^#[0-9a-f]{6}$/i),
33997
+ radius: z34.number().int().min(0).max(32)
33998
+ }).strict(),
33999
+ submitLabel: z34.string().trim().min(1).max(80).optional(),
34000
+ successMessage: z34.string().trim().min(1).max(500).optional(),
34001
+ redirectUrl: z34.string().url().max(3e3).optional(),
34002
+ consentText: z34.string().trim().max(1e3).optional(),
34003
+ publish: z34.boolean().optional()
34004
+ }).strict();
34005
+ var FormSubmissionSchema = z34.object({
34006
+ data: z34.record(
34007
+ z34.string(),
34008
+ z34.union([z34.string().max(5e3), z34.number(), z34.boolean()])
34009
+ ).refine((value) => Object.keys(value).length <= 30),
34010
+ visitorId: z34.string().trim().max(160).optional(),
34011
+ sessionId: z34.string().trim().max(160).optional(),
34012
+ source: z34.string().trim().max(180).optional(),
34013
+ medium: z34.string().trim().max(180).optional(),
34014
+ campaign: z34.string().trim().max(240).optional(),
34015
+ clickIds: z34.record(z34.string(), z34.string().trim().max(500)).optional(),
34016
+ placementUrl: z34.string().url().max(3e3),
34017
+ website: z34.string().max(0).optional()
34018
+ }).strict();
34019
+ var CrmImportSchema = z34.object({
34020
+ sourceSystem: z34.enum([
34021
+ "hubspot",
34022
+ "salesforce",
34023
+ "gohighlevel",
34024
+ "zoho",
34025
+ "pipedrive",
34026
+ "keap",
34027
+ "other"
34028
+ ]),
34029
+ filename: z34.string().trim().min(1).max(240),
34030
+ csv: z34.string().min(1).max(8e6),
34031
+ mapping: z34.object({
34032
+ email: z34.string().trim().min(1).max(240).optional(),
34033
+ firstName: z34.string().trim().min(1).max(240).optional(),
34034
+ lastName: z34.string().trim().min(1).max(240).optional(),
34035
+ name: z34.string().trim().min(1).max(240).optional(),
34036
+ phone: z34.string().trim().min(1).max(240).optional(),
34037
+ company: z34.string().trim().min(1).max(240).optional(),
34038
+ externalId: z34.string().trim().min(1).max(240).optional()
34039
+ }).strict()
34040
+ }).strict();
34041
+ var ActivationDestinationSchema = z34.object({
34042
+ platform: z34.enum(["meta", "google", "tiktok", "reddit"]),
34043
+ name: z34.string().trim().min(1).max(120),
34044
+ connectionRef: z34.string().trim().max(240).optional(),
34045
+ externalDatasetId: z34.string().trim().max(240).optional(),
34046
+ eventMapping: z34.record(z34.string(), z34.string().trim().max(120)).optional()
34047
+ }).strict();
34048
+ var BusinessModelSchema = z34.object({ businessModel: z34.enum(["leadgen", "saas", "ecommerce"]) }).strict();
34049
+ var AdSpendSchema = z34.object({
34050
+ rows: z34.array(
34051
+ z34.object({
34052
+ date: z34.string().date(),
34053
+ platform: z34.string().trim().min(1).max(80),
34054
+ campaign: z34.string().trim().max(240).optional(),
34055
+ spendMinor: z34.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
34056
+ currency: z34.string().regex(/^[A-Za-z]{3}$/).optional(),
34057
+ sourceRef: z34.string().max(500).optional()
34058
+ }).strict()
34059
+ ).min(1).max(5e3)
34060
+ }).strict();
34061
+ function safeOrigin(origin) {
34062
+ if (!origin || origin === "null") return null;
34063
+ try {
34064
+ const url = new URL(origin);
34065
+ return url.protocol === "http:" || url.protocol === "https:" ? url.origin : null;
34066
+ } catch {
34067
+ return null;
34068
+ }
34069
+ }
34070
+ function dashboardOriginAllowed(origin) {
34071
+ if (origin === "https://mcpscraper.dev" || origin === "https://www.mcpscraper.dev")
34072
+ return true;
34073
+ if (process.env.NODE_ENV !== "production" && /^http:\/\/(localhost|127\.0\.0\.1):\d+$/.test(origin))
34074
+ return true;
34075
+ return (process.env.ALLOWED_ORIGINS ?? process.env.APP_ORIGIN ?? "").split(",").map((value) => value.trim()).includes(origin);
34076
+ }
34077
+ analyticsApp.use("*", async (c, next) => {
34078
+ const origin = safeOrigin(c.req.header("origin"));
34079
+ const publicIngestion = c.req.path === "/v1/events";
34080
+ if (origin && (publicIngestion || dashboardOriginAllowed(origin))) {
34081
+ c.header("Access-Control-Allow-Origin", origin);
34082
+ c.header("Vary", "Origin");
34083
+ c.header(
34084
+ "Access-Control-Allow-Methods",
34085
+ "GET, POST, PATCH, DELETE, OPTIONS"
34086
+ );
34087
+ c.header("Access-Control-Allow-Headers", "content-type, x-api-key");
34088
+ c.header("Access-Control-Max-Age", "600");
34089
+ }
34090
+ if (c.req.method === "OPTIONS") return c.body(null, 204);
34091
+ await next();
34092
+ });
34093
+ function analyticsPixelSdk() {
34094
+ return `(()=>{const s=document.currentScript;if(!s)return;const pixel=s.dataset.pixel;if(!pixel)return;const endpoint=new URL('/analytics/v1/events',s.src).toString(),ttl=18e5;const key='mcp_analytics_visitor',sessionKey='mcp_analytics_session',touchKey='mcp_analytics_touch';const id=()=>crypto.randomUUID?crypto.randomUUID():'e_'+Date.now().toString(36)+Math.random().toString(36).slice(2);let consent=s.dataset.consent==='granted'&&!navigator.globalPrivacyControl,visitor=null,session=null;const read=k=>{try{return localStorage.getItem(k)}catch{return null}},write=(k,v)=>{try{localStorage.setItem(k,v)}catch{}};const ensure=()=>{if(!consent)return;visitor=read(key)||id();const now=Date.now();let saved={};try{saved=JSON.parse(read(sessionKey)||'{}')}catch{}session=saved.id&&now-Number(saved.seen||0)<ttl?saved.id:id();write(key,visitor);write(sessionKey,JSON.stringify({id:session,seen:now}))};const attribution=()=>{const q=new URLSearchParams(location.search),clickIds={},properties={},map={utm_term:'utm_term',utm_content:'utm_content',utm_id:'campaign_id',campaign_id:'campaign_id',adset_id:'ad_set_id',ad_set_id:'ad_set_id',adgroup_id:'ad_group_id',ad_group_id:'ad_group_id',ad_id:'ad_id',creative_id:'creative_id',placement:'placement'};for(const k of ['fbclid','gclid','gbraid','wbraid','ttclid','rdt_cid','msclkid']){const v=q.get(k);if(v)clickIds[k]=v}for(const [from,to] of Object.entries(map)){const v=q.get(from);if(v)properties[to]=v}const current={source:q.get('utm_source')||undefined,medium:q.get('utm_medium')||undefined,campaign:q.get('utm_campaign')||undefined,clickIds,properties};if(current.source||Object.keys(clickIds).length)write(touchKey,JSON.stringify(current));else{try{return JSON.parse(read(touchKey)||'null')||current}catch{}}return current};const send=(name,properties={})=>{if(!consent||navigator.globalPrivacyControl)return;ensure();const a=attribution();const body=JSON.stringify({pixelId:pixel,events:[{eventId:id(),eventName:name,occurredAt:new Date().toISOString(),visitorId:visitor,sessionId:session,path:location.pathname,canonicalUrl:location.origin+location.pathname,title:document.title,referrer:document.referrer||undefined,...a,deviceClass:innerWidth<640?'mobile':innerWidth<1024?'tablet':'desktop',properties:{...a.properties,...properties}}]});if(navigator.sendBeacon&&navigator.sendBeacon(endpoint,new Blob([body],{type:'application/json'})))return;fetch(endpoint,{method:'POST',headers:{'content-type':'application/json'},body,keepalive:true,credentials:'omit'}).catch(()=>{})};const api={page:()=>send('page_view'),track:send,consent:state=>{consent=(state==='granted'||state===true)&&!navigator.globalPrivacyControl;if(consent){ensure();api.page()}},debug:()=>({pixel,endpoint,consent,gpc:Boolean(navigator.globalPrivacyControl),visitorId:visitor,sessionId:session,hostname:location.hostname,path:location.pathname,...attribution()})};window.mcpAnalytics=api;ensure();if(consent)api.page();let current=location.href;const changed=()=>{if(location.href!==current){current=location.href;api.page()}};for(const method of ['pushState','replaceState']){const original=history[method];history[method]=function(...args){const result=original.apply(this,args);queueMicrotask(changed);return result}}addEventListener('popstate',changed)})();`;
34095
+ }
34096
+ function reportFilters(url) {
34097
+ const end = url.searchParams.get("end") || (/* @__PURE__ */ new Date()).toISOString();
34098
+ const defaultStart = new Date(
34099
+ Date.parse(end) - 30 * 24 * 60 * 60 * 1e3
34100
+ ).toISOString();
34101
+ const list = (name) => url.searchParams.getAll(name).flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean);
34102
+ return {
34103
+ start: url.searchParams.get("start") || defaultStart,
34104
+ end,
34105
+ timezone: validTimezone(url.searchParams.get("timezone")),
34106
+ pixelIds: list("pixelId"),
34107
+ hostnames: list("hostname"),
34108
+ source: url.searchParams.get("source") || void 0,
34109
+ medium: url.searchParams.get("medium") || void 0,
34110
+ campaign: url.searchParams.get("campaign") || void 0,
34111
+ eventName: url.searchParams.get("eventName") || void 0,
34112
+ attributionModel: url.searchParams.get("attributionModel") === "last_touch" ? "last_touch" : "first_touch",
34113
+ channelFamily: url.searchParams.get("channelFamily") || void 0,
34114
+ platform: url.searchParams.get("platform") || void 0,
34115
+ referrer: url.searchParams.get("referrer") || void 0,
34116
+ path: url.searchParams.get("path") || void 0,
34117
+ deviceClass: url.searchParams.get("deviceClass") || void 0,
34118
+ countryCode: url.searchParams.get("countryCode") || void 0,
34119
+ regionCode: url.searchParams.get("regionCode") || void 0,
34120
+ conversionKind: url.searchParams.get("conversionKind") || void 0
34121
+ };
34122
+ }
34123
+ function validTimezone(value) {
34124
+ if (!value) return "UTC";
34125
+ try {
34126
+ new Intl.DateTimeFormat("en-US", { timeZone: value }).format(/* @__PURE__ */ new Date());
34127
+ return value;
34128
+ } catch {
34129
+ throw new AnalyticsRepositoryError(
34130
+ "invalid_timezone",
34131
+ "Choose a valid IANA timezone."
34132
+ );
34133
+ }
34134
+ }
34135
+ function validateReportWindow(filters) {
34136
+ const start = Date.parse(filters.start);
34137
+ const end = Date.parse(filters.end);
34138
+ if (!Number.isFinite(start) || !Number.isFinite(end) || start >= end) {
34139
+ throw new AnalyticsRepositoryError(
34140
+ "invalid_date_range",
34141
+ "Choose a valid date range."
34142
+ );
34143
+ }
34144
+ if (end - start > 366 * 24 * 60 * 60 * 1e3) {
34145
+ throw new AnalyticsRepositoryError(
34146
+ "date_range_too_large",
34147
+ "Date ranges are limited to 366 days."
34148
+ );
34149
+ }
34150
+ }
34151
+ function reportPage(url) {
34152
+ const rawLimit = Number(url.searchParams.get("limit") || 25);
34153
+ return {
34154
+ limit: Number.isFinite(rawLimit) ? Math.min(100, Math.max(1, Math.trunc(rawLimit))) : 25,
34155
+ cursor: url.searchParams.get("cursor") || void 0
34156
+ };
34157
+ }
34158
+ function pixelSnippet(publicId) {
34159
+ return `<script async src="https://mcpscraper.dev/analytics/v1/pixel.js" data-pixel="${publicId}" data-consent="required"></script>`;
34160
+ }
34161
+ function formSnippet(publicId) {
34162
+ return `<div data-mcp-form="${publicId}"></div><script async src="https://mcpscraper.dev/analytics/v1/form.js?form=${publicId}"></script>`;
34163
+ }
34164
+ function escapeHtml6(value) {
34165
+ return String(value ?? "").replace(
34166
+ /[&<>"']/g,
34167
+ (character) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[character]
34168
+ );
34169
+ }
34170
+ function renderPublicForm(form, placementUrl) {
34171
+ if (!form)
34172
+ return "<!doctype html><title>Form unavailable</title><p>Form unavailable.</p>";
34173
+ const fields = form.fields.map((field) => {
34174
+ const required = field.required ? " required" : "";
34175
+ const options = (field.options ?? []).map(
34176
+ (option) => `<option value="${escapeHtml6(option)}">${escapeHtml6(option)}</option>`
34177
+ ).join("");
34178
+ const control = field.type === "textarea" ? `<textarea id="${escapeHtml6(field.id)}" name="${escapeHtml6(field.id)}"${required}></textarea>` : field.type === "select" ? `<select id="${escapeHtml6(field.id)}" name="${escapeHtml6(field.id)}"${required}><option value="">Choose\u2026</option>${options}</select>` : field.type === "checkbox" ? `<input id="${escapeHtml6(field.id)}" name="${escapeHtml6(field.id)}" type="checkbox" value="true"${required}>` : `<input id="${escapeHtml6(field.id)}" name="${escapeHtml6(field.id)}" type="${escapeHtml6(field.type)}"${required}>`;
34179
+ return `<label${field.type === "checkbox" ? ' class="check"' : ""}><span>${escapeHtml6(field.label)}${field.required ? " *" : ""}</span>${control}</label>`;
34180
+ }).join("");
34181
+ const brand = form.brand;
34182
+ return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>:root{font-family:ui-sans-serif,system-ui;color:${escapeHtml6(brand.textColor)};background:${escapeHtml6(brand.backgroundColor)}}*{box-sizing:border-box}body{margin:0;padding:18px}form{display:grid;gap:14px}label{display:grid;gap:6px;font-size:13px;font-weight:650}.check{grid-template-columns:auto 1fr;align-items:center}.check span{grid-column:2}.check input{grid-column:1;grid-row:1}input,textarea,select{width:100%;min-height:44px;padding:10px 12px;border:1px solid #ccd3df;border-radius:${brand.radius}px;background:white;color:inherit;font:inherit}textarea{min-height:110px;resize:vertical}button{min-height:46px;border:0;border-radius:${brand.radius}px;color:white;background:${escapeHtml6(brand.primaryColor)};font:inherit;font-weight:750;cursor:pointer}.consent{margin:0;color:#667085;font-size:11px;line-height:1.45}.notice{display:none;padding:12px;border-radius:${brand.radius}px;background:#edf8f2;color:#17613c}.hp{position:absolute!important;left:-9999px!important}</style></head><body><form id="mcp-form">${fields}<label class="hp">Website<input name="website" autocomplete="off" tabindex="-1"></label>${form.consent_text ? `<p class="consent">${escapeHtml6(form.consent_text)}</p>` : ""}<button>${escapeHtml6(form.submit_label)}</button><div class="notice" role="status"></div></form><script>(()=>{const form=document.querySelector('#mcp-form'),notice=form.querySelector('.notice'),q=new URLSearchParams(location.search);form.addEventListener('submit',async event=>{event.preventDefault();const button=form.querySelector('button');button.disabled=true;const raw=Object.fromEntries(new FormData(form).entries()),website=String(raw.website||''),clickIds={};delete raw.website;for(const k of ['fbclid','gclid','gbraid','wbraid','ttclid','rdt_cid','msclkid']){const v=q.get(k);if(v)clickIds[k]=v}try{const response=await fetch('/analytics/forms/${escapeHtml6(form.public_id)}/submissions',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({data:raw,website,placementUrl:${JSON.stringify(placementUrl)},visitorId:q.get('visitor')||undefined,sessionId:q.get('session')||undefined,source:q.get('source')||undefined,medium:q.get('medium')||undefined,campaign:q.get('campaign')||undefined,clickIds})});if(!response.ok)throw new Error('submit');notice.textContent=${JSON.stringify(form.success_message)};notice.style.display='block';form.reset();${form.redirect_url ? `setTimeout(()=>{top.location.href=${JSON.stringify(form.redirect_url)}},700);` : ""}}catch{notice.textContent='Your response could not be submitted. Please try again.';notice.style.display='block'}finally{button.disabled=false}})})()</script></body></html>`;
34183
+ }
34184
+ analyticsApp.get(
34185
+ "/health/live",
34186
+ (c) => c.json({ status: "live", checkedAt: (/* @__PURE__ */ new Date()).toISOString() }, 200, {
34187
+ "Cache-Control": "no-store"
34188
+ })
34189
+ );
34190
+ analyticsApp.get("/health/ready", async (c) => {
34191
+ const startedAt = Date.now();
34192
+ try {
34193
+ await getAnalyticsPool().query("SELECT 1");
34194
+ return c.json(
34195
+ {
34196
+ status: "ready",
34197
+ database: true,
34198
+ latencyMs: Date.now() - startedAt,
34199
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
34200
+ },
34201
+ 200,
34202
+ { "Cache-Control": "no-store" }
34203
+ );
34204
+ } catch (error) {
34205
+ return c.json(
34206
+ {
34207
+ status: "not_ready",
34208
+ database: false,
34209
+ error: error instanceof Error ? error.message : String(error),
34210
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
34211
+ },
34212
+ 503,
34213
+ { "Cache-Control": "no-store" }
34214
+ );
34215
+ }
34216
+ });
34217
+ analyticsApp.get("/v1/pixel.js", (c) => {
34218
+ c.header("Content-Type", "application/javascript; charset=utf-8");
34219
+ c.header("Cache-Control", "public, max-age=300");
34220
+ return c.body(analyticsPixelSdk());
34221
+ });
34222
+ analyticsApp.get("/v1/form.js", async (c) => {
34223
+ const form = await getPublicAnalyticsForm(c.req.query("form") || "");
34224
+ if (!form)
34225
+ return c.body("/* Form unavailable. */", 404, {
34226
+ "Content-Type": "application/javascript; charset=utf-8"
34227
+ });
34228
+ if (!await analyticsOwnerHasCurrentEntitlement(Number(form.owner_user_id)))
34229
+ return c.body("/* Thorbit X-Ray Pixel subscription required. */", 402, {
34230
+ "Content-Type": "application/javascript; charset=utf-8",
34231
+ "Cache-Control": "no-store"
34232
+ });
34233
+ const script = `(()=>{const s=document.currentScript,form=${JSON.stringify(form.public_id)},pixel=${JSON.stringify(form.pixel_public_id)};if(!document.querySelector('script[data-pixel="'+pixel+'"]')){const p=document.createElement('script');p.async=true;p.src=new URL('/analytics/v1/pixel.js',s.src);p.dataset.pixel=pixel;p.dataset.consent='required';document.head.appendChild(p)}const target=document.querySelector('[data-mcp-form="'+form+'"]');if(!target)return;const frame=document.createElement('iframe'),params=new URLSearchParams({placement:location.href}),q=new URLSearchParams(location.search);try{const visitor=localStorage.getItem('mcp_analytics_visitor'),session=JSON.parse(localStorage.getItem('mcp_analytics_session')||'{}').id;if(visitor)params.set('visitor',visitor);if(session)params.set('session',session)}catch{}for(const [from,to] of [['utm_source','source'],['utm_medium','medium'],['utm_campaign','campaign'],['fbclid','fbclid'],['gclid','gclid'],['gbraid','gbraid'],['wbraid','wbraid'],['ttclid','ttclid'],['rdt_cid','rdt_cid'],['msclkid','msclkid']]){const value=q.get(from);if(value)params.set(to,value)}frame.title=${JSON.stringify(form.name)};frame.src=new URL('/analytics/forms/'+form+'/embed?'+params,s.src);frame.style.cssText='width:100%;min-height:520px;border:0;display:block';frame.loading='lazy';target.replaceChildren(frame)})();`;
34234
+ return c.body(script, 200, {
34235
+ "Content-Type": "application/javascript; charset=utf-8",
34236
+ "Cache-Control": "public, max-age=300"
34237
+ });
34238
+ });
34239
+ analyticsApp.get("/forms/:publicId/embed", async (c) => {
34240
+ const form = await getPublicAnalyticsForm(c.req.param("publicId"));
34241
+ if (!form)
34242
+ return c.html(
34243
+ "<!doctype html><title>Form unavailable</title><p>Form unavailable.</p>",
34244
+ 404
34245
+ );
34246
+ if (!await analyticsOwnerHasCurrentEntitlement(Number(form.owner_user_id)))
34247
+ return c.html(
34248
+ '<!doctype html><meta name=viewport content="width=device-width"><style>body{font:15px system-ui;padding:24px;color:#475467}strong{display:block;color:#101828;margin-bottom:6px}</style><strong>Form unavailable</strong>This Thorbit X-Ray Pixel account needs an active subscription.',
34249
+ 402
34250
+ );
34251
+ const placementUrl = c.req.query("placement") || "";
34252
+ if (!await isAnalyticsFormPlacementApproved(form.pixel_id, placementUrl))
34253
+ return c.html(
34254
+ "<!doctype html><title>Form unavailable</title><p>This form is not approved for this hostname.</p>",
34255
+ 403
34256
+ );
34257
+ c.header(
34258
+ "Content-Security-Policy",
34259
+ "default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src 'self'; form-action 'none'; base-uri 'none'"
34260
+ );
34261
+ c.header("Cache-Control", "no-store");
34262
+ return c.html(renderPublicForm(form, placementUrl));
34263
+ });
34264
+ analyticsApp.post("/forms/:publicId/submissions", async (c) => {
34265
+ const parsed = FormSubmissionSchema.safeParse(
34266
+ await c.req.json().catch(() => null)
34267
+ );
34268
+ if (!parsed.success)
34269
+ return c.json(
34270
+ {
34271
+ ok: false,
34272
+ error: "Invalid form submission.",
34273
+ issues: parsed.error.issues
34274
+ },
34275
+ 400
34276
+ );
34277
+ const form = await getPublicAnalyticsForm(c.req.param("publicId"));
34278
+ if (!form) return c.json({ ok: false, error: "Form unavailable." }, 404);
34279
+ if (!await analyticsOwnerHasCurrentEntitlement(Number(form.owner_user_id)))
34280
+ return c.json(
34281
+ { ok: false, error: "This Thorbit X-Ray Pixel form is not currently active." },
34282
+ 402
34283
+ );
34284
+ if (!await isAnalyticsFormPlacementApproved(
34285
+ form.pixel_id,
34286
+ parsed.data.placementUrl
34287
+ ))
34288
+ return c.json({ ok: false, error: "Form hostname is not approved." }, 403);
34289
+ const allowed = new Map(
34290
+ form.fields.map((field) => [field.id, field])
34291
+ );
34292
+ for (const key of Object.keys(parsed.data.data))
34293
+ if (!allowed.has(key))
34294
+ return c.json(
34295
+ { ok: false, error: "Submission contains an unknown field." },
34296
+ 400
34297
+ );
34298
+ for (const field of allowed.values())
34299
+ if (field.required && (parsed.data.data[field.id] === void 0 || parsed.data.data[field.id] === ""))
34300
+ return c.json({ ok: false, error: `${field.label} is required.` }, 400);
34301
+ const user = await getUserById(form.owner_user_id);
34302
+ if (!user)
34303
+ return c.json({ ok: false, error: "Form owner unavailable." }, 503);
34304
+ const data = parsed.data.data;
34305
+ const email = typeof data.email === "string" ? data.email.trim().toLowerCase() : "";
34306
+ const firstName = typeof data.first_name === "string" ? data.first_name.trim() : "";
34307
+ const lastName = typeof data.last_name === "string" ? data.last_name.trim() : "";
34308
+ const fullName = [firstName, lastName].filter(Boolean).join(" ") || (typeof data.name === "string" ? data.name.trim() : "") || email || "Website lead";
34309
+ const identitySeed = email || `${fullName}:${Date.now()}`;
34310
+ const suffix2 = createHash15("sha256").update(identitySeed).digest("hex").slice(0, 12);
34311
+ const path5 = `Leads/person-${suffix2}`;
34312
+ const { key: memoryKey, error: memoryKeyError } = await getOrCreateUserMemoryKey(user);
34313
+ const existing = memoryKey ? await memoryCall("getTool", { vault: "People", path: path5 }, memoryKey) : { ok: false, error: memoryKeyError || "memory_credential_unavailable" };
34314
+ const props = {
34315
+ ...existing.note?.props ?? {},
34316
+ type: "person",
34317
+ status: "active",
34318
+ person_name: fullName,
34319
+ ...firstName ? { first_name: firstName } : {},
34320
+ ...lastName ? { last_name: lastName } : {},
34321
+ ...email ? { email } : {},
34322
+ ...typeof data.phone === "string" && data.phone.trim() ? { phone: data.phone.trim(), text_phone: data.phone.trim() } : {},
34323
+ ...typeof data.company === "string" && data.company.trim() ? { company: data.company.trim() } : {},
34324
+ attribution_site_id: form.site_id,
34325
+ attribution_form_id: form.id,
34326
+ attribution_form_name: form.name,
34327
+ attributed_source: parsed.data.source || "(direct)",
34328
+ attributed_medium: parsed.data.medium || "(none)",
34329
+ attributed_campaign: parsed.data.campaign || "(none)",
34330
+ form_fields: data,
34331
+ source_type: "analytics_form",
34332
+ source_ref: `form:${form.public_id}`,
34333
+ summary: `Website lead from ${form.name}`,
34334
+ embedding_summary: `${fullName} submitted ${form.name}`,
34335
+ related: [],
34336
+ related_vault_notes: [],
34337
+ embed: true,
34338
+ embed_priority: "normal"
34339
+ };
34340
+ const crmPayload = {
34341
+ vault: "People",
34342
+ path: path5,
34343
+ title: fullName,
34344
+ content: existing.note?.content || `# ${fullName}
34345
+
34346
+ ## Identity
34347
+ Website lead captured through ${form.name}.
34348
+
34349
+ ## Relationship
34350
+ Attribution is linked to Site Analytics.
34351
+
34352
+ ## Known Context
34353
+ Submitted the published form.
34354
+
34355
+ ## Interaction History
34356
+ - ${(/* @__PURE__ */ new Date()).toISOString()}: Form submitted.
34357
+
34358
+ ## Open Loops
34359
+ - Review and follow up.`,
34360
+ props,
34361
+ ...existing.note ? { baseRevision: existing.note.revision } : {}
34362
+ };
34363
+ const saved = memoryKey ? await memoryCall(
34364
+ "putTool",
34365
+ crmPayload,
34366
+ memoryKey
34367
+ ) : { ok: false, error: memoryKeyError || "memory_credential_unavailable" };
34368
+ const crmDeliveryStatus = saved.ok ? "delivered" : "pending";
34369
+ const receipt = await recordAnalyticsFormSubmission({
34370
+ form,
34371
+ visitorId: parsed.data.visitorId,
34372
+ sessionId: parsed.data.sessionId,
34373
+ crmPersonRef: `People::${path5}`,
34374
+ crmDeliveryStatus,
34375
+ source: parsed.data.source,
34376
+ medium: parsed.data.medium,
34377
+ campaign: parsed.data.campaign,
34378
+ clickIds: parsed.data.clickIds
34379
+ });
34380
+ const phone = typeof data.phone === "string" ? data.phone.trim() : "";
34381
+ const identityGraph = await linkAnalyticsFormIdentity({
34382
+ submissionId: receipt.submissionId,
34383
+ siteId: form.site_id,
34384
+ crmPersonRef: `People::${path5}`,
34385
+ visitorId: parsed.data.visitorId,
34386
+ sessionId: parsed.data.sessionId,
34387
+ email: email || void 0,
34388
+ phone: phone || void 0,
34389
+ clickIds: parsed.data.clickIds
34390
+ });
34391
+ if (!saved.ok) {
34392
+ await queueAnalyticsFormDelivery({
34393
+ submissionId: receipt.submissionId,
34394
+ ownerUserId: form.owner_user_id,
34395
+ payloadCiphertext: encryptMemoryKey(JSON.stringify(crmPayload)),
34396
+ errorCode: memoryKey ? "memory_write_failed" : "memory_credential_unavailable"
34397
+ });
34398
+ }
34399
+ const conversion = await createAnalyticsConversion({
34400
+ userId: form.owner_user_id,
34401
+ siteId: form.site_id,
34402
+ pixelId: form.pixel_id,
34403
+ sessionId: parsed.data.sessionId,
34404
+ sourceEventId: `form:${receipt.submissionId}`,
34405
+ conversionKind: "form_submitted",
34406
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString()
34407
+ });
34408
+ const match = {
34409
+ ...email ? { emailSha256: createHash15("sha256").update(email).digest("hex") } : {},
34410
+ ...phone ? {
34411
+ phoneSha256: createHash15("sha256").update(phone.replace(/\D/g, "")).digest("hex")
34412
+ } : {}
34413
+ };
34414
+ const activationQueued = await queueAnalyticsActivation({
34415
+ siteId: form.site_id,
34416
+ conversionId: conversion.id,
34417
+ personId: identityGraph.personId,
34418
+ payloadCiphertext: encryptMemoryKey(
34419
+ JSON.stringify({
34420
+ eventId: conversion.id,
34421
+ eventName: "form_submitted",
34422
+ eventTime: (/* @__PURE__ */ new Date()).toISOString(),
34423
+ actionSource: "website",
34424
+ sourceUrl: parsed.data.placementUrl,
34425
+ match,
34426
+ clickIds: parsed.data.clickIds ?? {},
34427
+ attribution: {
34428
+ source: parsed.data.source,
34429
+ medium: parsed.data.medium,
34430
+ campaign: parsed.data.campaign
34431
+ }
34432
+ })
34433
+ )
34434
+ });
34435
+ return c.json(
34436
+ {
34437
+ ok: true,
34438
+ submissionId: receipt.submissionId,
34439
+ crmDeliveryStatus,
34440
+ attribution: {
34441
+ personId: identityGraph.personId,
34442
+ linkedSignals: identityGraph.linkedSignals
34443
+ },
34444
+ activation: { queuedDestinations: activationQueued },
34445
+ message: form.success_message,
34446
+ redirectUrl: form.redirect_url
34447
+ },
34448
+ saved.ok ? 201 : 202
34449
+ );
34450
+ });
34451
+ analyticsApp.get("/l/:shortCode", async (c) => {
34452
+ const destination = await resolveAnalyticsCampaignLink(
34453
+ c.req.param("shortCode"),
34454
+ c.req.header("referer")
34455
+ );
34456
+ if (!destination)
34457
+ return c.json(
34458
+ {
34459
+ ok: false,
34460
+ code: "campaign_link_not_found",
34461
+ error: "Campaign link not found."
34462
+ },
34463
+ 404
34464
+ );
34465
+ c.header("Cache-Control", "no-store");
34466
+ return c.redirect(destination, 302);
34467
+ });
34468
+ analyticsApp.post("/v1/events", async (c) => {
34469
+ const contentLength = Number(c.req.header("content-length") || 0);
34470
+ if (contentLength > 32 * 1024)
34471
+ return c.json(
34472
+ { accepted: 0, rejected: 1, reasonCodes: ["payload_too_large"] },
34473
+ 413
34474
+ );
34475
+ const parsed = IngestionSchema.safeParse(
34476
+ await c.req.json().catch(() => null)
34477
+ );
34478
+ if (!parsed.success) {
34479
+ return c.json(
34480
+ {
34481
+ accepted: 0,
34482
+ rejected: 1,
34483
+ reasonCodes: ["invalid_payload"],
34484
+ issues: parsed.error.issues
34485
+ },
34486
+ 400
34487
+ );
34488
+ }
34489
+ const result = await ingestAnalyticsEvents({
34490
+ publicPixelId: parsed.data.pixelId,
34491
+ origin: c.req.header("origin") ?? null,
34492
+ events: parsed.data.events,
34493
+ geography: {
34494
+ countryCode: c.req.header("x-vercel-ip-country") || c.req.header("cf-ipcountry") || null,
34495
+ regionCode: c.req.header("x-vercel-ip-country-region") || null
34496
+ }
34497
+ });
34498
+ return c.json(result, 202);
34499
+ });
34500
+ analyticsApp.get("/entitlement", auth3, async (c) => {
34501
+ c.header("Cache-Control", "private, no-store");
34502
+ return c.json({
34503
+ entitlement: await resolveAnalyticsEntitlement(Number(c.get("user").id))
34504
+ });
34505
+ });
34506
+ analyticsApp.post("/entitlement/connect", auth3, async (c) => {
34507
+ c.header("Cache-Control", "private, no-store");
34508
+ try {
34509
+ return c.json({
34510
+ connectUrl: await createThorbitConnectUrl(Number(c.get("user").id))
34511
+ });
34512
+ } catch (error) {
34513
+ return c.json(
34514
+ {
34515
+ error: error instanceof Error ? error.message : "Could not start Thorbit sign-in."
34516
+ },
34517
+ 503
34518
+ );
34519
+ }
34520
+ });
34521
+ analyticsApp.post("/entitlement/api-key", auth3, async (c) => {
34522
+ c.header("Cache-Control", "private, no-store");
34523
+ const parsed = ThorbitApiKeySchema.safeParse(
34524
+ await c.req.json().catch(() => null)
34525
+ );
34526
+ if (!parsed.success)
34527
+ return c.json({ error: "Enter a valid Thorbit API key." }, 400);
34528
+ try {
34529
+ return c.json({
34530
+ entitlement: await connectAnalyticsWithThorbitApiKey(
34531
+ Number(c.get("user").id),
34532
+ parsed.data.apiKey
34533
+ )
34534
+ });
34535
+ } catch (error) {
34536
+ return c.json(
34537
+ {
34538
+ error: error instanceof Error ? error.message : "Thorbit could not verify this API key."
34539
+ },
34540
+ 401
34541
+ );
34542
+ }
34543
+ });
34544
+ analyticsApp.post("/entitlement/callback", auth3, async (c) => {
34545
+ c.header("Cache-Control", "private, no-store");
34546
+ const parsed = ThorbitCallbackSchema.safeParse(
34547
+ await c.req.json().catch(() => null)
34548
+ );
34549
+ if (!parsed.success)
34550
+ return c.json({ error: "Invalid Thorbit connection response." }, 400);
34551
+ try {
34552
+ return c.json({
34553
+ entitlement: await consumeThorbitConnectCallback({
34554
+ userId: Number(c.get("user").id),
34555
+ state: parsed.data.state,
34556
+ assertion: parsed.data.assertion
34557
+ })
34558
+ });
34559
+ } catch (error) {
34560
+ return c.json(
34561
+ {
34562
+ error: error instanceof Error ? error.message : "Could not connect the Thorbit account."
34563
+ },
34564
+ 400
34565
+ );
34566
+ }
34567
+ });
34568
+ analyticsApp.delete("/entitlement", auth3, async (c) => {
34569
+ await disconnectAnalyticsEntitlement(Number(c.get("user").id));
34570
+ return c.json({ ok: true });
34571
+ });
34572
+ analyticsApp.use("/v1/conversions", auth3, entitlementGuard);
34573
+ analyticsApp.use("/sites", auth3, entitlementGuard);
34574
+ analyticsApp.use("/sites/*", auth3, entitlementGuard);
34575
+ analyticsApp.post("/v1/conversions", auth3, async (c) => {
34576
+ const parsed = ConversionSchema.safeParse(
34577
+ await c.req.json().catch(() => null)
34578
+ );
34579
+ if (!parsed.success)
34580
+ return c.json(
34581
+ { error: "Invalid conversion.", issues: parsed.error.issues },
34582
+ 400
34583
+ );
34584
+ const conversion = await createAnalyticsConversion({
34585
+ userId: Number(c.get("user").id),
34586
+ ...parsed.data
34587
+ });
34588
+ return c.json({ ok: true, conversion }, conversion.created ? 201 : 200);
34589
+ });
34590
+ analyticsApp.get("/sites", auth3, async (c) => {
34591
+ const sites = await listAnalyticsSites(Number(c.get("user").id));
34592
+ return c.json({ ok: true, sites });
34593
+ });
34594
+ analyticsApp.post("/sites", auth3, async (c) => {
34595
+ const parsed = SiteInputSchema.safeParse(
34596
+ await c.req.json().catch(() => null)
34597
+ );
34598
+ if (!parsed.success)
34599
+ return c.json(
34600
+ { error: "Invalid Site details.", issues: parsed.error.issues },
34601
+ 400
34602
+ );
34603
+ const site = await createAnalyticsSite({
34604
+ userId: Number(c.get("user").id),
34605
+ ...parsed.data
34606
+ });
34607
+ return c.json({ ok: true, site }, 201);
34608
+ });
34609
+ analyticsApp.delete("/sites/:siteId", auth3, async (c) => {
34610
+ await deleteAnalyticsSite({
34611
+ siteId: c.req.param("siteId"),
34612
+ userId: Number(c.get("user").id)
34613
+ });
34614
+ return c.json({ ok: true });
34615
+ });
34616
+ analyticsApp.patch("/sites/:siteId/business-model", auth3, async (c) => {
34617
+ const parsed = BusinessModelSchema.safeParse(
34618
+ await c.req.json().catch(() => null)
34619
+ );
34620
+ if (!parsed.success)
34621
+ return c.json(
34622
+ { ok: false, error: "Choose a supported business model." },
34623
+ 400
34624
+ );
34625
+ await updateAnalyticsBusinessModel({
34626
+ siteId: c.req.param("siteId"),
34627
+ userId: Number(c.get("user").id),
34628
+ ...parsed.data
34629
+ });
34630
+ return c.json({ ok: true, businessModel: parsed.data.businessModel });
34631
+ });
34632
+ analyticsApp.post("/sites/:siteId/ad-spend", auth3, async (c) => {
34633
+ const parsed = AdSpendSchema.safeParse(await c.req.json().catch(() => null));
34634
+ if (!parsed.success)
34635
+ return c.json(
34636
+ {
34637
+ ok: false,
34638
+ error: "Invalid ad-spend rows.",
34639
+ issues: parsed.error.issues
34640
+ },
34641
+ 400
34642
+ );
34643
+ const result = await upsertAnalyticsAdSpend({
34644
+ siteId: c.req.param("siteId"),
34645
+ userId: Number(c.get("user").id),
34646
+ ...parsed.data
34647
+ });
34648
+ return c.json({ ok: true, result }, 202);
34649
+ });
34650
+ analyticsApp.get("/sites/:siteId/business-metrics", auth3, async (c) => {
34651
+ const url = new URL(c.req.url);
34652
+ const filters = reportFilters(url);
34653
+ validateReportWindow(filters);
34654
+ const data = await analyticsBusinessMetrics(
34655
+ c.req.param("siteId"),
34656
+ Number(c.get("user").id),
34657
+ filters.start,
34658
+ filters.end
34659
+ );
34660
+ return c.json({ ok: true, data, filters });
34661
+ });
34662
+ analyticsApp.get("/sites/:siteId/forecast", auth3, async (c) => {
34663
+ const horizon = Math.min(
34664
+ Math.max(Number(c.req.query("horizonMonths") || 12), 1),
34665
+ 36
34666
+ );
34667
+ const data = await analyticsForecast(
34668
+ c.req.param("siteId"),
34669
+ Number(c.get("user").id),
34670
+ horizon
34671
+ );
34672
+ return c.json({ ok: true, data });
34673
+ });
34674
+ analyticsApp.get("/sites/:siteId/pixels", auth3, async (c) => {
34675
+ const pixels = await listAnalyticsPixels(
34676
+ c.req.param("siteId"),
34677
+ Number(c.get("user").id)
34678
+ );
34679
+ return c.json({
34680
+ ok: true,
34681
+ pixels: pixels.map((pixel) => ({
34682
+ ...pixel,
34683
+ snippet: pixelSnippet(pixel.public_id)
34684
+ }))
34685
+ });
34686
+ });
34687
+ analyticsApp.post("/sites/:siteId/pixels", auth3, async (c) => {
34688
+ const parsed = PixelInputSchema.safeParse(
34689
+ await c.req.json().catch(() => null)
34690
+ );
34691
+ if (!parsed.success)
34692
+ return c.json(
34693
+ { error: "Invalid Pixel details.", issues: parsed.error.issues },
34694
+ 400
34695
+ );
34696
+ const pixel = await createAnalyticsPixel({
34697
+ siteId: c.req.param("siteId"),
34698
+ userId: Number(c.get("user").id),
34699
+ ...parsed.data
34700
+ });
34701
+ return c.json(
34702
+ { ok: true, pixel: { ...pixel, snippet: pixelSnippet(pixel.public_id) } },
34703
+ 201
34704
+ );
34705
+ });
34706
+ analyticsApp.get("/sites/:siteId/campaign-links", auth3, async (c) => {
34707
+ const url = new URL(c.req.url);
34708
+ const data = await listAnalyticsCampaignLinks(
34709
+ c.req.param("siteId"),
34710
+ Number(c.get("user").id),
34711
+ reportPage(url)
34712
+ );
34713
+ return c.json({
34714
+ ok: true,
34715
+ data: {
34716
+ ...data,
34717
+ links: data.links.map((link) => ({
34718
+ ...link,
34719
+ short_url: `${url.origin}/analytics/l/${link.short_code}`
34720
+ }))
34721
+ }
34722
+ });
34723
+ });
34724
+ analyticsApp.get("/sites/:siteId/forms", auth3, async (c) => {
34725
+ const url = new URL(c.req.url);
34726
+ const data = await listAnalyticsForms(
34727
+ c.req.param("siteId"),
34728
+ Number(c.get("user").id),
34729
+ reportPage(url)
34730
+ );
34731
+ return c.json({
34732
+ ok: true,
34733
+ data: {
34734
+ ...data,
34735
+ forms: data.forms.map((form) => ({
34736
+ ...form,
34737
+ snippet: formSnippet(form.public_id),
34738
+ embed_url: `${url.origin}/analytics/forms/${form.public_id}/embed`
34739
+ }))
34740
+ }
34741
+ });
34742
+ });
34743
+ analyticsApp.post("/sites/:siteId/forms", auth3, async (c) => {
34744
+ const parsed = FormInputSchema.safeParse(
34745
+ await c.req.json().catch(() => null)
34746
+ );
34747
+ if (!parsed.success)
34748
+ return c.json(
34749
+ { ok: false, error: "Invalid form.", issues: parsed.error.issues },
34750
+ 400
34751
+ );
34752
+ const form = await createAnalyticsForm({
34753
+ siteId: c.req.param("siteId"),
34754
+ userId: Number(c.get("user").id),
34755
+ ...parsed.data
34756
+ });
34757
+ return c.json(
34758
+ {
34759
+ ok: true,
34760
+ form: {
34761
+ ...form,
34762
+ snippet: formSnippet(form.public_id),
34763
+ embed_url: `${new URL(c.req.url).origin}/analytics/forms/${form.public_id}/embed`
34764
+ }
34765
+ },
34766
+ 201
34767
+ );
34768
+ });
34769
+ analyticsApp.get("/sites/:siteId/crm-imports", auth3, async (c) => {
34770
+ const data = await listAnalyticsCrmImports(
34771
+ c.req.param("siteId"),
34772
+ Number(c.get("user").id),
34773
+ reportPage(new URL(c.req.url))
34774
+ );
34775
+ return c.json({ ok: true, data });
34776
+ });
34777
+ analyticsApp.get("/sites/:siteId/people", auth3, async (c) => {
34778
+ const data = await listAnalyticsPeople(
34779
+ c.req.param("siteId"),
34780
+ Number(c.get("user").id),
34781
+ reportPage(new URL(c.req.url))
34782
+ );
34783
+ return c.json({ ok: true, data });
34784
+ });
34785
+ analyticsApp.get("/sites/:siteId/people/:personId/journey", auth3, async (c) => {
34786
+ const data = await getAnalyticsPersonJourney(
34787
+ c.req.param("siteId"),
34788
+ c.req.param("personId"),
34789
+ Number(c.get("user").id),
34790
+ reportPage(new URL(c.req.url))
34791
+ );
34792
+ return c.json({ ok: true, data });
34793
+ });
34794
+ analyticsApp.post("/sites/:siteId/crm-imports", auth3, async (c) => {
34795
+ const parsed = CrmImportSchema.safeParse(
34796
+ await c.req.json().catch(() => null)
34797
+ );
34798
+ if (!parsed.success)
34799
+ return c.json(
34800
+ { ok: false, error: "Invalid CRM import.", issues: parsed.error.issues },
34801
+ 400
34802
+ );
34803
+ const csv = Papa2.parse(parsed.data.csv, {
34804
+ header: true,
34805
+ skipEmptyLines: "greedy",
34806
+ transformHeader: (header) => header.trim()
34807
+ });
34808
+ if (csv.errors.length)
34809
+ return c.json(
34810
+ {
34811
+ ok: false,
34812
+ error: "CSV could not be parsed.",
34813
+ issues: csv.errors.slice(0, 10)
34814
+ },
34815
+ 400
34816
+ );
34817
+ if (csv.data.length > 2500)
34818
+ return c.json(
34819
+ { ok: false, error: "Import files are limited to 2,500 rows per batch." },
34820
+ 400
34821
+ );
34822
+ const pick = (row, header) => header ? String(row[header] ?? "").trim() : "";
34823
+ const queued = [];
34824
+ const identities = [];
34825
+ let rejectedCount = 0;
34826
+ for (const [index, row] of csv.data.entries()) {
34827
+ const email = pick(row, parsed.data.mapping.email).toLowerCase();
34828
+ const firstName = pick(row, parsed.data.mapping.firstName);
34829
+ const lastName = pick(row, parsed.data.mapping.lastName);
34830
+ const explicitName = pick(row, parsed.data.mapping.name);
34831
+ const fullName = explicitName || [firstName, lastName].filter(Boolean).join(" ");
34832
+ const phone = pick(row, parsed.data.mapping.phone);
34833
+ const company = pick(row, parsed.data.mapping.company);
34834
+ const externalId = pick(row, parsed.data.mapping.externalId);
34835
+ if (!email && !phone && !externalId && !fullName) {
34836
+ rejectedCount += 1;
34837
+ continue;
34838
+ }
34839
+ const digest2 = createHash15("sha256").update(
34840
+ `${parsed.data.sourceSystem}:${externalId || email || phone || `${fullName}:${index}`}`
34841
+ ).digest("hex").slice(0, 16);
34842
+ const path5 = `Leads/person-${digest2}`;
34843
+ const crmPersonRef = `People::${path5}`;
34844
+ const props = {
34845
+ type: "person",
34846
+ status: "active",
34847
+ person_name: fullName || email || phone || "Imported contact",
34848
+ ...firstName ? { first_name: firstName } : {},
34849
+ ...lastName ? { last_name: lastName } : {},
34850
+ ...email ? { email } : {},
34851
+ ...phone ? { phone, text_phone: phone } : {},
34852
+ ...company ? { company } : {},
34853
+ ...externalId ? { external_id: externalId } : {},
34854
+ import_fields: row,
34855
+ source_type: "crm_csv_import",
34856
+ source_ref: `${parsed.data.sourceSystem}:${digest2}`,
34857
+ summary: `Imported from ${parsed.data.sourceSystem}`,
34858
+ embedding_summary: `${fullName || "Contact"} imported from ${parsed.data.sourceSystem}`,
34859
+ related: [],
34860
+ related_vault_notes: [],
34861
+ embed: true,
34862
+ embed_priority: "normal"
34863
+ };
34864
+ const title = fullName || email || phone || "Imported contact";
34865
+ const payload = {
34866
+ vault: "People",
34867
+ path: path5,
34868
+ title,
34869
+ content: `# ${title}
34870
+
34871
+ ## Identity
34872
+ Imported from ${parsed.data.sourceSystem}.
34873
+
34874
+ ## Relationship
34875
+ Review and qualify this contact.
34876
+
34877
+ ## Known Context
34878
+ Imported through the Site Analytics CRM workflow.
34879
+
34880
+ ## Interaction History
34881
+ - ${(/* @__PURE__ */ new Date()).toISOString()}: Imported from ${parsed.data.filename}.
34882
+
34883
+ ## Open Loops
34884
+ - Verify attribution and follow-up status.`,
34885
+ props
34886
+ };
34887
+ queued.push({
34888
+ crmPersonRef,
34889
+ payloadCiphertext: encryptMemoryKey(JSON.stringify(payload))
34890
+ });
34891
+ identities.push({
34892
+ crmPersonRef,
34893
+ email: email || void 0,
34894
+ phone: phone || void 0
34895
+ });
34896
+ }
34897
+ const imported = await createAnalyticsCrmImport({
34898
+ siteId: c.req.param("siteId"),
34899
+ userId: Number(c.get("user").id),
34900
+ sourceSystem: parsed.data.sourceSystem,
34901
+ filename: parsed.data.filename,
34902
+ rows: queued,
34903
+ rejectedCount
34904
+ });
34905
+ for (const identity of identities) {
34906
+ await linkAnalyticsFormIdentity({
34907
+ siteId: c.req.param("siteId"),
34908
+ ...identity
34909
+ });
34910
+ }
34911
+ return c.json({ ok: true, import: imported }, 202);
34912
+ });
34913
+ analyticsApp.get("/sites/:siteId/activation-destinations", auth3, async (c) => {
34914
+ const destinations = await listAnalyticsActivationDestinations(
34915
+ c.req.param("siteId"),
34916
+ Number(c.get("user").id)
34917
+ );
34918
+ return c.json({ ok: true, destinations });
34919
+ });
34920
+ analyticsApp.post("/sites/:siteId/activation-destinations", auth3, async (c) => {
34921
+ const parsed = ActivationDestinationSchema.safeParse(
34922
+ await c.req.json().catch(() => null)
34923
+ );
34924
+ if (!parsed.success)
34925
+ return c.json(
34926
+ {
34927
+ ok: false,
34928
+ error: "Invalid activation destination.",
34929
+ issues: parsed.error.issues
34930
+ },
34931
+ 400
34932
+ );
34933
+ const destination = await createAnalyticsActivationDestination({
34934
+ siteId: c.req.param("siteId"),
34935
+ userId: Number(c.get("user").id),
34936
+ ...parsed.data
34937
+ });
34938
+ return c.json({ ok: true, destination }, 201);
34939
+ });
34940
+ analyticsApp.delete(
34941
+ "/sites/:siteId/activation-destinations/:destinationId",
34942
+ auth3,
34943
+ async (c) => {
34944
+ await archiveAnalyticsActivationDestination({
34945
+ siteId: c.req.param("siteId"),
34946
+ destinationId: c.req.param("destinationId"),
34947
+ userId: Number(c.get("user").id)
34948
+ });
34949
+ return c.json({ ok: true });
34950
+ }
34951
+ );
34952
+ analyticsApp.post("/sites/:siteId/campaign-links", auth3, async (c) => {
34953
+ const parsed = CampaignLinkSchema.safeParse(
34954
+ await c.req.json().catch(() => null)
34955
+ );
34956
+ if (!parsed.success)
34957
+ return c.json(
34958
+ {
34959
+ ok: false,
34960
+ error: "Invalid campaign link.",
34961
+ issues: parsed.error.issues
34962
+ },
34963
+ 400
34964
+ );
34965
+ const link = await createAnalyticsCampaignLink({
34966
+ siteId: c.req.param("siteId"),
34967
+ userId: Number(c.get("user").id),
34968
+ ...parsed.data
34969
+ });
34970
+ return c.json(
34971
+ {
34972
+ ok: true,
34973
+ link: {
34974
+ ...link,
34975
+ short_url: `${new URL(c.req.url).origin}/analytics/l/${link.short_code}`
34976
+ }
34977
+ },
34978
+ 201
34979
+ );
34980
+ });
34981
+ analyticsApp.delete(
34982
+ "/sites/:siteId/campaign-links/:linkId",
34983
+ auth3,
34984
+ async (c) => {
34985
+ await archiveAnalyticsCampaignLink({
34986
+ siteId: c.req.param("siteId"),
34987
+ linkId: c.req.param("linkId"),
34988
+ userId: Number(c.get("user").id)
34989
+ });
34990
+ return c.json({ ok: true });
34991
+ }
34992
+ );
34993
+ analyticsApp.patch("/sites/:siteId/pixels/:pixelId", auth3, async (c) => {
34994
+ const parsed = PixelUpdateSchema.safeParse(
34995
+ await c.req.json().catch(() => null)
34996
+ );
34997
+ if (!parsed.success)
34998
+ return c.json(
34999
+ { error: "Invalid Pixel changes.", issues: parsed.error.issues },
35000
+ 400
35001
+ );
35002
+ await updateAnalyticsPixel({
35003
+ siteId: c.req.param("siteId"),
35004
+ pixelId: c.req.param("pixelId"),
35005
+ userId: Number(c.get("user").id),
35006
+ ...parsed.data
35007
+ });
35008
+ return c.json({ ok: true });
35009
+ });
35010
+ analyticsApp.post(
35011
+ "/sites/:siteId/pixels/:pixelId/domains/:hostname/approve",
35012
+ auth3,
35013
+ async (c) => {
35014
+ await setAnalyticsPixelDomainState({
35015
+ siteId: c.req.param("siteId"),
35016
+ pixelId: c.req.param("pixelId"),
35017
+ userId: Number(c.get("user").id),
35018
+ hostname: c.req.param("hostname"),
35019
+ state: "approved"
35020
+ });
35021
+ return c.json({ ok: true });
35022
+ }
35023
+ );
35024
+ analyticsApp.post(
35025
+ "/sites/:siteId/pixels/:pixelId/domains/:hostname/reject",
35026
+ auth3,
35027
+ async (c) => {
35028
+ await setAnalyticsPixelDomainState({
35029
+ siteId: c.req.param("siteId"),
35030
+ pixelId: c.req.param("pixelId"),
35031
+ userId: Number(c.get("user").id),
35032
+ hostname: c.req.param("hostname"),
35033
+ state: "rejected"
35034
+ });
35035
+ return c.json({ ok: true });
35036
+ }
35037
+ );
35038
+ for (const [path5, load] of [
35039
+ ["/sites/:siteId/overview", analyticsOverview],
35040
+ ["/sites/:siteId/timeseries", analyticsTimeseries],
35041
+ ["/sites/:siteId/acquisition", analyticsAcquisition],
35042
+ ["/sites/:siteId/content", analyticsContent],
35043
+ ["/sites/:siteId/events", analyticsEventCounts]
35044
+ ]) {
35045
+ analyticsApp.get(path5, auth3, async (c) => {
35046
+ const url = new URL(c.req.url);
35047
+ const filters = reportFilters(url);
35048
+ validateReportWindow(filters);
35049
+ const paginated = path5.endsWith("/acquisition") || path5.endsWith("/content") || path5.endsWith("/events");
35050
+ const data = await load(
35051
+ c.req.param("siteId"),
35052
+ Number(c.get("user").id),
35053
+ filters,
35054
+ paginated ? reportPage(url) : void 0
35055
+ );
35056
+ return c.json({ ok: true, data, filters });
35057
+ });
35058
+ }
35059
+ analyticsApp.get("/sites/:siteId/dashboard", auth3, async (c) => {
35060
+ const url = new URL(c.req.url);
35061
+ const filters = reportFilters(url);
35062
+ validateReportWindow(filters);
35063
+ const siteId = c.req.param("siteId");
35064
+ const userId = Number(c.get("user").id);
35065
+ const [
35066
+ overview,
35067
+ timeseries,
35068
+ acquisition,
35069
+ devices,
35070
+ activity,
35071
+ geography,
35072
+ content,
35073
+ conversions
35074
+ ] = await Promise.all([
35075
+ analyticsOverview(siteId, userId, filters),
35076
+ analyticsTimeseries(siteId, userId, filters),
35077
+ analyticsAcquisition(siteId, userId, filters, { limit: 25 }),
35078
+ analyticsDimensions(siteId, userId, filters, "device", { limit: 10 }),
35079
+ analyticsDimensions(siteId, userId, filters, "weekday_hour", {
35080
+ limit: 100
35081
+ }),
35082
+ analyticsDimensions(siteId, userId, filters, "region", { limit: 60 }),
35083
+ analyticsContent(siteId, userId, filters, { limit: 8 }),
35084
+ analyticsConversions(siteId, userId, filters, { limit: 8 })
35085
+ ]);
35086
+ let comparison = null;
35087
+ if (url.searchParams.get("compare") === "1") {
35088
+ const start = Date.parse(filters.start);
35089
+ const end = Date.parse(filters.end);
35090
+ const duration = end - start;
35091
+ const previousFilters = {
35092
+ ...filters,
35093
+ start: new Date(start - duration).toISOString(),
35094
+ end: new Date(start).toISOString()
35095
+ };
35096
+ const [previousOverview, previousTimeseries] = await Promise.all([
35097
+ analyticsOverview(siteId, userId, previousFilters),
35098
+ analyticsTimeseries(siteId, userId, previousFilters)
35099
+ ]);
35100
+ comparison = { overview: previousOverview, timeseries: previousTimeseries };
35101
+ }
35102
+ return c.json({
35103
+ ok: true,
35104
+ data: {
35105
+ overview,
35106
+ timeseries,
35107
+ acquisition,
35108
+ devices,
35109
+ activity,
35110
+ geography,
35111
+ content,
35112
+ conversions,
35113
+ comparison
35114
+ },
35115
+ filters
35116
+ });
35117
+ });
35118
+ analyticsApp.get("/sites/:siteId/dimensions", auth3, async (c) => {
35119
+ const url = new URL(c.req.url);
35120
+ const filters = reportFilters(url);
35121
+ validateReportWindow(filters);
35122
+ const dimension = url.searchParams.get("dimension") || "device";
35123
+ if (!["device", "source", "country", "region", "weekday_hour"].includes(
35124
+ dimension
35125
+ )) {
35126
+ return c.json(
35127
+ {
35128
+ ok: false,
35129
+ code: "invalid_dimension",
35130
+ error: "Choose a supported dimension."
35131
+ },
35132
+ 400
35133
+ );
35134
+ }
35135
+ const data = await analyticsDimensions(
35136
+ c.req.param("siteId"),
35137
+ Number(c.get("user").id),
35138
+ filters,
35139
+ dimension,
35140
+ reportPage(url)
35141
+ );
35142
+ return c.json({ ok: true, data, filters });
35143
+ });
35144
+ analyticsApp.get("/sites/:siteId/conversions", auth3, async (c) => {
35145
+ const url = new URL(c.req.url);
35146
+ const filters = reportFilters(url);
35147
+ validateReportWindow(filters);
35148
+ const data = await analyticsConversions(
35149
+ c.req.param("siteId"),
35150
+ Number(c.get("user").id),
35151
+ filters,
35152
+ reportPage(url)
35153
+ );
35154
+ return c.json({ ok: true, data, filters });
35155
+ });
35156
+ analyticsApp.get("/sites/:siteId/paths", auth3, async (c) => {
35157
+ const url = new URL(c.req.url);
35158
+ const filters = reportFilters(url);
35159
+ validateReportWindow(filters);
35160
+ const data = await analyticsPaths(
35161
+ c.req.param("siteId"),
35162
+ Number(c.get("user").id),
35163
+ filters,
35164
+ reportPage(url)
35165
+ );
35166
+ return c.json({ ok: true, data, filters });
35167
+ });
35168
+ analyticsApp.get("/sites/:siteId/channel-breakdown", auth3, async (c) => {
35169
+ const filters = reportFilters(new URL(c.req.url));
35170
+ const data = await analyticsChannelBreakdown(
35171
+ c.req.param("siteId"),
35172
+ Number(c.get("user").id),
35173
+ filters
35174
+ );
35175
+ return c.json({ ok: true, data });
35176
+ });
35177
+ analyticsApp.get("/sites/:siteId/health", auth3, async (c) => {
35178
+ const health = await analyticsHealth(
35179
+ c.req.param("siteId"),
35180
+ Number(c.get("user").id)
35181
+ );
35182
+ return c.json({ ok: true, health }, 200, { "Cache-Control": "no-store" });
35183
+ });
35184
+ analyticsApp.post("/sites/:siteId/exports", auth3, async (c) => {
35185
+ const parsed = ExportSchema.safeParse(await c.req.json().catch(() => ({})));
35186
+ if (!parsed.success)
35187
+ return c.json(
35188
+ {
35189
+ ok: false,
35190
+ error: "Invalid export request.",
35191
+ issues: parsed.error.issues
35192
+ },
35193
+ 400
35194
+ );
35195
+ const filters = reportFilters(new URL(c.req.url));
35196
+ validateReportWindow(filters);
35197
+ const artifact = await createAnalyticsExport({
35198
+ siteId: c.req.param("siteId"),
35199
+ userId: Number(c.get("user").id),
35200
+ filters,
35201
+ ...parsed.data,
35202
+ idempotencyKey: c.req.header("idempotency-key") || parsed.data.idempotencyKey
35203
+ });
35204
+ return c.json({ ok: true, artifact }, 201);
35205
+ });
35206
+ analyticsApp.onError((error, c) => {
35207
+ if (error instanceof AnalyticsRepositoryError) {
35208
+ return c.json(
35209
+ { ok: false, code: error.code, error: error.message },
35210
+ error.status
35211
+ );
35212
+ }
35213
+ console.error(
35214
+ "[analytics]",
35215
+ error instanceof Error ? error.message : String(error)
35216
+ );
35217
+ return c.json(
35218
+ {
35219
+ ok: false,
35220
+ code: "analytics_internal_error",
35221
+ error: "Site Analytics could not complete the request."
35222
+ },
35223
+ 500
35224
+ );
35225
+ });
35226
+
35227
+ // src/api/analytics-delivery.ts
35228
+ function safeDeliveryError(error) {
35229
+ if (!error) return "memory_write_failed";
35230
+ if (error.includes("configured") || error.includes("credential")) return "memory_credential_unavailable";
35231
+ if (error.includes("401") || error.includes("403")) return "memory_authorization_failed";
35232
+ if (error.includes("429")) return "memory_rate_limited";
35233
+ return "memory_write_failed";
35234
+ }
35235
+ async function deliverPendingAnalyticsForms(limit = 10) {
35236
+ const jobs = await claimAnalyticsFormDeliveryJobs(limit);
35237
+ let delivered = 0;
35238
+ let deferred = 0;
35239
+ for (const job of jobs) {
35240
+ const payloadText = decryptMemoryKey(job.payloadCiphertext);
35241
+ const user = await getUserById(job.ownerUserId);
35242
+ if (!payloadText || !user) {
35243
+ await deferAnalyticsFormDelivery(job.id, job.attempts, payloadText ? "owner_not_found" : "payload_unreadable");
35244
+ deferred += 1;
35245
+ continue;
35246
+ }
35247
+ let payload;
35248
+ try {
35249
+ payload = JSON.parse(payloadText);
35250
+ } catch {
35251
+ await deferAnalyticsFormDelivery(job.id, job.attempts, "payload_invalid");
35252
+ deferred += 1;
35253
+ continue;
35254
+ }
35255
+ const { key, error } = await getOrCreateUserMemoryKey(user);
35256
+ const result = key ? await memoryCall("putTool", payload, key) : { ok: false, error };
35257
+ if (result.ok) {
35258
+ await completeAnalyticsFormDelivery(job.id, job.submissionId);
35259
+ delivered += 1;
35260
+ } else {
35261
+ await deferAnalyticsFormDelivery(job.id, job.attempts, safeDeliveryError(result.error));
35262
+ deferred += 1;
35263
+ }
35264
+ }
35265
+ return { claimed: jobs.length, delivered, deferred };
35266
+ }
35267
+
35268
+ // src/api/scheduled-result-routes.ts
35269
+ import { Hono as Hono33 } from "hono";
35270
+ import { z as z35 } from "zod";
33567
35271
 
33568
35272
  // src/api/scheduled-artifact-owner.ts
33569
- import { createHash as createHash14 } from "crypto";
35273
+ import { createHash as createHash16 } from "crypto";
33570
35274
  function scheduledArtifactOwnerIdForApiKey(apiKey) {
33571
- return createHash14("sha256").update(apiKey).digest("hex").slice(0, 24);
35275
+ return createHash16("sha256").update(apiKey).digest("hex").slice(0, 24);
33572
35276
  }
33573
35277
 
33574
35278
  // src/api/scheduled-run-view-links.ts
33575
- import { createHash as createHash15, randomBytes as randomBytes3, randomUUID as randomUUID17 } from "crypto";
35279
+ import { createHash as createHash17, randomBytes as randomBytes4, randomUUID as randomUUID17 } from "crypto";
33576
35280
  var schemaReady3 = false;
33577
35281
  async function ensureScheduledRunViewLinksSchema() {
33578
35282
  if (schemaReady3) return;
@@ -33585,7 +35289,7 @@ async function ensureScheduledRunViewLinksSchema() {
33585
35289
  artifact_id TEXT NOT NULL,
33586
35290
  title TEXT NOT NULL,
33587
35291
  summary TEXT NOT NULL,
33588
- renderer_key TEXT NOT NULL CHECK (renderer_key IN ('editorial_reading_room_v1','personal_authority_v1')),
35292
+ renderer_key TEXT NOT NULL CHECK (renderer_key IN ('editorial_reading_room_v1','personal_authority_v1','newsroom_publisher_v1')),
33589
35293
  token_hash TEXT NOT NULL UNIQUE,
33590
35294
  expires_at TEXT NOT NULL,
33591
35295
  revoked_at TEXT,
@@ -33597,22 +35301,22 @@ async function ensureScheduledRunViewLinksSchema() {
33597
35301
  args: []
33598
35302
  });
33599
35303
  const createSql = String(schema.rows[0]?.sql ?? "");
33600
- if (createSql.includes("renderer_key = 'editorial_reading_room_v1'")) {
35304
+ if (!createSql.includes("newsroom_publisher_v1")) {
33601
35305
  await db.batch([
33602
- `DROP TABLE IF EXISTS scheduled_run_view_links_v2`,
33603
- `CREATE TABLE scheduled_run_view_links_v2 (
35306
+ `DROP TABLE IF EXISTS scheduled_run_view_links_v3`,
35307
+ `CREATE TABLE scheduled_run_view_links_v3 (
33604
35308
  share_id TEXT PRIMARY KEY, owner_id TEXT NOT NULL, run_id TEXT NOT NULL,
33605
35309
  artifact_id TEXT NOT NULL, title TEXT NOT NULL, summary TEXT NOT NULL,
33606
- renderer_key TEXT NOT NULL CHECK (renderer_key IN ('editorial_reading_room_v1','personal_authority_v1')),
35310
+ renderer_key TEXT NOT NULL CHECK (renderer_key IN ('editorial_reading_room_v1','personal_authority_v1','newsroom_publisher_v1')),
33607
35311
  token_hash TEXT NOT NULL UNIQUE, expires_at TEXT NOT NULL, revoked_at TEXT,
33608
35312
  created_at TEXT NOT NULL
33609
35313
  )`,
33610
- `INSERT INTO scheduled_run_view_links_v2
35314
+ `INSERT INTO scheduled_run_view_links_v3
33611
35315
  SELECT share_id, owner_id, run_id, artifact_id, title, summary, renderer_key,
33612
35316
  token_hash, expires_at, revoked_at, created_at
33613
35317
  FROM scheduled_run_view_links`,
33614
35318
  `DROP TABLE scheduled_run_view_links`,
33615
- `ALTER TABLE scheduled_run_view_links_v2 RENAME TO scheduled_run_view_links`
35319
+ `ALTER TABLE scheduled_run_view_links_v3 RENAME TO scheduled_run_view_links`
33616
35320
  ], "write");
33617
35321
  }
33618
35322
  await db.execute(`
@@ -33626,7 +35330,7 @@ async function ensureScheduledRunViewLinksSchema() {
33626
35330
  schemaReady3 = true;
33627
35331
  }
33628
35332
  function tokenHash2(token4) {
33629
- return createHash15("sha256").update(token4).digest("hex");
35333
+ return createHash17("sha256").update(token4).digest("hex");
33630
35334
  }
33631
35335
  function mapRow(row) {
33632
35336
  return {
@@ -33636,7 +35340,7 @@ function mapRow(row) {
33636
35340
  artifactId: String(row.artifact_id),
33637
35341
  title: String(row.title),
33638
35342
  summary: String(row.summary),
33639
- rendererKey: row.renderer_key === "personal_authority_v1" ? "personal_authority_v1" : "editorial_reading_room_v1",
35343
+ rendererKey: row.renderer_key === "newsroom_publisher_v1" ? "newsroom_publisher_v1" : row.renderer_key === "personal_authority_v1" ? "personal_authority_v1" : "editorial_reading_room_v1",
33640
35344
  tokenHash: String(row.token_hash),
33641
35345
  expiresAt: String(row.expires_at),
33642
35346
  revokedAt: row.revoked_at == null ? null : String(row.revoked_at),
@@ -33646,7 +35350,7 @@ function mapRow(row) {
33646
35350
  async function createScheduledRunViewLink(input) {
33647
35351
  await ensureScheduledRunViewLinksSchema();
33648
35352
  const now = input.now ?? /* @__PURE__ */ new Date();
33649
- const token4 = randomBytes3(32).toString("base64url");
35353
+ const token4 = randomBytes4(32).toString("base64url");
33650
35354
  const record = {
33651
35355
  shareId: randomUUID17(),
33652
35356
  ownerId: input.ownerId,
@@ -33713,8 +35417,8 @@ async function revokeScheduledRunViewLink(ownerId, runId, shareId, now = /* @__P
33713
35417
  }
33714
35418
 
33715
35419
  // src/personal-authority/render.ts
33716
- import { createHash as createHash16 } from "crypto";
33717
- function escapeHtml6(value) {
35420
+ import { createHash as createHash18 } from "crypto";
35421
+ function escapeHtml7(value) {
33718
35422
  return value.replace(/[&<>"']/g, (character) => ({
33719
35423
  "&": "&amp;",
33720
35424
  "<": "&lt;",
@@ -33725,13 +35429,13 @@ function escapeHtml6(value) {
33725
35429
  }
33726
35430
  function safeHref(value) {
33727
35431
  const trimmed = value.trim();
33728
- return /^(?:https?:|mailto:|tel:|#|\/)/i.test(trimmed) ? escapeHtml6(trimmed) : "#";
35432
+ return /^(?:https?:|mailto:|tel:|#|\/)/i.test(trimmed) ? escapeHtml7(trimmed) : "#";
33729
35433
  }
33730
35434
  function initials(name) {
33731
35435
  return name.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase()).join("") || "PA";
33732
35436
  }
33733
35437
  function action(action2, secondary = false) {
33734
- return `<a class="button${secondary ? " button--secondary" : ""}" href="${safeHref(action2.href)}">${escapeHtml6(action2.label)} <span aria-hidden="true">\u2197</span></a>`;
35438
+ return `<a class="button${secondary ? " button--secondary" : ""}" href="${safeHref(action2.href)}">${escapeHtml7(action2.label)} <span aria-hidden="true">\u2197</span></a>`;
33735
35439
  }
33736
35440
  function section(enabled, html) {
33737
35441
  return enabled ? html : "";
@@ -33740,7 +35444,7 @@ function renderPersonalAuthorityV1(input, config, generatedAt) {
33740
35444
  const flags = config.featureFlags;
33741
35445
  const brand = config.brandName || input.name;
33742
35446
  const generatedAtIso = generatedAt.toISOString();
33743
- const social = flags.socialLinks && input.socialLinks.length > 0 ? `<nav class="social" aria-label="Professional profiles">${input.socialLinks.map((link) => `<a href="${safeHref(link.href)}">${escapeHtml6(link.label)}</a>`).join("")}</nav>` : "";
35447
+ const social = flags.socialLinks && input.socialLinks.length > 0 ? `<nav class="social" aria-label="Professional profiles">${input.socialLinks.map((link) => `<a href="${safeHref(link.href)}">${escapeHtml7(link.label)}</a>`).join("")}</nav>` : "";
33744
35448
  const navigation = flags.navigation ? `<button class="menu" type="button" aria-expanded="false" aria-controls="nav">Menu</button><nav id="nav" class="nav" aria-label="Primary navigation"><a href="#about">About</a><a href="#services">Work</a><a href="#proof">Proof</a><a href="#contact">Contact</a></nav>` : "";
33745
35449
  const authority = input.authority;
33746
35450
  const consultation = input.consultation;
@@ -33752,32 +35456,144 @@ function renderPersonalAuthorityV1(input, config, generatedAt) {
33752
35456
  <html lang="en" data-theme="${config.theme}">
33753
35457
  <head>
33754
35458
  <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
33755
- <meta name="description" content="${escapeHtml6(input.summary)}"><title>${escapeHtml6(input.name)} \u2014 ${escapeHtml6(input.professionalTitle)}</title>
35459
+ <meta name="description" content="${escapeHtml7(input.summary)}"><title>${escapeHtml7(input.name)} \u2014 ${escapeHtml7(input.professionalTitle)}</title>
33756
35460
  <style>
33757
35461
  :root{--blue:#2f66eb;--blue-soft:#dbe6ff;--ink:#050708;--paper:#f7f8fb;--white:#fff;--gray:#9ca5b5;--line:#27303b;--serif:Georgia,'Times New Roman',serif;--sans:Arial,Helvetica,sans-serif}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--ink);color:var(--white);font-family:var(--sans);line-height:1.55}a{color:inherit}.skip{position:absolute;left:-9999px}.skip:focus{left:16px;top:16px;z-index:9;background:#fff;color:#000;padding:10px}.header{height:88px;display:flex;align-items:center;justify-content:space-between;padding:0 clamp(20px,4vw,72px);border-bottom:1px solid var(--line);position:relative;z-index:3;background:rgba(5,7,8,.96)}.brand{display:flex;align-items:center;gap:12px;text-decoration:none;font-family:var(--serif);font-size:24px}.mark{width:40px;height:40px;border-radius:50%;display:grid;place-items:center;background:var(--blue);font-family:var(--sans);font-weight:700}.nav{display:flex;align-items:center;gap:28px}.nav a{text-decoration:none;color:#c8ced8;font-size:14px}.nav a:hover{color:#fff}.menu{display:none;background:transparent;color:#fff;border:1px solid #5b6471;padding:10px 14px}.hero{display:grid;grid-template-columns:minmax(0,.9fr) minmax(0,1.25fr);min-height:760px;border-bottom:3px solid var(--blue)}.portrait{display:grid;place-items:end center;background:radial-gradient(circle at 50% 38%,#273243 0,#11161d 38%,#060708 70%);overflow:hidden}.portrait-card{width:min(82%,560px);aspect-ratio:4/5;display:grid;place-items:center;align-self:end;border:1px solid #263142;border-bottom:0;background:linear-gradient(145deg,#252d38,#0b0d10 58%);font-family:var(--serif);font-size:clamp(80px,13vw,180px);color:#d9e4ff;text-shadow:0 0 42px rgba(47,102,235,.5)}.hero-copy{padding:clamp(44px,7vw,110px) clamp(24px,6vw,100px);display:flex;flex-direction:column;justify-content:center;background:linear-gradient(135deg,#080a0d,#101318)}h1,h2,h3,p{margin-top:0}.hero h1{font-family:var(--serif);font-size:clamp(58px,8vw,116px);line-height:.92;font-weight:400;color:var(--blue);margin-bottom:24px}.role{font-family:var(--serif);font-size:clamp(28px,3.8vw,52px);line-height:1.05;border-left:5px solid var(--blue);padding-left:22px;max-width:820px}.summary{color:#c6cbd3;font-size:clamp(17px,1.5vw,22px);max-width:760px}.actions{display:flex;gap:12px;flex-wrap:wrap;margin:24px 0}.button{display:inline-flex;align-items:center;justify-content:space-between;gap:28px;min-height:58px;padding:0 26px;background:var(--blue);border:1px solid var(--blue);text-decoration:none;font-weight:700}.button--secondary{background:transparent;border-color:#5c6572}.social{display:flex;gap:10px;flex-wrap:wrap}.social a{border:1px solid #39414d;border-radius:999px;padding:8px 13px;text-decoration:none;color:#d8dce4;font-size:13px}.light{background:var(--paper);color:#111722;padding:clamp(70px,10vw,150px) clamp(20px,6vw,90px)}.light h2,.section h2{font-family:var(--serif);font-size:clamp(42px,6vw,82px);line-height:1.02;font-weight:400;max-width:1080px}.light p{max-width:920px;color:#4e596a;font-size:18px}.section{padding:clamp(70px,9vw,130px) clamp(20px,6vw,90px);border-bottom:1px solid var(--line)}.section-head{max-width:920px;margin-bottom:46px}.eyebrow{text-transform:uppercase;letter-spacing:.18em;font-size:12px;color:#aab2bf}.consult{display:grid;grid-template-columns:1.3fr .7fr;gap:50px;background:linear-gradient(130deg,#060708,#111721)}.accent-card{min-height:360px;border:1px solid #2a3545;background:linear-gradient(145deg,rgba(47,102,235,.22),transparent 55%);display:grid;place-items:center;font-family:var(--serif);font-size:96px;color:var(--blue)}.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}.card{border:1px solid #323a46;padding:26px;min-height:220px;background:#0b0e12}.card h3{font-family:var(--serif);font-size:30px}.card p{color:#b9c0ca}.learning{background:#020303}.topic-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1px;background:#29313c;border:1px solid #29313c}.topic{background:#050708;padding:30px}.topic strong{display:block;color:var(--blue);text-transform:uppercase;font-size:11px;letter-spacing:.15em}.topic h3{font-family:var(--serif);font-size:30px;margin:8px 0}.topic p{color:#aeb6c1}.proof{background:linear-gradient(180deg,#0a0d11,#050607)}.quotes{display:flex;gap:14px;overflow:auto;scroll-snap-type:x mandatory;padding-bottom:12px}.quote{min-width:min(420px,86vw);scroll-snap-align:start;border:1px solid #343d49;padding:30px;background:#0c1015}.quote q{font-family:var(--serif);font-size:30px;line-height:1.25}.quote span{display:block;margin-top:24px;color:#aeb5c0;font-size:12px;text-transform:uppercase;letter-spacing:.12em}.cta{text-align:center;border-top:3px solid var(--blue);background:radial-gradient(circle at 50% 0,rgba(47,102,235,.25),transparent 50%)}.cta h2{margin-left:auto;margin-right:auto}.footer{display:grid;grid-template-columns:1fr auto;gap:40px;padding:46px clamp(20px,6vw,90px);color:#abb3bf}.footer strong{color:#fff;font-family:var(--serif);font-size:26px}.generated{font-size:11px;color:#737d8b;margin-top:10px}html[data-theme='slate']{--blue:#7f95b8;--blue-soft:#dfe6ef}html[data-theme='monochrome']{--blue:#fff;--blue-soft:#e7e7e7}.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)}
33758
35462
  @media(max-width:760px){.header{height:78px}.menu{display:block}.nav{display:none;position:absolute;left:0;right:0;top:77px;padding:18px 20px;background:#090b0e;border-bottom:1px solid #303844;flex-direction:column;align-items:stretch}.nav.open{display:flex}.hero{grid-template-columns:1fr;min-height:0}.portrait{min-height:390px}.portrait-card{width:72%;max-height:360px}.hero-copy{padding:48px 20px 66px}.hero h1{font-size:64px}.role{font-size:34px}.actions{display:grid}.button{width:100%}.consult{grid-template-columns:1fr}.accent-card{min-height:220px}.grid,.topic-grid{grid-template-columns:1fr}.light,.section{padding:64px 20px}.light h2,.section h2{font-size:46px}.footer{grid-template-columns:1fr}}
33759
35463
  </style></head>
33760
35464
  <body><a class="skip" href="#main">Skip to main content</a>
33761
- <header class="header"><a class="brand" href="#top"><span class="mark">${escapeHtml6(initials(input.name).slice(0, 1))}</span><strong>${escapeHtml6(brand)}</strong></a>${navigation}</header>
33762
- <main id="main"><section class="hero" id="top"><div class="portrait" aria-label="Portrait placeholder"><div class="portrait-card" aria-hidden="true">${escapeHtml6(initials(input.name))}</div></div><div class="hero-copy"><h1>${escapeHtml6(input.name)}</h1><p class="role">${escapeHtml6(input.professionalTitle)}</p><p class="summary">${escapeHtml6(input.summary)}</p><div class="actions">${action(input.primaryAction)}${input.secondaryAction ? action(input.secondaryAction, true) : ""}</div>${social}</div></section>
33763
- ${section(flags.authority && Boolean(authority), `<section class="light" id="about"><h2>${escapeHtml6(authority?.heading ?? "")}</h2>${authority?.paragraphs.map((paragraph) => `<p>${escapeHtml6(paragraph)}</p>`).join("") ?? ""}</section>`)}
33764
- ${section(flags.consultation && Boolean(consultation), `<section class="section consult"><div><p class="eyebrow">${escapeHtml6(consultation?.eyebrow ?? "Start a conversation")}</p><h2>${escapeHtml6(consultation?.heading ?? "")}</h2><p class="summary">${escapeHtml6(consultation?.summary ?? "")}</p>${consultation ? action(consultation.action) : ""}</div><div class="accent-card" aria-label="Image placeholder">${escapeHtml6(initials(input.name))}</div></section>`)}
33765
- ${section(flags.learning && Boolean(learning), `<section class="section learning"><div class="section-head"><h2>${escapeHtml6(learning?.heading ?? "")}</h2><p class="summary">${escapeHtml6(learning?.summary ?? "")}</p></div><div class="topic-grid">${learning?.topics.map((topic) => `<article class="topic"><strong>${escapeHtml6(topic.title)}</strong><h3>${escapeHtml6(topic.headline)}</h3><p>${escapeHtml6(topic.summary)}</p>${topic.href ? `<a href="${safeHref(topic.href)}">Explore \u2192</a>` : ""}</article>`).join("") ?? ""}</div></section>`)}
33766
- ${section(flags.services && Boolean(services), `<section class="section" id="services"><div class="section-head"><h2>${escapeHtml6(services?.heading ?? "")}</h2><p class="summary">${escapeHtml6(services?.summary ?? "")}</p></div><div class="grid">${services?.offers.map((offer) => `<article class="card"><h3>${escapeHtml6(offer.title)}</h3><p>${escapeHtml6(offer.description)}</p>${offer.action ? action(offer.action, true) : ""}</article>`).join("") ?? ""}</div></section>`)}
33767
- ${section(flags.proof && Boolean(proof), `<section class="section proof" id="proof"><div class="section-head"><h2>${escapeHtml6(proof?.heading ?? "")}</h2><p class="summary">${escapeHtml6(proof?.summary ?? "")}</p></div><div class="quotes">${proof?.testimonials.map((item) => `<article class="quote"><q>${escapeHtml6(item.quote)}</q><span>${escapeHtml6(item.person)}${item.role ? ` / ${escapeHtml6(item.role)}` : ""}</span></article>`).join("") ?? ""}</div></section>`)}
33768
- ${section(flags.finalCta && Boolean(finalCta), `<section class="section cta" id="contact"><p class="eyebrow">${escapeHtml6(finalCta?.eyebrow ?? "Ready when you are")}</p><h2>${escapeHtml6(finalCta?.heading ?? "")}</h2><p class="summary">${escapeHtml6(finalCta?.summary ?? "")}</p>${finalCta ? action(finalCta.action) : ""}</section>`)}
33769
- </main><footer class="footer"><div><strong>${escapeHtml6(input.name)}</strong><div>${escapeHtml6(input.professionalTitle)}</div>${config.showGeneratedAt ? `<div class="generated">Generated ${escapeHtml6(generatedAtIso)}</div>` : ""}</div><div>${input.location ? `<div>${escapeHtml6(input.location)}</div>` : ""}${input.email ? `<a href="mailto:${escapeHtml6(input.email)}">${escapeHtml6(input.email)}</a>` : ""}${input.phone ? `<div>${escapeHtml6(input.phone)}</div>` : ""}</div></footer>
35465
+ <header class="header"><a class="brand" href="#top"><span class="mark">${escapeHtml7(initials(input.name).slice(0, 1))}</span><strong>${escapeHtml7(brand)}</strong></a>${navigation}</header>
35466
+ <main id="main"><section class="hero" id="top"><div class="portrait" aria-label="Portrait placeholder"><div class="portrait-card" aria-hidden="true">${escapeHtml7(initials(input.name))}</div></div><div class="hero-copy"><h1>${escapeHtml7(input.name)}</h1><p class="role">${escapeHtml7(input.professionalTitle)}</p><p class="summary">${escapeHtml7(input.summary)}</p><div class="actions">${action(input.primaryAction)}${input.secondaryAction ? action(input.secondaryAction, true) : ""}</div>${social}</div></section>
35467
+ ${section(flags.authority && Boolean(authority), `<section class="light" id="about"><h2>${escapeHtml7(authority?.heading ?? "")}</h2>${authority?.paragraphs.map((paragraph) => `<p>${escapeHtml7(paragraph)}</p>`).join("") ?? ""}</section>`)}
35468
+ ${section(flags.consultation && Boolean(consultation), `<section class="section consult"><div><p class="eyebrow">${escapeHtml7(consultation?.eyebrow ?? "Start a conversation")}</p><h2>${escapeHtml7(consultation?.heading ?? "")}</h2><p class="summary">${escapeHtml7(consultation?.summary ?? "")}</p>${consultation ? action(consultation.action) : ""}</div><div class="accent-card" aria-label="Image placeholder">${escapeHtml7(initials(input.name))}</div></section>`)}
35469
+ ${section(flags.learning && Boolean(learning), `<section class="section learning"><div class="section-head"><h2>${escapeHtml7(learning?.heading ?? "")}</h2><p class="summary">${escapeHtml7(learning?.summary ?? "")}</p></div><div class="topic-grid">${learning?.topics.map((topic) => `<article class="topic"><strong>${escapeHtml7(topic.title)}</strong><h3>${escapeHtml7(topic.headline)}</h3><p>${escapeHtml7(topic.summary)}</p>${topic.href ? `<a href="${safeHref(topic.href)}">Explore \u2192</a>` : ""}</article>`).join("") ?? ""}</div></section>`)}
35470
+ ${section(flags.services && Boolean(services), `<section class="section" id="services"><div class="section-head"><h2>${escapeHtml7(services?.heading ?? "")}</h2><p class="summary">${escapeHtml7(services?.summary ?? "")}</p></div><div class="grid">${services?.offers.map((offer) => `<article class="card"><h3>${escapeHtml7(offer.title)}</h3><p>${escapeHtml7(offer.description)}</p>${offer.action ? action(offer.action, true) : ""}</article>`).join("") ?? ""}</div></section>`)}
35471
+ ${section(flags.proof && Boolean(proof), `<section class="section proof" id="proof"><div class="section-head"><h2>${escapeHtml7(proof?.heading ?? "")}</h2><p class="summary">${escapeHtml7(proof?.summary ?? "")}</p></div><div class="quotes">${proof?.testimonials.map((item) => `<article class="quote"><q>${escapeHtml7(item.quote)}</q><span>${escapeHtml7(item.person)}${item.role ? ` / ${escapeHtml7(item.role)}` : ""}</span></article>`).join("") ?? ""}</div></section>`)}
35472
+ ${section(flags.finalCta && Boolean(finalCta), `<section class="section cta" id="contact"><p class="eyebrow">${escapeHtml7(finalCta?.eyebrow ?? "Ready when you are")}</p><h2>${escapeHtml7(finalCta?.heading ?? "")}</h2><p class="summary">${escapeHtml7(finalCta?.summary ?? "")}</p>${finalCta ? action(finalCta.action) : ""}</section>`)}
35473
+ </main><footer class="footer"><div><strong>${escapeHtml7(input.name)}</strong><div>${escapeHtml7(input.professionalTitle)}</div>${config.showGeneratedAt ? `<div class="generated">Generated ${escapeHtml7(generatedAtIso)}</div>` : ""}</div><div>${input.location ? `<div>${escapeHtml7(input.location)}</div>` : ""}${input.email ? `<a href="mailto:${escapeHtml7(input.email)}">${escapeHtml7(input.email)}</a>` : ""}${input.phone ? `<div>${escapeHtml7(input.phone)}</div>` : ""}</div></footer>
33770
35474
  <script>document.querySelector('.menu')?.addEventListener('click',function(){var nav=document.querySelector('.nav');var open=this.getAttribute('aria-expanded')!=='true';this.setAttribute('aria-expanded',String(open));nav?.classList.toggle('open',open)});document.querySelectorAll('.nav a').forEach(function(link){link.addEventListener('click',function(){document.querySelector('.nav')?.classList.remove('open');document.querySelector('.menu')?.setAttribute('aria-expanded','false')})});</script></body></html>`;
33771
35475
  const bytes = Buffer.byteLength(html, "utf8");
33772
35476
  return {
33773
35477
  html,
33774
35478
  filename: `${input.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "personal-authority"}.html`,
33775
35479
  bytes,
33776
- sha256: createHash16("sha256").update(html).digest("hex"),
35480
+ sha256: createHash18("sha256").update(html).digest("hex"),
33777
35481
  generatedAt: generatedAtIso
33778
35482
  };
33779
35483
  }
33780
35484
 
35485
+ // src/newsroom-publisher/render.ts
35486
+ import { createHash as createHash19 } from "crypto";
35487
+ function escapeHtml8(value) {
35488
+ return value.replace(/[&<>"']/g, (character) => ({
35489
+ "&": "&amp;",
35490
+ "<": "&lt;",
35491
+ ">": "&gt;",
35492
+ '"': "&quot;",
35493
+ "'": "&#39;"
35494
+ })[character] ?? character);
35495
+ }
35496
+ function safeHref2(value) {
35497
+ const trimmed = value.trim();
35498
+ return /^(?:https?:|mailto:|tel:|#|\/)/i.test(trimmed) ? escapeHtml8(trimmed) : "#";
35499
+ }
35500
+ function formatDate(value) {
35501
+ const date = new Date(value);
35502
+ return Number.isNaN(date.getTime()) ? escapeHtml8(value) : new Intl.DateTimeFormat("en", { month: "short", day: "numeric", year: "numeric" }).format(date);
35503
+ }
35504
+ function initials2(value) {
35505
+ return value.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]).join("").toUpperCase();
35506
+ }
35507
+ function storyMedia(story, className = "") {
35508
+ if (story.imageUrl) {
35509
+ return `<div class="story-media ${className}"><img src="${safeHref2(story.imageUrl)}" alt="${escapeHtml8(story.imageAlt || story.title)}" loading="lazy"></div>`;
35510
+ }
35511
+ const tone = [...story.title].reduce((total, character) => total + character.charCodeAt(0), 0) % 5;
35512
+ return `<div class="story-media story-media--placeholder story-media--tone-${tone} ${className}" role="img" aria-label="Editorial image placeholder for ${escapeHtml8(story.title)}"><i></i><b></b><span>${escapeHtml8(story.category)}</span></div>`;
35513
+ }
35514
+ function byline(story, showBylines) {
35515
+ const author = story.author.href ? `<a href="${safeHref2(story.author.href)}">${escapeHtml8(story.author.name)}</a>` : escapeHtml8(story.author.name);
35516
+ return `<div class="byline">${showBylines ? `<span>By ${author}</span>` : ""}<time datetime="${escapeHtml8(story.publishedAt)}">${formatDate(story.publishedAt)}</time>${story.sponsored ? "<b>Sponsored</b>" : ""}</div>`;
35517
+ }
35518
+ function storyCard(story, showBylines, variant = "") {
35519
+ return `<article class="story-card ${variant}">
35520
+ <a class="story-card__media" href="${safeHref2(story.href)}">${storyMedia(story)}</a>
35521
+ <div class="story-card__body"><a class="kicker" href="${safeHref2(story.href)}">${escapeHtml8(story.category)}</a><h3><a href="${safeHref2(story.href)}">${escapeHtml8(story.title)}</a></h3>${story.dek ? `<p>${escapeHtml8(story.dek)}</p>` : ""}${byline(story, showBylines)}</div>
35522
+ </article>`;
35523
+ }
35524
+ function renderNewsroomPublisherV1(input, config, generatedAt) {
35525
+ const flags = config.featureFlags;
35526
+ const generatedAtIso = generatedAt.toISOString();
35527
+ const brand = config.brandName || input.publication.name;
35528
+ const [lead, ...supporting] = input.featured;
35529
+ const primaryPages = [
35530
+ { label: "Home", href: input.publication.url },
35531
+ input.primaryPages.about,
35532
+ input.primaryPages.editor,
35533
+ input.primaryPages.press,
35534
+ input.primaryPages.news,
35535
+ input.primaryPages.contact
35536
+ ];
35537
+ const trustPages = Object.values(input.trustPages);
35538
+ const ticker = flags.breakingTicker && input.ticker ? `<div class="ticker"><div><b>${escapeHtml8(input.ticker.label)}</b><a href="${safeHref2(input.ticker.href || "#latest")}">${escapeHtml8(input.ticker.headline)}</a><span>${formatDate(generatedAtIso)}</span></div></div>` : "";
35539
+ const masthead = `<nav class="utility-nav" aria-label="Publication pages"><div><span>${escapeHtml8(input.publication.location || "Independent newsroom")}</span>${primaryPages.slice(1).map((link) => `<a href="${safeHref2(link.href)}">${escapeHtml8(link.label)}</a>`).join("")}</div></nav><header class="masthead">
35540
+ <a class="wordmark" href="${safeHref2(input.publication.url)}"><span>${escapeHtml8(brand)}</span><small>${escapeHtml8(input.publication.tagline)}</small></a>
35541
+ <div class="masthead__date"><span>Independent journalism</span><time datetime="${generatedAtIso}">${formatDate(generatedAtIso)}</time></div>
35542
+ <div class="masthead__tools"><button type="button" aria-label="Search">Search</button><button type="button" aria-label="Open menu">Menu</button></div>
35543
+ </header>`;
35544
+ const navigation = flags.navigation ? `<nav class="desk-nav" aria-label="Publication categories"><div><a class="desk-nav__home" href="#top">Home</a>${input.categories.map((category) => `<a href="${safeHref2(category.href)}">${escapeHtml8(category.label)}</a>`).join("")}<a class="desk-nav__latest" href="#latest">Latest</a></div></nav>` : "";
35545
+ const leadGrid = flags.leadGrid ? `<section class="lead-grid" aria-label="Top stories">
35546
+ <div class="lead-grid__label"><span>Today\u2019s edition</span><b>Top stories</b></div>
35547
+ <article class="lead-story"><a class="lead-story__media" href="${safeHref2(lead.href)}">${storyMedia(lead, "story-media--lead")}</a><div class="lead-story__body"><a class="kicker" href="${safeHref2(lead.href)}">${escapeHtml8(lead.category)}</a><h1><a href="${safeHref2(lead.href)}">${escapeHtml8(lead.title)}</a></h1>${lead.dek ? `<p>${escapeHtml8(lead.dek)}</p>` : ""}${byline(lead, config.showBylines)}<a class="read-story" href="${safeHref2(lead.href)}">Read the full report <span>\u2197</span></a></div></article>
35548
+ <div class="lead-support">${supporting.slice(0, 4).map((story, index) => `<div class="lead-support__item"><span>${String(index + 1).padStart(2, "0")}</span>${storyCard(story, config.showBylines, "story-card--support")}</div>`).join("")}</div>
35549
+ </section>` : "";
35550
+ const latest = flags.latestNews && input.latest.length > 0 ? `<section class="latest" id="latest">
35551
+ <div class="section-title"><div><span>Live desk</span><h2>Latest news</h2></div><a href="#top">Back to top \u2191</a></div>
35552
+ <div class="latest__layout"><div class="latest__feed">${input.latest.map((story, index) => `<article class="feed-story"><span>${String(index + 1).padStart(2, "0")}</span><div><a class="kicker" href="${safeHref2(story.href)}">${escapeHtml8(story.category)}</a><h3><a href="${safeHref2(story.href)}">${escapeHtml8(story.title)}</a></h3>${story.dek ? `<p>${escapeHtml8(story.dek)}</p>` : ""}${byline(story, config.showBylines)}</div>${storyMedia(story, "story-media--feed")}</article>`).join("")}</div>
35553
+ <aside class="editor-note"><span>From the newsroom</span><h3>Reporting with context, sources, and accountable bylines.</h3><p>${escapeHtml8(input.editorial.disclosure)}</p>${trustPages.slice(0, 4).map((link) => `<a href="${safeHref2(link.href)}">${escapeHtml8(link.label)} \u2197</a>`).join("")}</aside></div>
35554
+ </section>` : "";
35555
+ const sectionLayouts = ["panorama", "mosaic", "tiles"];
35556
+ const sections = flags.categorySections ? input.sections.map((section2, sectionIndex) => {
35557
+ const layout = sectionLayouts[sectionIndex % sectionLayouts.length];
35558
+ const cards = section2.stories.map((story, index) => storyCard(
35559
+ story,
35560
+ config.showBylines,
35561
+ layout === "panorama" && index === 0 ? "story-card--panorama" : layout === "mosaic" && index === 0 ? "story-card--mosaic-lead" : ""
35562
+ )).join("");
35563
+ return `<section class="desk-section desk-section--${sectionIndex % 2 ? "tint" : "plain"} desk-section--layout-${layout}">
35564
+ <div class="section-title"><div><span>Coverage desk \xB7 ${String(sectionIndex + 1).padStart(2, "0")}</span><h2>${escapeHtml8(section2.name)}</h2></div>${section2.href ? `<a href="${safeHref2(section2.href)}">View all ${escapeHtml8(section2.name)} \u2192</a>` : ""}</div>
35565
+ <div class="desk-section__grid desk-section__grid--${layout}">${cards}</div>
35566
+ </section>`;
35567
+ }).join("") : "";
35568
+ const newsletter = flags.newsletter && input.newsletter ? `<section class="newsletter"><div><span>${escapeHtml8(input.newsletter.eyebrow || "Reader briefing")}</span><h2>${escapeHtml8(input.newsletter.heading)}</h2><p>${escapeHtml8(input.newsletter.summary)}</p></div><a href="${safeHref2(input.newsletter.action.href)}">${escapeHtml8(input.newsletter.action.label)} \u2192</a></section>` : "";
35569
+ const pressRoom = flags.pressRoom && input.pressRoom ? `<section class="press-room" id="press-room"><div class="press-room__intro"><span>Separate from editorial coverage</span><h2>${escapeHtml8(input.pressRoom.heading)}</h2><p>${escapeHtml8(input.pressRoom.summary)}</p><a href="${safeHref2(input.pressRoom.action.href)}">${escapeHtml8(input.pressRoom.action.label)} \u2192</a></div><div class="release-list">${input.pressRoom.releases.map((release) => `<article><time datetime="${escapeHtml8(release.publishedAt)}">${formatDate(release.publishedAt)}</time><span>${escapeHtml8(release.originatingCity)}</span><h3><a href="${safeHref2(release.href)}">${escapeHtml8(release.headline)}</a></h3></article>`).join("") || "<p>No releases are currently published.</p>"}</div></section>` : "";
35570
+ const trustCenter = `<section class="trust-center" id="trust"><div class="trust-center__intro"><span>Public record</span><h2>How this newsroom works.</h2><p>Ownership, editorial practice, technology use, commercial relationships, and reader rights should be easy to find\u2014and written to be understood.</p></div><div class="trust-center__grid">${trustPages.map((link, index) => `<a href="${safeHref2(link.href)}"><span>${String(index + 1).padStart(2, "0")}</span><strong>${escapeHtml8(link.label)}</strong><small>Read \u2192</small></a>`).join("")}</div></section>`;
35571
+ const trustFooter = flags.trustFooter ? `<footer class="site-footer"><div class="editor"><span class="editor__avatar">${escapeHtml8(initials2(input.editorial.managingEditor.name))}</span><div><small>${escapeHtml8(input.editorial.managingEditor.role)}</small><h2>${input.editorial.managingEditor.href ? `<a href="${safeHref2(input.editorial.managingEditor.href)}">${escapeHtml8(input.editorial.managingEditor.name)}</a>` : escapeHtml8(input.editorial.managingEditor.name)}</h2><p>${escapeHtml8(input.editorial.managingEditor.bio)}</p></div></div><div class="footer-links"><h3>${escapeHtml8(brand)}</h3><p>${escapeHtml8(input.publication.tagline)}</p><nav>${primaryPages.map((link) => `<a href="${safeHref2(link.href)}">${escapeHtml8(link.label)}</a>`).join("")}</nav>${config.showGeneratedAt ? `<time datetime="${generatedAtIso}">Updated ${formatDate(generatedAtIso)}</time>` : ""}</div></footer>` : "";
35572
+ const html = `<!doctype html>
35573
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow"><title>${escapeHtml8(brand)} \xB7 News Publisher</title><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Archivo:wdth,wght@75..125,300..900&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
35574
+ <style>
35575
+ :root{--ink:#0b0b0c;--soft-ink:#222326;--muted:#696b70;--paper:#fff;--cream:#f4f0e8;--wash:#f5f5f3;--line:#d8d8d4;--signal:#275cf3;--acid:#d9ff43;--dark:#0b0b0c;--serif:"Instrument Serif",Georgia,serif;--sans:"Archivo",Arial,sans-serif;--mono:"JetBrains Mono",monospace;font-family:var(--sans);color:var(--ink);background:var(--paper)}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--paper);line-height:1.45}body[data-theme=journal]{--cream:#eee7da;--signal:#9d2d25;--acid:#f0c84f}body[data-theme=midnight]{--paper:#e8e9ec;--cream:#dfe1e6;--wash:#d8dae0;--line:#b9bdc6;--signal:#224bcc}a{color:inherit;text-decoration:none}a,button{transition:color .18s ease,background .18s ease,opacity .18s ease,transform .18s ease}button{font:inherit}.utility-nav{color:#d2d2d2;background:var(--dark);border-bottom:1px solid #292929}.utility-nav>div{max-width:1180px;height:35px;margin:auto;display:flex;align-items:center;justify-content:flex-end;gap:20px;padding:0 18px;overflow:auto}.utility-nav span{margin-right:auto;color:#8d8d8d}.utility-nav a,.utility-nav span{font:600 8px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;white-space:nowrap}.utility-nav a:hover{color:#fff}.ticker{color:var(--ink);background:var(--acid);padding:0 18px}.ticker>div{max-width:1180px;height:36px;margin:auto;display:flex;align-items:center;gap:16px;font:600 10px/1 var(--mono)}.ticker b{padding:5px 7px;color:#fff;background:var(--ink);font-size:8px;letter-spacing:.1em;text-transform:uppercase}.ticker a{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ticker a:hover{text-decoration:underline}.ticker span{margin-left:auto;opacity:.58;font-size:8px;text-transform:uppercase}.masthead{max-width:1180px;height:126px;margin:auto;display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:42px;padding:0 18px}.wordmark{display:grid;justify-self:start}.wordmark span{font:400 clamp(40px,5vw,68px)/.82 var(--serif);letter-spacing:-.055em}.wordmark small{max-width:430px;margin-top:11px;color:var(--muted);font:600 8px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase}.masthead__date{display:grid;gap:3px;text-align:right}.masthead__date span,.masthead__date time{font:600 8px/1.4 var(--mono);letter-spacing:.08em;text-transform:uppercase}.masthead__date span{color:var(--signal)}.masthead__date time{color:var(--muted)}.masthead__tools{display:flex;border:1px solid var(--ink)}.masthead__tools button{height:38px;padding:0 13px;border:0;background:transparent;font:700 9px/1 var(--mono);text-transform:uppercase;cursor:pointer}.masthead__tools button+button{border-left:1px solid var(--ink)}.masthead__tools button:hover{color:#fff;background:var(--ink)}.desk-nav{position:sticky;top:0;z-index:20;color:var(--ink);background:rgba(255,255,255,.96);border-top:1px solid var(--ink);border-bottom:1px solid var(--ink);backdrop-filter:blur(10px)}.desk-nav>div{max-width:1180px;height:43px;margin:auto;display:flex;align-items:center;overflow-x:auto;padding:0 18px}.desk-nav a{height:42px;display:grid;place-items:center;padding:0 17px;border-right:1px solid var(--line);font:650 9px/1 var(--mono);letter-spacing:.04em;text-transform:uppercase;white-space:nowrap}.desk-nav a:first-child{border-left:1px solid var(--line)}.desk-nav a:hover,.desk-nav__home{color:#fff!important;background:var(--ink)}.desk-nav__latest{margin-left:auto;color:var(--signal);border-left:1px solid var(--line)}main{max-width:1180px;margin:auto;padding:30px 18px 90px}.lead-grid{margin:0 -18px;padding:0 18px 42px;color:#fff;background:var(--dark);border-bottom:1px solid var(--ink)}.lead-grid__label{height:50px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #303030}.lead-grid__label span,.lead-grid__label b{font:600 8px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase}.lead-grid__label span{color:var(--acid)}.lead-grid__label b{color:#8f8f8f}.lead-story{display:grid;grid-template-columns:minmax(0,1.28fr) minmax(340px,.72fr);min-height:500px;border-bottom:1px solid #303030}.lead-story__media{display:block;margin-left:calc((1180px - 100vw)/2);min-width:0}.story-media{position:relative;aspect-ratio:16/10;overflow:hidden;background:#e2e2df}.story-media img{width:100%;height:100%;display:block;object-fit:cover;filter:saturate(.88) contrast(1.03);transition:transform .55s cubic-bezier(.2,.7,.2,1)}a:hover .story-media img{transform:scale(1.025)}.story-media--lead{height:100%;min-height:500px;aspect-ratio:auto}.story-media--placeholder{isolation:isolate;display:block;background:#d9d7d1}.story-media--placeholder:after{position:absolute;z-index:1;inset:0;background:linear-gradient(145deg,transparent 35%,rgba(0,0,0,.14));content:""}.story-media--placeholder i,.story-media--placeholder b{position:absolute;display:block}.story-media--placeholder i{width:72%;aspect-ratio:1;right:-20%;top:-32%;border:clamp(22px,5vw,72px) solid currentColor;border-radius:50%;opacity:.78}.story-media--placeholder b{width:42%;height:115%;left:15%;bottom:-45%;background:currentColor;transform:rotate(32deg);opacity:.88}.story-media--placeholder span{position:absolute;z-index:2;left:18px;bottom:15px;padding:6px 8px;color:#fff;background:var(--ink);font:600 8px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase}.story-media--tone-0{color:#123fce;background:#a5c8ff}.story-media--tone-1{color:#ef4d25;background:#ffd9bd}.story-media--tone-2{color:#907000;background:#f3df36}.story-media--tone-3{color:#1a7857;background:#b8e8cc}.story-media--tone-4{color:#7d3de0;background:#dfc5ff}.lead-story__body{display:flex;flex-direction:column;justify-content:center;padding:45px 0 45px 40px}.kicker{display:inline-flex;width:max-content;color:var(--signal);font:700 8px/1 var(--mono);letter-spacing:.11em;text-transform:uppercase}.lead-grid .kicker{color:var(--acid)}.lead-story h1{max-width:540px;margin:13px 0 18px;font:400 clamp(48px,5vw,76px)/.92 var(--serif);letter-spacing:-.052em}.lead-story p{max-width:490px;margin:0;color:#bdbdbd;font-size:15px;line-height:1.55}.byline{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:16px;color:var(--muted);font:600 8px/1.4 var(--mono);letter-spacing:.05em;text-transform:uppercase}.byline span{color:var(--ink);font-weight:700}.lead-grid .byline{color:#848484}.lead-grid .byline span{color:#e7e7e7}.byline b{padding:4px 6px;color:var(--ink);background:var(--acid)}.read-story{width:max-content;margin-top:34px;padding-bottom:6px;border-bottom:1px solid #777;font:650 10px/1 var(--mono);text-transform:uppercase}.read-story span{color:var(--acid)}.read-story:hover{border-color:#fff}.lead-support{display:grid;grid-template-columns:repeat(4,1fr);gap:0}.lead-support__item{display:grid;grid-template-columns:25px minmax(0,1fr);gap:12px;padding:22px 18px 0 0;border-right:1px solid #303030}.lead-support__item+.lead-support__item{padding-left:18px}.lead-support__item:last-child{border-right:0}.lead-support__item>span{color:#666;font:600 9px/1 var(--mono)}.story-card{min-width:0}.story-card h3{margin:7px 0;font:400 26px/1.02 var(--serif);letter-spacing:-.025em}.story-card p{margin:9px 0;color:var(--muted);font-size:12px}.story-card__body{padding-top:11px}.story-card--support .story-card__media{display:none}.story-card--support .story-card__body{padding:0}.story-card--support h3{margin-top:8px;color:#fff;font-size:21px}.story-card--support p,.story-card--support .byline span{display:none}.story-card--support .byline{color:#777}.section-title{display:flex;align-items:end;justify-content:space-between;gap:30px;margin-bottom:24px;padding-bottom:11px;border-bottom:2px solid var(--ink)}.section-title span,.newsletter span,.press-room__intro>span,.trust-center__intro>span{display:block;color:var(--signal);font:700 8px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase}.section-title h2,.newsletter h2,.press-room h2,.trust-center h2,.site-footer h2{margin:7px 0 0;font:400 clamp(38px,5vw,58px)/.95 var(--serif);letter-spacing:-.04em}.section-title>a{padding-bottom:4px;border-bottom:1px solid var(--ink);font:650 9px/1 var(--mono);text-transform:uppercase}.latest{padding:64px 0}.latest__layout{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:55px}.latest__feed{border-top:1px solid var(--ink)}.feed-story{display:grid;grid-template-columns:34px minmax(0,1fr) 180px;gap:18px;align-items:start;padding:19px 0;border-bottom:1px solid var(--line)}.feed-story>span{color:#a0a0a0;font:600 9px/1 var(--mono)}.feed-story h3{margin:7px 0;font:400 29px/1.02 var(--serif);letter-spacing:-.025em}.feed-story p{margin:8px 0;color:var(--muted);font-size:12px}.story-media--feed{aspect-ratio:4/3}.editor-note{position:sticky;top:68px;align-self:start;padding:25px;color:#fff;background:var(--dark);border-top:5px solid var(--acid)}.editor-note>span{color:var(--acid);font:600 8px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase}.editor-note h3{margin:13px 0;font:400 31px/.98 var(--serif)}.editor-note p{color:#aaa;font-size:12px}.editor-note a{display:block;padding:11px 0;border-top:1px solid #333;font:600 9px/1 var(--mono);text-transform:uppercase}.editor-note a:hover{color:var(--acid)}.desk-section{margin:0 -18px;padding:58px 18px;border-top:1px solid var(--ink)}.desk-section--tint{background:var(--cream)}.desk-section__grid{display:grid;grid-template-columns:repeat(4,1fr);gap:26px 14px}.story-card--wide{grid-column:span 2;grid-row:span 2}.story-card--wide h3{max-width:530px;font-size:40px}.story-card--wide p{max-width:540px;font-size:13px}.desk-section__grid>.story-card:not(.story-card--wide) .story-media{aspect-ratio:1}.newsletter{position:relative;margin:64px 0;display:grid;grid-template-columns:1fr auto;align-items:end;gap:50px;overflow:hidden;padding:clamp(38px,6vw,72px);color:var(--ink);background:var(--acid)}.newsletter:after{position:absolute;width:260px;height:260px;right:18%;top:-160px;border:55px solid var(--ink);border-radius:50%;content:"";opacity:.08}.newsletter span{color:var(--ink)}.newsletter h2{max-width:760px;font-size:clamp(44px,6vw,72px)}.newsletter p{max-width:680px;margin-bottom:0;color:#303030}.newsletter>a,.press-room__intro>a{position:relative;z-index:1;display:inline-flex;padding:14px 18px;color:#fff;background:var(--ink);border:1px solid var(--ink);font:700 9px/1 var(--mono);text-transform:uppercase}.newsletter>a:hover,.press-room__intro>a:hover{color:var(--ink);background:transparent}.press-room{display:grid;grid-template-columns:.7fr 1.3fr;gap:56px;padding:62px 0;border-top:6px solid var(--ink)}.press-room__intro p{max-width:390px;color:var(--muted)}.release-list{border-top:1px solid var(--ink)}.release-list article{display:grid;grid-template-columns:105px 105px 1fr;gap:15px;align-items:start;padding:20px 0;border-bottom:1px solid var(--line)}.release-list time,.release-list span{color:var(--muted);font:500 8px/1.4 var(--mono);text-transform:uppercase}.release-list h3{margin:0;font:400 22px/1.04 var(--serif)}.trust-center{margin:20px -18px -90px;padding:70px max(18px,calc((100vw - 1144px)/2)) 82px;color:#fff;background:var(--dark)}.trust-center__intro{max-width:810px;margin-bottom:38px}.trust-center__intro>span{color:var(--acid)}.trust-center__intro p{max-width:670px;color:#aaa}.trust-center__grid{display:grid;grid-template-columns:repeat(2,1fr);border-top:1px solid #444}.trust-center__grid a{display:grid;grid-template-columns:32px minmax(0,1fr) auto;align-items:center;gap:15px;min-height:70px;padding:0 18px 0 0;border-bottom:1px solid #333}.trust-center__grid a:nth-child(odd){margin-right:28px}.trust-center__grid a:hover{color:var(--acid)}.trust-center__grid span,.trust-center__grid small{color:#696969;font:600 8px/1 var(--mono)}.trust-center__grid strong{font:400 20px/1 var(--serif)}.site-footer{display:grid;grid-template-columns:1.15fr .85fr;gap:80px;padding:70px max(24px,calc((100vw - 1144px)/2));color:var(--ink);background:var(--cream);border-top:1px solid var(--ink)}.editor{display:grid;grid-template-columns:78px 1fr;gap:22px}.editor__avatar{width:78px;height:78px;display:grid;place-items:center;color:#fff;background:var(--signal);font:400 27px/1 var(--serif)}.editor small{color:var(--signal);font:700 8px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase}.editor h2{font-size:38px}.editor p,.footer-links p{max-width:620px;color:var(--muted)}.footer-links h3{margin-top:0;font:400 29px/1 var(--serif)}.footer-links nav{display:grid;grid-template-columns:1fr 1fr;border-top:1px solid var(--ink)}.footer-links nav a{padding:9px 0;border-bottom:1px solid var(--line);font:600 8px/1 var(--mono);text-transform:uppercase}.footer-links time{display:block;margin-top:24px;color:var(--muted);font:500 8px/1 var(--mono)}@media(max-width:900px){.masthead{grid-template-columns:1fr auto;height:110px}.masthead__date{display:none}.lead-story{grid-template-columns:1fr}.lead-story__media{margin:0}.lead-story__body{padding:35px 0}.lead-support{grid-template-columns:1fr 1fr}.lead-support__item:nth-child(2){border-right:0}.lead-support__item:nth-child(n+3){border-top:1px solid #303030}.latest__layout,.press-room,.site-footer{grid-template-columns:1fr}.editor-note{position:static}.desk-section__grid{grid-template-columns:1fr 1fr}.story-card--wide{grid-column:1/-1}.site-footer{gap:40px}.feed-story{grid-template-columns:30px 1fr 150px}}@media(max-width:620px){.utility-nav span,.utility-nav a:nth-of-type(-n+2),.ticker span{display:none}.ticker>div{height:34px}.masthead{height:92px;gap:14px}.wordmark span{font-size:38px}.wordmark small{max-width:220px;font-size:6px}.masthead__tools button:first-child{display:none}.desk-nav>div{padding:0}.desk-nav a{padding:0 13px}.lead-grid{padding-bottom:20px}.lead-grid__label{height:40px}.story-media--lead{min-height:320px}.lead-story{min-height:0}.lead-story h1{font-size:48px}.lead-story__body{padding:27px 0 34px}.lead-support{grid-template-columns:1fr}.lead-support__item,.lead-support__item+.lead-support__item{padding:17px 0;border-right:0;border-top:1px solid #303030}.story-card--support h3{font-size:24px}.section-title{align-items:start}.section-title>a{display:none}.latest{padding:48px 0}.feed-story{grid-template-columns:25px 1fr}.story-media--feed{grid-column:2}.feed-story h3{font-size:26px}.desk-section__grid{grid-template-columns:1fr}.story-card--wide{grid-column:auto;grid-row:auto}.story-card--wide h3{font-size:34px}.newsletter{grid-template-columns:1fr}.newsletter h2{font-size:48px}.release-list article{grid-template-columns:1fr}.trust-center__grid{grid-template-columns:1fr}.trust-center__grid a:nth-child(odd){margin-right:0}.site-footer{padding:50px 20px}.editor{grid-template-columns:1fr}.footer-links nav{grid-template-columns:1fr}}
35576
+ /* Tampa-inspired publication palette */
35577
+ :root{--ink:#12312f;--soft-ink:#294744;--muted:#697b77;--paper:#fffdf8;--cream:#f8f2e8;--wash:#f3eee5;--line:#d8d3c8;--signal:#ee7057;--acid:#f0b45d;--dark:#082f2c}
35578
+ body[data-theme=journal]{--dark:#102d2a;--ink:#172f2d;--cream:#eee7da;--signal:#bd5b47;--acid:#e4aa52}
35579
+ body[data-theme=midnight]{--paper:#e8e9e4;--cream:#dfe3dc;--wash:#d8ddd6;--line:#b9c2bb;--dark:#072a28;--signal:#df6c56}
35580
+ .utility-nav{color:#d7e0dc;border-bottom-color:rgba(255,255,255,.14)}
35581
+ .lead-grid__label,.lead-story{border-bottom-color:rgba(255,255,255,.17)}
35582
+ .lead-grid__label b{color:#90aaa4}
35583
+ .lead-story__media .story-media:not(.story-media--placeholder):after{position:absolute;inset:0;background:linear-gradient(90deg,rgba(8,47,44,.38),transparent 64%),linear-gradient(0deg,rgba(8,47,44,.26),transparent 58%);content:""}
35584
+ .story-media--tone-0{color:#0b514b;background:#8fb8ac}.story-media--tone-1{color:#d65f49;background:#f5c2a9}.story-media--tone-2{color:#a97022;background:#f1d59b}.story-media--tone-3{color:#255f52;background:#b8d9c9}.story-media--tone-4{color:#9a5f48;background:#dfc2ad}
35585
+ body{overflow-x:hidden}.lead-story__body,.lead-support__item,.story-card__body{min-width:0}.lead-story h1,.story-card h3,.feed-story h3{overflow-wrap:anywhere}
35586
+ .desk-section__grid--panorama{grid-template-columns:repeat(4,1fr)}.story-card--panorama{grid-column:1/-1;display:grid;grid-template-columns:minmax(0,1.65fr) minmax(250px,.65fr);gap:28px;margin-bottom:12px;padding-bottom:28px;border-bottom:1px solid var(--line)}.story-card--panorama .story-media{aspect-ratio:2.2/1}.story-card--panorama .story-card__body{display:flex;flex-direction:column;justify-content:center;padding:0}.story-card--panorama h3{font-size:48px}.story-card--panorama p{font-size:14px}.desk-section__grid--mosaic{grid-template-columns:repeat(4,1fr);grid-auto-flow:dense}.story-card--mosaic-lead{grid-column:span 2;grid-row:span 2}.story-card--mosaic-lead .story-media{aspect-ratio:1}.story-card--mosaic-lead h3{font-size:42px}.desk-section__grid--mosaic>.story-card:not(.story-card--mosaic-lead) .story-media{aspect-ratio:1.35}.desk-section__grid--tiles{grid-template-columns:repeat(4,1fr)}.desk-section__grid--tiles .story-media{aspect-ratio:1.15}.desk-section__grid--tiles>.story-card:nth-child(3n+2){margin-top:38px}
35587
+ @media(max-width:620px){main{width:100%}.story-media--lead{height:360px;min-height:0}.lead-story__body,.lead-support,.latest,.feed-story{width:100%;max-width:calc(100vw - 36px)}.feed-story>div{min-width:0}.lead-story h1,.story-card h3,.feed-story h3,.story-card h3 a,.feed-story h3 a{white-space:normal;word-break:break-word;overflow-wrap:anywhere}.lead-story h1{max-width:100%;font-size:40px;line-height:.94}.lead-support__item{width:100%;max-width:100%;grid-template-columns:24px minmax(0,1fr);overflow:hidden}.story-card--support,.story-card--support .story-card__body,.story-card--support h3{width:100%;max-width:calc(100vw - 92px)}.feed-story>div,.feed-story h3,.feed-story p{max-width:calc(100vw - 100px)}}
35588
+ @media(max-width:900px){.story-card--panorama{grid-template-columns:1fr}.desk-section__grid--mosaic{grid-template-columns:repeat(2,1fr)}.desk-section__grid--tiles{grid-template-columns:repeat(2,1fr)}}
35589
+ @media(max-width:620px){.desk-section__grid--panorama,.desk-section__grid--mosaic,.desk-section__grid--tiles{grid-template-columns:1fr}.story-card--panorama{display:block}.story-card--panorama h3,.story-card--mosaic-lead h3{font-size:34px}.story-card--mosaic-lead{grid-column:auto;grid-row:auto}.desk-section__grid--tiles>.story-card:nth-child(3n+2){margin-top:0}}
35590
+ </style></head><body data-theme="${escapeHtml8(config.theme)}" id="top" data-scheduled-renderer="newsroom_publisher_v1">${ticker}${masthead}${navigation}<main>${leadGrid}${latest}${sections}${newsletter}${pressRoom}${trustCenter}</main>${trustFooter}</body></html>`;
35591
+ const bytes = Buffer.byteLength(html);
35592
+ const sha2563 = createHash19("sha256").update(html).digest("hex");
35593
+ const filename = `${brand.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "newsroom"}-news-site.html`;
35594
+ return { html, filename, bytes, sha256: sha2563, generatedAt: generatedAtIso };
35595
+ }
35596
+
33781
35597
  // src/scheduled-artifacts/scheduled-artifact-renderer-registry.ts
33782
35598
  var editorialReadingRoomV1 = {
33783
35599
  rendererKey: "editorial_reading_room_v1",
@@ -33815,24 +35631,47 @@ var personalAuthorityV1 = {
33815
35631
  };
33816
35632
  }
33817
35633
  };
35634
+ var newsroomPublisherV1 = {
35635
+ rendererKey: "newsroom_publisher_v1",
35636
+ rendererVersion: 1,
35637
+ async render(rawInput, rawConfig, generatedAt) {
35638
+ const input = NewsroomPublisherV1InputSchema.parse(rawInput);
35639
+ const config = NewsroomPublisherTemplateConfigSchema.parse(rawConfig);
35640
+ const rendered = renderNewsroomPublisherV1(input, config, generatedAt);
35641
+ if (rendered.bytes > 2e6) {
35642
+ throw new Error("Scheduled artifact HTML must be 2,000,000 bytes or fewer.");
35643
+ }
35644
+ return {
35645
+ ...rendered,
35646
+ title: `${input.publication.name} News Publisher`
35647
+ };
35648
+ }
35649
+ };
33818
35650
  var scheduledArtifactRendererRegistry = Object.freeze({
33819
35651
  "editorial_reading_room_v1:1": editorialReadingRoomV1,
33820
- "personal_authority_v1:1": personalAuthorityV1
35652
+ "personal_authority_v1:1": personalAuthorityV1,
35653
+ "newsroom_publisher_v1:1": newsroomPublisherV1
33821
35654
  });
33822
35655
  function getScheduledArtifactRenderer(rendererKey, rendererVersion) {
33823
35656
  return scheduledArtifactRendererRegistry[`${rendererKey}:${rendererVersion}`] ?? null;
33824
35657
  }
33825
35658
  var scheduledArtifactTemplatePresets = [
33826
35659
  EDITORIAL_READING_ROOM_TEMPLATE_PRESET,
33827
- PERSONAL_AUTHORITY_TEMPLATE_PRESET
35660
+ PERSONAL_AUTHORITY_TEMPLATE_PRESET,
35661
+ NEWSROOM_PUBLISHER_TEMPLATE_PRESET
33828
35662
  ];
33829
35663
 
33830
35664
  // src/api/scheduled-result-routes.ts
33831
- var TemplateParamsSchema = z33.object({ templateId: z33.string().uuid() }).strict();
33832
- var RunParamsSchema = z33.object({ runId: z33.string().min(1).max(200) }).strict();
33833
- var ViewLinkParamsSchema = z33.object({
33834
- runId: z33.string().min(1).max(200),
33835
- shareId: z33.string().uuid()
35665
+ var TemplateParamsSchema = z35.object({ templateId: z35.string().uuid() }).strict();
35666
+ var RunParamsSchema = z35.object({ runId: z35.string().min(1).max(200) }).strict();
35667
+ var ViewLinkParamsSchema = z35.object({
35668
+ runId: z35.string().min(1).max(200),
35669
+ shareId: z35.string().uuid()
35670
+ }).strict();
35671
+ var ArtifactTemplateListQuerySchema = z35.object({
35672
+ status: z35.enum(["active", "archived", "all"]).default("active"),
35673
+ query: z35.string().trim().min(1).max(120).optional(),
35674
+ presetKey: ArtifactTemplatePresetKeySchema.optional()
33836
35675
  }).strict();
33837
35676
  function apiError(error, errorCode = "invalid_request") {
33838
35677
  return { ok: false, error, errorCode };
@@ -33861,25 +35700,36 @@ function safeMemoryFailure(result) {
33861
35700
  errorCode in messages ? errorCode : "internal_error"
33862
35701
  );
33863
35702
  }
33864
- var scheduledResultApp = new Hono32();
35703
+ var scheduledResultApp = new Hono33();
33865
35704
  scheduledResultApp.use("/artifact-templates", createApiKeyAuth());
33866
35705
  scheduledResultApp.use("/artifact-templates/*", createApiKeyAuth());
33867
35706
  scheduledResultApp.use("/schedule-runs", createApiKeyAuth());
33868
35707
  scheduledResultApp.use("/schedule-runs/*", createApiKeyAuth());
33869
35708
  scheduledResultApp.get("/artifact-templates", async (c) => {
33870
- const status = c.req.query("status") ?? "active";
33871
- if (!["active", "archived", "all"].includes(status)) {
33872
- return c.json(apiError("status must be active, archived, or all."), 400);
33873
- }
35709
+ const parsed = ArtifactTemplateListQuerySchema.safeParse(c.req.query());
35710
+ if (!parsed.success) return c.json(apiError("The template search filters are invalid."), 400);
35711
+ const { status, query: searchText, presetKey } = parsed.data;
33874
35712
  const { key, error } = await memoryContext(c);
33875
35713
  if (!key) return c.json(apiError(error ?? "Memory is unavailable.", "internal_error"), 503);
33876
35714
  const result = await memoryCall("list_artifact_templates", { status }, key);
33877
35715
  if (!result.ok) return c.json(safeMemoryFailure(result), memoryStatus(result));
33878
35716
  c.header("Cache-Control", "private, no-store");
33879
- return c.json({
33880
- ...result,
33881
- presets: Array.isArray(result.presets) ? result.presets : scheduledArtifactTemplatePresets
33882
- });
35717
+ const normalizedQuery = searchText?.toLocaleLowerCase();
35718
+ const presets = scheduledArtifactTemplatePresets.filter((preset) => {
35719
+ if (presetKey && preset.presetKey !== presetKey) return false;
35720
+ if (!normalizedQuery) return true;
35721
+ return [preset.name, preset.description, preset.presetKey].some((value) => value.toLocaleLowerCase().includes(normalizedQuery));
35722
+ });
35723
+ const templates = (Array.isArray(result.templates) ? result.templates : []).filter((template) => {
35724
+ if (!template || typeof template !== "object") return false;
35725
+ const record = template;
35726
+ const currentVersion = record.currentVersion && typeof record.currentVersion === "object" ? record.currentVersion : null;
35727
+ const rendererKey = typeof currentVersion?.rendererKey === "string" ? currentVersion.rendererKey : "";
35728
+ if (presetKey && rendererKey !== presetKey) return false;
35729
+ if (!normalizedQuery) return true;
35730
+ return [record.name, record.description, rendererKey, currentVersion?.authoringInstructions].filter((value) => typeof value === "string").some((value) => value.toLocaleLowerCase().includes(normalizedQuery));
35731
+ });
35732
+ return c.json({ ok: true, presets, templates });
33883
35733
  });
33884
35734
  scheduledResultApp.post("/artifact-templates", async (c) => {
33885
35735
  const parsed = CreateArtifactTemplateInputSchema.safeParse(await c.req.json().catch(() => null));
@@ -33997,10 +35847,10 @@ scheduledResultApp.delete("/schedule-runs/:runId/view-links/:shareId", async (c)
33997
35847
  });
33998
35848
 
33999
35849
  // src/api/public-scheduled-result-routes.ts
34000
- import { Hono as Hono33 } from "hono";
35850
+ import { Hono as Hono34 } from "hono";
34001
35851
 
34002
35852
  // src/scheduled-artifacts/scheduled-run-artifact-store.ts
34003
- import { createHash as createHash17 } from "crypto";
35853
+ import { createHash as createHash20 } from "crypto";
34004
35854
  var SCHEDULED_RUN_ARTIFACT_PREFIX = "scheduled-run-artifacts/";
34005
35855
  var SCHEDULED_RUN_ARTIFACT_DOWNLOAD_TTL_MS = 15 * 60 * 1e3;
34006
35856
  var SCHEDULED_RUN_ARTIFACT_MAX_BYTES = 2e6;
@@ -34016,7 +35866,7 @@ function policy3() {
34016
35866
  };
34017
35867
  }
34018
35868
  function runStorageSegment(runId) {
34019
- return /^[a-zA-Z0-9_-]{1,160}$/.test(runId) ? runId : `run-${createHash17("sha256").update(runId).digest("hex").slice(0, 32)}`;
35869
+ return /^[a-zA-Z0-9_-]{1,160}$/.test(runId) ? runId : `run-${createHash20("sha256").update(runId).digest("hex").slice(0, 32)}`;
34020
35870
  }
34021
35871
  async function createScheduledRunArtifact(args) {
34022
35872
  if (args.rendered.bytes > SCHEDULED_RUN_ARTIFACT_MAX_BYTES) {
@@ -34059,7 +35909,7 @@ async function readScheduledRunArtifact(args) {
34059
35909
  }
34060
35910
 
34061
35911
  // src/api/public-scheduled-result-routes.ts
34062
- var publicScheduledResultApp = new Hono33();
35912
+ var publicScheduledResultApp = new Hono34();
34063
35913
  function allowedFrameAncestors() {
34064
35914
  const configured = (process.env.SCHEDULED_ARTIFACT_FRAME_ANCESTORS ?? "").split(",").map((value) => value.trim()).filter((value) => /^https?:\/\/[^/\s]+$/.test(value));
34065
35915
  const origins = configured.length ? configured : ["https://mcpscraper.dev", "https://www.mcpscraper.dev"];
@@ -34120,10 +35970,10 @@ publicScheduledResultApp.get("/scheduled-run-view-links/:token/content", async (
34120
35970
  });
34121
35971
 
34122
35972
  // src/api/scheduled-artifact-internal-routes.ts
34123
- import { Hono as Hono34 } from "hono";
35973
+ import { Hono as Hono35 } from "hono";
34124
35974
 
34125
35975
  // src/api/scheduler-integration-auth.ts
34126
- import { createHash as createHash18, createHmac as createHmac4, timingSafeEqual as timingSafeEqual3 } from "crypto";
35976
+ import { createHash as createHash21, createHmac as createHmac4, timingSafeEqual as timingSafeEqual3 } from "crypto";
34127
35977
 
34128
35978
  // src/api/service-connections.ts
34129
35979
  import { randomUUID as randomUUID18 } from "crypto";
@@ -34416,12 +36266,12 @@ var SchedulerIntegrationAuthError = class extends Error {
34416
36266
  }
34417
36267
  };
34418
36268
  function signingSecret() {
34419
- const secret2 = process.env.SCHEDULER_INTEGRATION_SIGNING_SECRET?.trim();
34420
- if (!secret2) throw new SchedulerIntegrationAuthError("not_configured");
34421
- return secret2;
36269
+ const secret = process.env.SCHEDULER_INTEGRATION_SIGNING_SECRET?.trim();
36270
+ if (!secret) throw new SchedulerIntegrationAuthError("not_configured");
36271
+ return secret;
34422
36272
  }
34423
36273
  function schedulerIntegrationSignature(args) {
34424
- const bodyHash = createHash18("sha256").update(args.body).digest("hex");
36274
+ const bodyHash = createHash21("sha256").update(args.body).digest("hex");
34425
36275
  return createHmac4("sha256", args.secret).update(`${args.method.toUpperCase()}
34426
36276
  ${args.path}
34427
36277
  ${args.timestamp}
@@ -34477,7 +36327,7 @@ async function verifySchedulerIntegrationRequest(request, rawBody) {
34477
36327
  }
34478
36328
 
34479
36329
  // src/api/scheduled-artifact-internal-routes.ts
34480
- var scheduledArtifactInternalApp = new Hono34();
36330
+ var scheduledArtifactInternalApp = new Hono35();
34481
36331
  function errorPayload(errorCode, error, requestId) {
34482
36332
  return ApiErrorResponseSchema.parse({
34483
36333
  ok: false,
@@ -34597,7 +36447,7 @@ function startSiteAuditWorker() {
34597
36447
  }
34598
36448
 
34599
36449
  // src/api/server.ts
34600
- import { createMiddleware as createMiddleware4 } from "hono/factory";
36450
+ import { createMiddleware as createMiddleware5 } from "hono/factory";
34601
36451
  import { getCookie as getCookie2, setCookie as setCookie2, deleteCookie } from "hono/cookie";
34602
36452
  import Stripe2 from "stripe";
34603
36453
 
@@ -34677,13 +36527,13 @@ async function reconcileSiteExtractSettlements(limit = 25) {
34677
36527
  }
34678
36528
 
34679
36529
  // src/api/page-diff.ts
34680
- import { createHash as createHash19 } from "crypto";
36530
+ import { createHash as createHash22 } from "crypto";
34681
36531
  import { diffLines } from "diff";
34682
36532
  var MAX_SNAPSHOT_CONTENT_CHARS = 25e4;
34683
36533
  var MAX_DIFF_HUNKS = 200;
34684
36534
  var MAX_DIFF_LINES_PER_RESPONSE = 2e3;
34685
36535
  function sha256Hex(value) {
34686
- return createHash19("sha256").update(value).digest("hex");
36536
+ return createHash22("sha256").update(value).digest("hex");
34687
36537
  }
34688
36538
  function truncateForStorage(value, maxChars = MAX_SNAPSHOT_CONTENT_CHARS) {
34689
36539
  if (value.length <= maxChars) return { value, truncated: false };
@@ -34797,10 +36647,10 @@ async function persistScrapeBody(user, opts) {
34797
36647
  }
34798
36648
 
34799
36649
  // src/api/scrape-image-sink.ts
34800
- import { createHash as createHash20 } from "crypto";
36650
+ import { createHash as createHash23 } from "crypto";
34801
36651
  var MAX_IMAGES_PER_SCRAPE = 25;
34802
36652
  function idempotencyKey2(userId, vault, input) {
34803
- return `scrape-image-${createHash20("sha256").update(`${userId}\0${vault}\0${input.sourceKind}\0${input.sourceUrl}\0${input.imageUrl ?? ""}\0${input.imageBase64 ?? ""}`).digest("hex")}`;
36653
+ return `scrape-image-${createHash23("sha256").update(`${userId}\0${vault}\0${input.sourceKind}\0${input.sourceUrl}\0${input.imageUrl ?? ""}\0${input.imageBase64 ?? ""}`).digest("hex")}`;
34804
36654
  }
34805
36655
  async function persistScrapeImagesToMemory(user, inputs, vault) {
34806
36656
  const selected = inputs.slice(0, MAX_IMAGES_PER_SCRAPE);
@@ -35272,7 +37122,7 @@ function inferredWaybackCapturedAt(content) {
35272
37122
  }
35273
37123
 
35274
37124
  // src/api/connection-memory-import.ts
35275
- import { createHash as createHash21 } from "crypto";
37125
+ import { createHash as createHash24 } from "crypto";
35276
37126
  var CONNECTION_MEMORY_IMPORT_MAX_ARGS_BYTES = 64 * 1024;
35277
37127
  var CONNECTION_MEMORY_IMPORT_MAX_RESULT_BYTES = 1e6;
35278
37128
  var CONNECTION_MEMORY_IMPORT_MAX_STRING_CHARS = 256e3;
@@ -35302,7 +37152,7 @@ function canonicalJson(value) {
35302
37152
  return JSON.stringify(value);
35303
37153
  }
35304
37154
  function sha2562(value) {
35305
- return createHash21("sha256").update(value).digest("hex");
37155
+ return createHash24("sha256").update(value).digest("hex");
35306
37156
  }
35307
37157
  function sensitiveKey(key) {
35308
37158
  const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -36313,7 +38163,7 @@ async function dbUsage(identity, plan) {
36313
38163
  }
36314
38164
 
36315
38165
  // src/api/nango-control.ts
36316
- import { createHash as createHash23, randomUUID as randomUUID22 } from "crypto";
38166
+ import { createHash as createHash26, randomUUID as randomUUID22 } from "crypto";
36317
38167
 
36318
38168
  // src/api/slack-archive-analysis.ts
36319
38169
  function isRecord2(value) {
@@ -36687,7 +38537,7 @@ async function exportSlackChannelPage(input, dependencies) {
36687
38537
  }
36688
38538
 
36689
38539
  // src/api/main-nango-transport.ts
36690
- import { createHash as createHash22, randomUUID as randomUUID21 } from "crypto";
38540
+ import { createHash as createHash25, randomUUID as randomUUID21 } from "crypto";
36691
38541
  import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
36692
38542
 
36693
38543
  // src/api/connected-tool-policy.ts
@@ -36820,9 +38670,9 @@ function arrayAt(payload, key) {
36820
38670
  return Array.isArray(data?.[key]) ? data[key] : [];
36821
38671
  }
36822
38672
  function nangoSecret() {
36823
- const secret2 = process.env.NANGO_SECRET_KEY?.trim();
36824
- if (!secret2) throw new MainNangoTransportError("Nango is not configured.", 503, "nango_not_configured");
36825
- return secret2;
38673
+ const secret = process.env.NANGO_SECRET_KEY?.trim();
38674
+ if (!secret) throw new MainNangoTransportError("Nango is not configured.", 503, "nango_not_configured");
38675
+ return secret;
36826
38676
  }
36827
38677
  function nangoMcpUrl() {
36828
38678
  try {
@@ -37080,7 +38930,7 @@ async function listNangoToolsDirect(identity, connectionId) {
37080
38930
  });
37081
38931
  const readTools = [...policies.values()].filter((policy4) => policy4.classification === "read").map((policy4) => policy4.name);
37082
38932
  const actionTools = [...policies.values()].filter((policy4) => policy4.classification === "action").map((policy4) => policy4.name);
37083
- const revision = createHash22("sha256").update(JSON.stringify(tools.map((tool) => ({ name: tool.name, inputSchema: tool.inputSchema })))).digest("hex");
38933
+ const revision = createHash25("sha256").update(JSON.stringify(tools.map((tool) => ({ name: tool.name, inputSchema: tool.inputSchema })))).digest("hex");
37084
38934
  await updateServiceConnectionTools(connection.id, readTools, actionTools, revision);
37085
38935
  const refreshed = await getOwnedServiceConnection(identity, connection.id);
37086
38936
  return { connection: refreshed ?? { ...connection, readTools, actionTools, toolRevision: revision }, tools };
@@ -37108,7 +38958,7 @@ async function callNangoToolDirect(args) {
37108
38958
  ratePolicyVersion: CONNECTED_USAGE_RATE_POLICY_VERSION
37109
38959
  });
37110
38960
  const requestId = args.requestId?.trim() || randomUUID21();
37111
- const idempotencyKey3 = `main-nango:${createHash22("sha256").update(args.identity.toLowerCase()).update("\0").update(connection.id).update("\0").update(args.tool).update("\0").update(requestId).digest("hex")}`;
38961
+ const idempotencyKey3 = `main-nango:${createHash25("sha256").update(args.identity.toLowerCase()).update("\0").update(connection.id).update("\0").update(args.tool).update("\0").update(requestId).digest("hex")}`;
37112
38962
  const startedAt = /* @__PURE__ */ new Date();
37113
38963
  const started = performance.now();
37114
38964
  let result;
@@ -37133,7 +38983,7 @@ async function callNangoToolDirect(args) {
37133
38983
  toolName: args.tool,
37134
38984
  operationKind: args.operationKind ?? args.classification,
37135
38985
  outcome: providerError ? "error" : "partial",
37136
- requestId: requestId.length <= 200 ? requestId : createHash22("sha256").update(requestId).digest("hex"),
38986
+ requestId: requestId.length <= 200 ? requestId : createHash25("sha256").update(requestId).digest("hex"),
37137
38987
  startedAt: startedAt.toISOString(),
37138
38988
  completedAt: completedAt.toISOString()
37139
38989
  }
@@ -37175,7 +39025,7 @@ async function describeNangoToolDirect(identity, connectionId, toolName) {
37175
39025
  providerContractHash: MAIN_INTEGRATION_CONTRACT_HASH,
37176
39026
  protocolVersion: null,
37177
39027
  schemaSource: "live_tools_list",
37178
- schemaHash: createHash22("sha256").update(JSON.stringify(projected)).digest("hex"),
39028
+ schemaHash: createHash25("sha256").update(JSON.stringify(projected)).digest("hex"),
37179
39029
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
37180
39030
  };
37181
39031
  }
@@ -37321,9 +39171,9 @@ function controlBaseUrl() {
37321
39171
  return (process.env.NANGO_CONTROL_URL?.trim() || DEFAULT_NANGO_CONTROL_URL).replace(/\/$/, "");
37322
39172
  }
37323
39173
  function controlSecret() {
37324
- const secret2 = process.env.SCHEDULE_INTEGRATIONS_SECRET?.trim();
37325
- if (!secret2) throw new NangoControlError("Scheduled service connections are not configured.", 503);
37326
- return secret2;
39174
+ const secret = process.env.SCHEDULE_INTEGRATIONS_SECRET?.trim();
39175
+ if (!secret) throw new NangoControlError("Scheduled service connections are not configured.", 503);
39176
+ return secret;
37327
39177
  }
37328
39178
  function isRecord4(value) {
37329
39179
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -37899,8 +39749,8 @@ async function setScheduleConnectionActionsEnabled(identity, connectionId, enabl
37899
39749
  return data.connection.actionsEnabled === true;
37900
39750
  }
37901
39751
  async function callScheduleConnectionAction(identity, connectionId, input, tool, idempotencyKey3) {
37902
- const requestId = `main-connected-action:${createHash23("sha256").update(identity).update("\0").update(idempotencyKey3?.trim() || randomUUID22()).digest("hex")}`;
37903
- const requestDigest = createHash23("sha256").update(connectionId).update("\0").update(tool?.trim() ?? "").update("\0").update(canonicalJson2(input)).digest("hex");
39752
+ const requestId = `main-connected-action:${createHash26("sha256").update(identity).update("\0").update(idempotencyKey3?.trim() || randomUUID22()).digest("hex")}`;
39753
+ const requestDigest = createHash26("sha256").update(connectionId).update("\0").update(tool?.trim() ?? "").update("\0").update(canonicalJson2(input)).digest("hex");
37904
39754
  if (mainOwnsIntegrations()) {
37905
39755
  const selectedTool = tool?.trim();
37906
39756
  if (!selectedTool) throw new NangoControlError("An action tool is required.", 400, "invalid_request", false);
@@ -38051,7 +39901,7 @@ function canonicalJson2(value) {
38051
39901
  return JSON.stringify(value);
38052
39902
  }
38053
39903
  function projectedToolSchemaHash(tool) {
38054
- return createHash23("sha256").update(canonicalJson2(tool)).digest("hex");
39904
+ return createHash26("sha256").update(canonicalJson2(tool)).digest("hex");
38055
39905
  }
38056
39906
  async function describeNangoTool(identity, connectionId, tool, fresh) {
38057
39907
  if (mainOwnsIntegrations()) {
@@ -38330,7 +40180,7 @@ async function callMainOwnedExportPage(identity, input) {
38330
40180
  }
38331
40181
 
38332
40182
  // src/api/resend-control.ts
38333
- import { createHash as createHash24 } from "crypto";
40183
+ import { createHash as createHash27 } from "crypto";
38334
40184
  var DEFAULT_CONNECTION_CONTROL_URL = "https://mcp-scraper-scheduler.vercel.app";
38335
40185
  var RESEND_PROVIDER_CONFIG_KEY = "resend";
38336
40186
  var RESEND_LOGO_URL = "https://cdn.resend.com/brand/resend-icon-black.svg";
@@ -38370,9 +40220,9 @@ function controlBaseUrl2() {
38370
40220
  return (process.env.SCHEDULE_INTEGRATIONS_CONTROL_URL?.trim() || process.env.NANGO_CONTROL_URL?.trim() || DEFAULT_CONNECTION_CONTROL_URL).replace(/\/$/, "");
38371
40221
  }
38372
40222
  function controlSecret2() {
38373
- const secret2 = process.env.SCHEDULE_INTEGRATIONS_SECRET?.trim();
38374
- if (!secret2) throw new ResendControlError("Resend connections are not configured.", 503);
38375
- return secret2;
40223
+ const secret = process.env.SCHEDULE_INTEGRATIONS_SECRET?.trim();
40224
+ if (!secret) throw new ResendControlError("Resend connections are not configured.", 503);
40225
+ return secret;
38376
40226
  }
38377
40227
  function isRecord5(value) {
38378
40228
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -38589,7 +40439,7 @@ async function callResendRead(identity, connectionId, tool, args) {
38589
40439
  return isRecord5(data) ? data.result ?? data : data;
38590
40440
  }
38591
40441
  async function callResendAction(identity, connectionId, tool, input, idempotencyKey3) {
38592
- const requestId = `main-resend-action:${createHash24("sha256").update(identity).update("\0").update(idempotencyKey3.trim()).digest("hex")}`;
40442
+ const requestId = `main-resend-action:${createHash27("sha256").update(identity).update("\0").update(idempotencyKey3.trim()).digest("hex")}`;
38593
40443
  const body = await controlRequest2("/api/internal/resend/actions/call", {
38594
40444
  method: "POST",
38595
40445
  headers: { "x-request-id": requestId },
@@ -38657,7 +40507,7 @@ async function callResendExportPage(identity, input) {
38657
40507
 
38658
40508
  // src/api/server.ts
38659
40509
  var secureCookies2 = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
38660
- var isProduction2 = secureCookies2;
40510
+ var isProduction = secureCookies2;
38661
40511
  var sessionCookieOptions2 = {
38662
40512
  httpOnly: true,
38663
40513
  secure: secureCookies2,
@@ -38671,7 +40521,7 @@ function configuredOrigins() {
38671
40521
  const trimmed = raw.trim();
38672
40522
  if (trimmed) origins.add(trimmed);
38673
40523
  }
38674
- if (!isProduction2) {
40524
+ if (!isProduction) {
38675
40525
  origins.add("http://localhost:5173");
38676
40526
  origins.add("http://localhost:3000");
38677
40527
  origins.add("http://localhost:63316");
@@ -38697,13 +40547,13 @@ async function enforceRateLimit(c, scope, key, limit, windowSeconds) {
38697
40547
  { "Retry-After": String(result.resetSeconds) }
38698
40548
  );
38699
40549
  }
38700
- var requireAllowedOrigin = createMiddleware4(async (c, next) => {
40550
+ var requireAllowedOrigin = createMiddleware5(async (c, next) => {
38701
40551
  const origin = c.req.header("origin");
38702
40552
  if (!origin) return next();
38703
40553
  if (!configuredOrigins().has(origin)) return c.json({ error: "Origin not allowed" }, 403);
38704
40554
  return next();
38705
40555
  });
38706
- var auth3 = createMiddleware4(async (c, next) => {
40556
+ var auth4 = createMiddleware5(async (c, next) => {
38707
40557
  const key = c.req.header("x-api-key");
38708
40558
  if (!key) return c.json({ error: "Missing API key" }, 401);
38709
40559
  const user = await getUserByApiKey(key);
@@ -38715,7 +40565,7 @@ var auth3 = createMiddleware4(async (c, next) => {
38715
40565
  if (cost && cost.userId == null) cost.userId = Number(refreshed.id);
38716
40566
  return next();
38717
40567
  });
38718
- var sessionAuth = createMiddleware4(async (c, next) => {
40568
+ var sessionAuth = createMiddleware5(async (c, next) => {
38719
40569
  const token4 = getCookie2(c, "session");
38720
40570
  if (!token4) return c.json({ error: "Not authenticated" }, 401);
38721
40571
  const userId = verifySession(token4);
@@ -38727,7 +40577,7 @@ var sessionAuth = createMiddleware4(async (c, next) => {
38727
40577
  c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
38728
40578
  return next();
38729
40579
  });
38730
- var requireIntegrationsTier = createMiddleware4(async (c, next) => {
40580
+ var requireIntegrationsTier = createMiddleware5(async (c, next) => {
38731
40581
  const user = c.get("user");
38732
40582
  if (!user.subscription_tier) {
38733
40583
  return c.json({
@@ -38738,27 +40588,27 @@ var requireIntegrationsTier = createMiddleware4(async (c, next) => {
38738
40588
  }
38739
40589
  return next();
38740
40590
  });
38741
- var requirePaidSchedulingTier = createMiddleware4(async (c, next) => {
40591
+ var requirePaidSchedulingTier = createMiddleware5(async (c, next) => {
38742
40592
  const user = c.get("user");
38743
40593
  if (!hasPaidSchedulingAccess(user)) return c.json(paidPlanRequiredPayload(), 403);
38744
40594
  return next();
38745
40595
  });
38746
- var app = new Hono35();
40596
+ var app = new Hono36();
38747
40597
  var deploymentProfile = resolveDeploymentProfile();
38748
40598
  var STRIPE_API_VERSION = "2026-02-25.clover";
38749
40599
  function permitsLocalApiRequest(request) {
38750
40600
  return (deploymentProfile === "development" || deploymentProfile === "test" || process.env.MCP_SCRAPER_ALLOW_PRIVATE_NETWORK === "1") && request.headers.get("x-mcp-scraper-local-network") === "1" && isLoopbackBaseUrl(request.url);
38751
40601
  }
38752
40602
  function requireStripeSecret() {
38753
- const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
38754
- if (secret2) return secret2;
40603
+ const secret = process.env.STRIPE_SECRET_KEY?.trim();
40604
+ if (secret) return secret;
38755
40605
  if (process.env.NODE_ENV === "production" || process.env.VERCEL === "1") return null;
38756
40606
  return null;
38757
40607
  }
38758
40608
  async function createSignupStripeCustomer(email) {
38759
- const secret2 = requireStripeSecret();
38760
- if (!secret2) return null;
38761
- const stripeClient = new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
40609
+ const secret = requireStripeSecret();
40610
+ if (!secret) return null;
40611
+ const stripeClient = new Stripe2(secret, { apiVersion: STRIPE_API_VERSION });
38762
40612
  const customer = await stripeClient.customers.create({
38763
40613
  email,
38764
40614
  description: "MCP Scraper free account",
@@ -38909,9 +40759,9 @@ app.post("/account/delete", requireAllowedOrigin, sessionAuth, async (c) => {
38909
40759
  return c.json({ error: "Could not remove every connected account \u2014 please retry before deleting the account." }, 502);
38910
40760
  }
38911
40761
  }
38912
- const secret2 = requireStripeSecret();
38913
- if (secret2 && user.stripe_customer_id) {
38914
- const stripeClient = new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
40762
+ const secret = requireStripeSecret();
40763
+ if (secret && user.stripe_customer_id) {
40764
+ const stripeClient = new Stripe2(secret, { apiVersion: STRIPE_API_VERSION });
38915
40765
  const subs = await stripeClient.subscriptions.list({ customer: user.stripe_customer_id, status: "all", limit: 100 });
38916
40766
  for (const sub of subs.data) {
38917
40767
  if (sub.status === "active" || sub.status === "trialing" || sub.status === "past_due") {
@@ -39048,7 +40898,7 @@ function maybeProvisionInBackground(user, vaultCount) {
39048
40898
  }
39049
40899
  })();
39050
40900
  }
39051
- app.get("/memory/overview", auth3, async (c) => {
40901
+ app.get("/memory/overview", auth4, async (c) => {
39052
40902
  const user = c.get("user");
39053
40903
  const identity = resolveMemoryIdentity(user);
39054
40904
  const plan = memoryPlanForReads(user);
@@ -39076,7 +40926,7 @@ app.get("/memory/overview", auth3, async (c) => {
39076
40926
  return c.json({ ok: false, error: message }, 502);
39077
40927
  }
39078
40928
  });
39079
- app.get("/memory/vaults", auth3, async (c) => {
40929
+ app.get("/memory/vaults", auth4, async (c) => {
39080
40930
  const user = c.get("user");
39081
40931
  try {
39082
40932
  const vaults = await dbListVaults(resolveMemoryIdentity(user));
@@ -39086,7 +40936,7 @@ app.get("/memory/vaults", auth3, async (c) => {
39086
40936
  return c.json({ ok: false, error: err instanceof Error ? err.message : "could not load vaults" }, 502);
39087
40937
  }
39088
40938
  });
39089
- app.post("/memory/mcp-call", auth3, async (c) => {
40939
+ app.post("/memory/mcp-call", auth4, async (c) => {
39090
40940
  const user = c.get("user");
39091
40941
  const body = await c.req.json().catch(() => ({}));
39092
40942
  if (!body.toolName) return c.json({ ok: false, error: "toolName is required" }, 400);
@@ -39107,13 +40957,13 @@ app.post("/memory/mcp-call", auth3, async (c) => {
39107
40957
  const result = await memoryCall(body.toolName, body.args ?? {}, key);
39108
40958
  return c.json(result);
39109
40959
  });
39110
- app.get("/memory/approved-senders", auth3, async (c) => {
40960
+ app.get("/memory/approved-senders", auth4, async (c) => {
39111
40961
  const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
39112
40962
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 502);
39113
40963
  const r = await memoryCall("listApprovedSendersTool", {}, key);
39114
40964
  return c.json(r);
39115
40965
  });
39116
- app.post("/memory/approved-senders", auth3, async (c) => {
40966
+ app.post("/memory/approved-senders", auth4, async (c) => {
39117
40967
  const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
39118
40968
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 502);
39119
40969
  const body = await c.req.json().catch(() => ({}));
@@ -39122,14 +40972,14 @@ app.post("/memory/approved-senders", auth3, async (c) => {
39122
40972
  const r = await memoryCall("approveSenderTool", { senderIdentity }, key);
39123
40973
  return c.json(r);
39124
40974
  });
39125
- app.delete("/memory/approved-senders/:identity", auth3, async (c) => {
40975
+ app.delete("/memory/approved-senders/:identity", auth4, async (c) => {
39126
40976
  const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
39127
40977
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 502);
39128
40978
  const senderIdentity = c.req.param("identity");
39129
40979
  const r = await memoryCall("removeApprovedSenderTool", { senderIdentity }, key);
39130
40980
  return c.json(r);
39131
40981
  });
39132
- app.get("/memory/connect", auth3, async (c) => {
40982
+ app.get("/memory/connect", auth4, async (c) => {
39133
40983
  const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
39134
40984
  if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
39135
40985
  const url = "https://memory.mcpscraper.dev/mcp";
@@ -39144,7 +40994,7 @@ app.get("/memory/connect", auth3, async (c) => {
39144
40994
  const codeCommand = `claude mcp add --transport http memory ${url} --header "Authorization: Bearer ${key}"`;
39145
40995
  return c.json({ ok: true, url, key, config, codeCommand });
39146
40996
  });
39147
- app.post("/memory/vault", auth3, async (c) => {
40997
+ app.post("/memory/vault", auth4, async (c) => {
39148
40998
  const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
39149
40999
  if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
39150
41000
  const body = await c.req.json().catch(() => ({}));
@@ -39153,7 +41003,7 @@ app.post("/memory/vault", auth3, async (c) => {
39153
41003
  const result = await memoryCall("addVaultTool", { vault }, key);
39154
41004
  return c.json(result, result.ok ? 200 : 502);
39155
41005
  });
39156
- app.get("/memory/notes", auth3, async (c) => {
41006
+ app.get("/memory/notes", auth4, async (c) => {
39157
41007
  const user = c.get("user");
39158
41008
  const identity = resolveMemoryIdentity(user);
39159
41009
  try {
@@ -39169,7 +41019,7 @@ app.get("/memory/notes", auth3, async (c) => {
39169
41019
  return c.json({ ok: false, error: err instanceof Error ? err.message : "could not load notes" }, 502);
39170
41020
  }
39171
41021
  });
39172
- app.get("/memory/note", auth3, async (c) => {
41022
+ app.get("/memory/note", auth4, async (c) => {
39173
41023
  const user = c.get("user");
39174
41024
  const vault = c.req.query("vault")?.trim();
39175
41025
  const path5 = c.req.query("path");
@@ -39183,7 +41033,7 @@ app.get("/memory/note", auth3, async (c) => {
39183
41033
  return c.json({ ok: false, error: err instanceof Error ? err.message : "could not load note" }, 502);
39184
41034
  }
39185
41035
  });
39186
- app.get("/memory/universe", auth3, async (c) => {
41036
+ app.get("/memory/universe", auth4, async (c) => {
39187
41037
  const user = c.get("user");
39188
41038
  const includeConnections = c.req.query("includeConnections") === "1";
39189
41039
  try {
@@ -39193,7 +41043,7 @@ app.get("/memory/universe", auth3, async (c) => {
39193
41043
  return c.json({ ok: false, error: err instanceof Error ? err.message : "could not build universe" }, 502);
39194
41044
  }
39195
41045
  });
39196
- app.put("/memory/note", auth3, async (c) => {
41046
+ app.put("/memory/note", auth4, async (c) => {
39197
41047
  const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
39198
41048
  if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
39199
41049
  const body = await c.req.json().catch(() => ({}));
@@ -39201,7 +41051,7 @@ app.put("/memory/note", auth3, async (c) => {
39201
41051
  const result = await memoryCall("putTool", { vault: body.vault, path: body.path, title: body.title, content: body.content }, key);
39202
41052
  return c.json(result, result.ok ? 200 : 502);
39203
41053
  });
39204
- app.delete("/memory/note", auth3, async (c) => {
41054
+ app.delete("/memory/note", auth4, async (c) => {
39205
41055
  const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
39206
41056
  if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
39207
41057
  const vault = c.req.query("vault");
@@ -39210,7 +41060,7 @@ app.delete("/memory/note", auth3, async (c) => {
39210
41060
  const result = await memoryCall("deleteNoteTool", { vault, path: path5 }, key);
39211
41061
  return c.json(result, result.ok ? 200 : 502);
39212
41062
  });
39213
- app.get("/memory/note/download", auth3, async (c) => {
41063
+ app.get("/memory/note/download", auth4, async (c) => {
39214
41064
  const user = c.get("user");
39215
41065
  const vault = c.req.query("vault")?.trim();
39216
41066
  const path5 = c.req.query("path");
@@ -39227,7 +41077,7 @@ app.get("/memory/note/download", auth3, async (c) => {
39227
41077
  }
39228
41078
  });
39229
41079
  });
39230
- app.get("/memory/storage", auth3, async (c) => {
41080
+ app.get("/memory/storage", auth4, async (c) => {
39231
41081
  const user = c.get("user");
39232
41082
  try {
39233
41083
  const result = await dbStorage(resolveMemoryIdentity(user), memoryPlanForReads(user));
@@ -39236,7 +41086,7 @@ app.get("/memory/storage", auth3, async (c) => {
39236
41086
  return c.json({ ok: false, error: err instanceof Error ? err.message : "could not load storage" }, 502);
39237
41087
  }
39238
41088
  });
39239
- app.get("/memory/billing", auth3, async (c) => {
41089
+ app.get("/memory/billing", auth4, async (c) => {
39240
41090
  const user = c.get("user");
39241
41091
  const plans = Object.values(MEMORY_PLANS).map((p) => ({ plan: p.plan, label: p.label, price_id: p.price_id, interval: p.interval, monthly_usd: p.monthly_usd, quota_gb: p.quota_bytes / 1e9 }));
39242
41092
  return c.json({
@@ -39247,7 +41097,7 @@ app.get("/memory/billing", auth3, async (c) => {
39247
41097
  plans
39248
41098
  });
39249
41099
  });
39250
- app.post("/memory/checkout", auth3, async (c) => {
41100
+ app.post("/memory/checkout", auth4, async (c) => {
39251
41101
  try {
39252
41102
  const user = c.get("user");
39253
41103
  const body = await c.req.json().catch(() => ({}));
@@ -39259,9 +41109,9 @@ app.post("/memory/checkout", auth3, async (c) => {
39259
41109
  error_code: "memory_already_included"
39260
41110
  }, 409);
39261
41111
  }
39262
- const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
39263
- if (!secret2) return c.json({ error: "Stripe is not configured." }, 503);
39264
- const stripeClient = new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
41112
+ const secret = process.env.STRIPE_SECRET_KEY?.trim();
41113
+ if (!secret) return c.json({ error: "Stripe is not configured." }, 503);
41114
+ const stripeClient = new Stripe2(secret, { apiVersion: STRIPE_API_VERSION });
39265
41115
  let customerId = user.stripe_customer_id;
39266
41116
  if (!customerId) {
39267
41117
  const customer = await stripeClient.customers.create({ email: user.email });
@@ -39290,13 +41140,13 @@ app.post("/memory/checkout", auth3, async (c) => {
39290
41140
  return c.json({ error: message }, 500);
39291
41141
  }
39292
41142
  });
39293
- app.post("/memory/portal", auth3, async (c) => {
41143
+ app.post("/memory/portal", auth4, async (c) => {
39294
41144
  try {
39295
41145
  const user = c.get("user");
39296
41146
  if (!user.stripe_customer_id) return c.json({ error: "No billing account yet \u2014 subscribe first." }, 409);
39297
- const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
39298
- if (!secret2) return c.json({ error: "Stripe is not configured." }, 503);
39299
- const stripeClient = new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
41147
+ const secret = process.env.STRIPE_SECRET_KEY?.trim();
41148
+ if (!secret) return c.json({ error: "Stripe is not configured." }, 503);
41149
+ const stripeClient = new Stripe2(secret, { apiVersion: STRIPE_API_VERSION });
39300
41150
  const session = await stripeClient.billingPortal.sessions.create({
39301
41151
  customer: user.stripe_customer_id,
39302
41152
  return_url: `${appOrigin()}/memory`
@@ -39308,7 +41158,7 @@ app.post("/memory/portal", auth3, async (c) => {
39308
41158
  return c.json({ error: message }, 500);
39309
41159
  }
39310
41160
  });
39311
- app.post("/schedule-checkout", auth3, async (c) => {
41161
+ app.post("/schedule-checkout", auth4, async (c) => {
39312
41162
  return c.json({
39313
41163
  error_code: "scheduled_actions_now_credit_metered",
39314
41164
  error: "Scheduled Actions is included and no longer requires a separate subscription.",
@@ -39468,7 +41318,7 @@ function renderResendOAuthResult(ok) {
39468
41318
  const message = ok ? "Your Resend account is connected privately to MCP Scraper. You can close this tab." : "The connection could not be completed. Close this tab and try again from Integrations.";
39469
41319
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="referrer" content="no-referrer"><title>${title}</title><style>:root{color-scheme:light dark}*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;background:#f5f2ee;color:#26211d;font-family:ui-sans-serif,system-ui,-apple-system,sans-serif}.card{width:min(100%,440px);padding:32px;border:1px solid #d8d0c8;border-radius:16px;background:#fff;box-shadow:0 18px 55px rgba(31,24,18,.1)}.mark{width:38px;height:38px;display:grid;place-items:center;border-radius:10px;background:#26211d;color:#fff;font-size:20px;font-weight:700}h1{margin:18px 0 8px;font-size:24px;line-height:1.15}p{margin:0;color:#71675f;line-height:1.55}a{display:inline-flex;margin-top:20px;color:#b44d36;font-weight:650;text-underline-offset:3px}@media(prefers-color-scheme:dark){body{background:#171411;color:#f4eee8}.card{background:#211d19;border-color:#3b342d}.mark{background:#f4eee8;color:#211d19}p{color:#b9aea5}}</style></head><body><main class="card"><div class="mark" aria-hidden="true">R</div><h1>${title}</h1><p>${message}</p><a href="/dashboard/integrations">Return to Integrations</a></main><script>if(window.opener){try{window.opener.postMessage({type:'mcp-scraper:resend-oauth',ok:${ok}},window.location.origin)}catch(e){}}setTimeout(function(){window.close()},1200)</script></body></html>`;
39470
41320
  }
39471
- app.get("/schedule-status", auth3, requirePaidSchedulingTier, async (c) => {
41321
+ app.get("/schedule-status", auth4, requirePaidSchedulingTier, async (c) => {
39472
41322
  try {
39473
41323
  const user = c.get("user");
39474
41324
  const { key, error } = await getOrCreateUserMemoryKey(user);
@@ -39488,14 +41338,14 @@ app.get("/schedule-status", auth3, requirePaidSchedulingTier, async (c) => {
39488
41338
  return c.json({ error: message }, 500);
39489
41339
  }
39490
41340
  });
39491
- app.get("/schedule-connection-catalog", auth3, async (c) => {
41341
+ app.get("/schedule-connection-catalog", auth4, async (c) => {
39492
41342
  try {
39493
41343
  return c.json({ ok: true, catalog: await combinedConnectionCatalog() });
39494
41344
  } catch (err) {
39495
41345
  return scheduleConnectionError(c, err, "Unable to load the service connection catalog.");
39496
41346
  }
39497
41347
  });
39498
- app.get("/schedule-connections", auth3, async (c) => {
41348
+ app.get("/schedule-connections", auth4, async (c) => {
39499
41349
  try {
39500
41350
  const user = c.get("user");
39501
41351
  const connections = await combinedConnections(user.email);
@@ -39511,7 +41361,7 @@ app.get("/schedule-connections", auth3, async (c) => {
39511
41361
  return scheduleConnectionError(c, err, "Unable to load service connections.");
39512
41362
  }
39513
41363
  });
39514
- app.post("/schedule-connections/billing/reconcile", auth3, requireIntegrationsTier, async (c) => {
41364
+ app.post("/schedule-connections/billing/reconcile", auth4, requireIntegrationsTier, async (c) => {
39515
41365
  const user = c.get("user");
39516
41366
  try {
39517
41367
  return c.json({
@@ -39522,7 +41372,7 @@ app.post("/schedule-connections/billing/reconcile", auth3, requireIntegrationsTi
39522
41372
  return scheduleConnectionError(c, err, "Unable to synchronize connected-account billing.");
39523
41373
  }
39524
41374
  });
39525
- app.post("/schedule-connections/session", auth3, requireIntegrationsTier, async (c) => {
41375
+ app.post("/schedule-connections/session", auth4, requireIntegrationsTier, async (c) => {
39526
41376
  const user = c.get("user");
39527
41377
  const body = await c.req.json().catch(() => ({}));
39528
41378
  const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
@@ -39537,7 +41387,7 @@ app.post("/schedule-connections/session", auth3, requireIntegrationsTier, async
39537
41387
  return scheduleConnectionError(c, err, "Unable to start the service connection flow.");
39538
41388
  }
39539
41389
  });
39540
- app.post("/schedule-connections/:id/reconnect-session", auth3, requireIntegrationsTier, async (c) => {
41390
+ app.post("/schedule-connections/:id/reconnect-session", auth4, requireIntegrationsTier, async (c) => {
39541
41391
  const user = c.get("user");
39542
41392
  const body = await c.req.json().catch(() => ({}));
39543
41393
  const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
@@ -39548,7 +41398,7 @@ app.post("/schedule-connections/:id/reconnect-session", auth3, requireIntegratio
39548
41398
  return scheduleConnectionError(c, err, "Unable to start the service reconnection flow.");
39549
41399
  }
39550
41400
  });
39551
- app.delete("/schedule-connections/:id", auth3, async (c) => {
41401
+ app.delete("/schedule-connections/:id", auth4, async (c) => {
39552
41402
  const user = c.get("user");
39553
41403
  const body = await c.req.json().catch(() => ({}));
39554
41404
  const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
@@ -39574,7 +41424,7 @@ app.delete("/schedule-connections/:id", auth3, async (c) => {
39574
41424
  return scheduleConnectionError(c, err, "Unable to disconnect this service connection.");
39575
41425
  }
39576
41426
  });
39577
- app.post("/schedule-connections/:id/enable-actions", auth3, requireIntegrationsTier, async (c) => {
41427
+ app.post("/schedule-connections/:id/enable-actions", auth4, requireIntegrationsTier, async (c) => {
39578
41428
  const user = c.get("user");
39579
41429
  const body = await c.req.json().catch(() => ({}));
39580
41430
  if (typeof body.enabled !== "boolean") return c.json({ ok: false, error: "enabled must be a boolean." }, 400);
@@ -39586,7 +41436,7 @@ app.post("/schedule-connections/:id/enable-actions", auth3, requireIntegrationsT
39586
41436
  return scheduleConnectionError(c, err, "Unable to update this connection's action setting.");
39587
41437
  }
39588
41438
  });
39589
- app.post("/schedule-connections/:id/test", auth3, requireIntegrationsTier, async (c) => {
41439
+ app.post("/schedule-connections/:id/test", auth4, requireIntegrationsTier, async (c) => {
39590
41440
  const user = c.get("user");
39591
41441
  const body = await c.req.json().catch(() => ({}));
39592
41442
  const providerConfigKey = providerConfigKeyFrom(body.providerConfigKey);
@@ -39676,7 +41526,7 @@ app.get("/oauth/resend/callback", async (c) => {
39676
41526
  return c.html(renderResendOAuthResult(false), 502);
39677
41527
  }
39678
41528
  });
39679
- app.post("/schedule-connections/actions/slack/send-message", auth3, requireIntegrationsTier, async (c) => {
41529
+ app.post("/schedule-connections/actions/slack/send-message", auth4, requireIntegrationsTier, async (c) => {
39680
41530
  const user = c.get("user");
39681
41531
  const body = await c.req.json().catch(() => ({}));
39682
41532
  const connectionId = providerConfigKeyFrom(body.connectionId);
@@ -39696,7 +41546,7 @@ app.post("/schedule-connections/actions/slack/send-message", auth3, requireInteg
39696
41546
  return scheduleConnectionError(c, err, "Unable to send the Slack message.");
39697
41547
  }
39698
41548
  });
39699
- app.post("/schedule-connections/actions/gmail/send-message", auth3, requireIntegrationsTier, async (c) => {
41549
+ app.post("/schedule-connections/actions/gmail/send-message", auth4, requireIntegrationsTier, async (c) => {
39700
41550
  const user = c.get("user");
39701
41551
  const body = await c.req.json().catch(() => ({}));
39702
41552
  const connectionId = providerConfigKeyFrom(body.connectionId);
@@ -39732,7 +41582,7 @@ function parseEmailAddress(headerValue) {
39732
41582
  const bare = value.match(/^([^\s<>]+@[^\s<>]+)$/);
39733
41583
  return bare ? { email: bare[1].trim().toLowerCase(), name: null } : null;
39734
41584
  }
39735
- app.post("/schedule-connections/actions/gmail/search-contacts", auth3, requireIntegrationsTier, async (c) => {
41585
+ app.post("/schedule-connections/actions/gmail/search-contacts", auth4, requireIntegrationsTier, async (c) => {
39736
41586
  const user = c.get("user");
39737
41587
  const body = await c.req.json().catch(() => ({}));
39738
41588
  const connectionId = providerConfigKeyFrom(body.connectionId);
@@ -39786,7 +41636,7 @@ app.post("/schedule-connections/actions/gmail/search-contacts", auth3, requireIn
39786
41636
  return scheduleConnectionError(c, err, "Unable to search Gmail contacts.");
39787
41637
  }
39788
41638
  });
39789
- app.post("/schedule-connections/actions/google-calendar/create-event", auth3, requireIntegrationsTier, async (c) => {
41639
+ app.post("/schedule-connections/actions/google-calendar/create-event", auth4, requireIntegrationsTier, async (c) => {
39790
41640
  const user = c.get("user");
39791
41641
  const body = await c.req.json().catch(() => ({}));
39792
41642
  const connectionId = providerConfigKeyFrom(body.connectionId);
@@ -39816,7 +41666,7 @@ app.post("/schedule-connections/actions/google-calendar/create-event", auth3, re
39816
41666
  return scheduleConnectionError(c, err, "Unable to create the calendar event.");
39817
41667
  }
39818
41668
  });
39819
- app.post("/schedule-connections/actions/zoom/create-meeting", auth3, requireIntegrationsTier, async (c) => {
41669
+ app.post("/schedule-connections/actions/zoom/create-meeting", auth4, requireIntegrationsTier, async (c) => {
39820
41670
  const user = c.get("user");
39821
41671
  const body = await c.req.json().catch(() => ({}));
39822
41672
  const connectionId = providerConfigKeyFrom(body.connectionId);
@@ -39842,7 +41692,7 @@ app.post("/schedule-connections/actions/zoom/create-meeting", auth3, requireInte
39842
41692
  return scheduleConnectionError(c, err, "Unable to create the Zoom meeting.");
39843
41693
  }
39844
41694
  });
39845
- app.post("/schedule-connections/actions/read", auth3, requireIntegrationsTier, async (c) => {
41695
+ app.post("/schedule-connections/actions/read", auth4, requireIntegrationsTier, async (c) => {
39846
41696
  const user = c.get("user");
39847
41697
  const body = await c.req.json().catch(() => ({}));
39848
41698
  const connectionId = providerConfigKeyFrom(body.connectionId);
@@ -39859,7 +41709,7 @@ app.post("/schedule-connections/actions/read", auth3, requireIntegrationsTier, a
39859
41709
  return scheduleConnectionError(c, err, "Unable to read from this connection.");
39860
41710
  }
39861
41711
  });
39862
- app.post("/schedule-connections/actions/import-memory", auth3, requireIntegrationsTier, async (c) => {
41712
+ app.post("/schedule-connections/actions/import-memory", auth4, requireIntegrationsTier, async (c) => {
39863
41713
  const user = c.get("user");
39864
41714
  const body = await c.req.json().catch(() => ({}));
39865
41715
  const connectionId = providerConfigKeyFrom(body.connectionId);
@@ -39916,7 +41766,7 @@ app.post("/schedule-connections/actions/import-memory", auth3, requireIntegratio
39916
41766
  return scheduleConnectionError(c, err, "Unable to import this connected-service snapshot into Memory.");
39917
41767
  }
39918
41768
  });
39919
- app.post("/schedule-connections/actions/describe", auth3, requireIntegrationsTier, async (c) => {
41769
+ app.post("/schedule-connections/actions/describe", auth4, requireIntegrationsTier, async (c) => {
39920
41770
  const user = c.get("user");
39921
41771
  const body = await c.req.json().catch(() => ({}));
39922
41772
  const connectionId = providerConfigKeyFrom(body.connectionId);
@@ -39933,7 +41783,7 @@ app.post("/schedule-connections/actions/describe", auth3, requireIntegrationsTie
39933
41783
  return scheduleConnectionError(c, err, "Unable to describe this connection tool.");
39934
41784
  }
39935
41785
  });
39936
- app.post("/schedule-connections/actions/export", auth3, requireIntegrationsTier, async (c) => {
41786
+ app.post("/schedule-connections/actions/export", auth4, requireIntegrationsTier, async (c) => {
39937
41787
  const user = c.get("user");
39938
41788
  const body = await c.req.json().catch(() => ({}));
39939
41789
  const connectionId = providerConfigKeyFrom(body.connectionId);
@@ -40020,7 +41870,7 @@ app.post("/schedule-connections/actions/export", auth3, requireIntegrationsTier,
40020
41870
  return scheduleConnectionError(c, err, "Unable to export this connected service.");
40021
41871
  }
40022
41872
  });
40023
- app.post("/schedule-connections/actions/export-search-console-table", auth3, requireIntegrationsTier, async (c) => {
41873
+ app.post("/schedule-connections/actions/export-search-console-table", auth4, requireIntegrationsTier, async (c) => {
40024
41874
  const user = c.get("user");
40025
41875
  const body = await c.req.json().catch(() => ({}));
40026
41876
  try {
@@ -40045,7 +41895,7 @@ app.post("/schedule-connections/actions/export-search-console-table", auth3, req
40045
41895
  return c.json({ ok: false, error: "Unable to export the persisted Search Console table." }, 502);
40046
41896
  }
40047
41897
  });
40048
- app.post("/schedule-connections/actions/export-download", auth3, requireIntegrationsTier, async (c) => {
41898
+ app.post("/schedule-connections/actions/export-download", auth4, requireIntegrationsTier, async (c) => {
40049
41899
  const user = c.get("user");
40050
41900
  const body = await c.req.json().catch(() => ({}));
40051
41901
  const artifactId = typeof body.artifactId === "string" ? body.artifactId.trim() : "";
@@ -40062,7 +41912,7 @@ app.post("/schedule-connections/actions/export-download", auth3, requireIntegrat
40062
41912
  return c.json({ ok: false, error: "Unable to renew this artifact download." }, 502);
40063
41913
  }
40064
41914
  });
40065
- app.post("/schedule-connections/actions/call", auth3, requireIntegrationsTier, async (c) => {
41915
+ app.post("/schedule-connections/actions/call", auth4, requireIntegrationsTier, async (c) => {
40066
41916
  const user = c.get("user");
40067
41917
  const body = await c.req.json().catch(() => ({}));
40068
41918
  const connectionId = providerConfigKeyFrom(body.connectionId);
@@ -40102,7 +41952,7 @@ app.post("/integrations/actions/read", (c) => forwardIntegrationAlias(c, "/sched
40102
41952
  app.post("/integrations/actions/describe", (c) => forwardIntegrationAlias(c, "/schedule-connections/actions/describe"));
40103
41953
  app.post("/integrations/actions/call", (c) => forwardIntegrationAlias(c, "/schedule-connections/actions/call"));
40104
41954
  app.post("/integrations/actions/export", (c) => forwardIntegrationAlias(c, "/schedule-connections/actions/export"));
40105
- app.post("/schedule-link", auth3, requirePaidSchedulingTier, async (c) => {
41955
+ app.post("/schedule-link", auth4, requirePaidSchedulingTier, async (c) => {
40106
41956
  try {
40107
41957
  const user = c.get("user");
40108
41958
  const credentials = await syncScheduledActionCredentials(user);
@@ -40119,7 +41969,7 @@ app.post("/schedule-link", auth3, requirePaidSchedulingTier, async (c) => {
40119
41969
  return c.json({ error: message }, 500);
40120
41970
  }
40121
41971
  });
40122
- app.get("/schedule-actions", auth3, async (c) => {
41972
+ app.get("/schedule-actions", auth4, async (c) => {
40123
41973
  const user = c.get("user");
40124
41974
  const { key, error } = await getOrCreateUserMemoryKey(user);
40125
41975
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
@@ -40146,7 +41996,7 @@ app.get("/schedule-actions", auth3, async (c) => {
40146
41996
  });
40147
41997
  }
40148
41998
  });
40149
- app.post("/schedule-actions", auth3, requirePaidSchedulingTier, async (c) => {
41999
+ app.post("/schedule-actions", auth4, requirePaidSchedulingTier, async (c) => {
40150
42000
  const user = c.get("user");
40151
42001
  const credentials = await syncScheduledActionCredentials(user);
40152
42002
  if (!credentials.ok) {
@@ -40191,7 +42041,7 @@ app.post("/schedule-actions", auth3, requirePaidSchedulingTier, async (c) => {
40191
42041
  return response;
40192
42042
  }
40193
42043
  });
40194
- app.post("/schedule-actions/propose", auth3, async (c) => {
42044
+ app.post("/schedule-actions/propose", auth4, async (c) => {
40195
42045
  const user = c.get("user");
40196
42046
  const { key, error } = await getOrCreateUserMemoryKey(user);
40197
42047
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
@@ -40199,7 +42049,7 @@ app.post("/schedule-actions/propose", auth3, async (c) => {
40199
42049
  const r = await memoryCall("proposeScheduledActionTool", body, key);
40200
42050
  return c.json(r);
40201
42051
  });
40202
- app.get("/schedule-actions/:id/connections", auth3, async (c) => {
42052
+ app.get("/schedule-actions/:id/connections", auth4, async (c) => {
40203
42053
  try {
40204
42054
  const user = c.get("user");
40205
42055
  const scheduleActionId = c.req.param("id");
@@ -40209,7 +42059,7 @@ app.get("/schedule-actions/:id/connections", auth3, async (c) => {
40209
42059
  return scheduleConnectionError(c, err, "Unable to load scheduled action service connections.");
40210
42060
  }
40211
42061
  });
40212
- app.put("/schedule-actions/:id/connections", auth3, requireIntegrationsTier, async (c) => {
42062
+ app.put("/schedule-actions/:id/connections", auth4, requireIntegrationsTier, async (c) => {
40213
42063
  const user = c.get("user");
40214
42064
  const body = await c.req.json().catch(() => ({}));
40215
42065
  let connections;
@@ -40236,7 +42086,7 @@ app.put("/schedule-actions/:id/connections", auth3, requireIntegrationsTier, asy
40236
42086
  return scheduleConnectionError(c, err, "Unable to update scheduled action service connections.");
40237
42087
  }
40238
42088
  });
40239
- app.get("/schedule-actions/:id/runs", auth3, async (c) => {
42089
+ app.get("/schedule-actions/:id/runs", auth4, async (c) => {
40240
42090
  const user = c.get("user");
40241
42091
  const { key, error } = await getOrCreateUserMemoryKey(user);
40242
42092
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
@@ -40245,7 +42095,7 @@ app.get("/schedule-actions/:id/runs", auth3, async (c) => {
40245
42095
  const r = await memoryCall("listScheduledActionRunsTool", { id: c.req.param("id"), limit }, key);
40246
42096
  return c.json(r);
40247
42097
  });
40248
- app.put("/schedule-actions/:id", auth3, requirePaidSchedulingTier, async (c) => {
42098
+ app.put("/schedule-actions/:id", auth4, requirePaidSchedulingTier, async (c) => {
40249
42099
  const user = c.get("user");
40250
42100
  const { key, error } = await getOrCreateUserMemoryKey(user);
40251
42101
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
@@ -40260,21 +42110,21 @@ app.put("/schedule-actions/:id", auth3, requirePaidSchedulingTier, async (c) =>
40260
42110
  const r = await memoryCall("updateScheduledActionTool", { id: c.req.param("id"), ...updates }, key);
40261
42111
  return c.json(r);
40262
42112
  });
40263
- app.post("/schedule-actions/:id/pause", auth3, async (c) => {
42113
+ app.post("/schedule-actions/:id/pause", auth4, async (c) => {
40264
42114
  const user = c.get("user");
40265
42115
  const { key, error } = await getOrCreateUserMemoryKey(user);
40266
42116
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
40267
42117
  const r = await memoryCall("pauseScheduledActionTool", { id: c.req.param("id") }, key);
40268
42118
  return c.json(r);
40269
42119
  });
40270
- app.post("/schedule-actions/:id/resume", auth3, requirePaidSchedulingTier, async (c) => {
42120
+ app.post("/schedule-actions/:id/resume", auth4, requirePaidSchedulingTier, async (c) => {
40271
42121
  const user = c.get("user");
40272
42122
  const { key, error } = await getOrCreateUserMemoryKey(user);
40273
42123
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
40274
42124
  const r = await memoryCall("resumeScheduledActionTool", { id: c.req.param("id") }, key);
40275
42125
  return c.json(r);
40276
42126
  });
40277
- app.delete("/schedule-actions/:id", auth3, async (c) => {
42127
+ app.delete("/schedule-actions/:id", auth4, async (c) => {
40278
42128
  const user = c.get("user");
40279
42129
  const { key, error } = await getOrCreateUserMemoryKey(user);
40280
42130
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
@@ -40289,7 +42139,7 @@ app.delete("/schedule-actions/:id", auth3, async (c) => {
40289
42139
  return c.json({ ...r, connectionBindingCleanupPending: true });
40290
42140
  }
40291
42141
  });
40292
- app.post("/schedule-link/revoke", auth3, async (c) => {
42142
+ app.post("/schedule-link/revoke", auth4, async (c) => {
40293
42143
  try {
40294
42144
  const user = c.get("user");
40295
42145
  const { key, error } = await getOrCreateUserMemoryKey(user);
@@ -40302,7 +42152,7 @@ app.post("/schedule-link/revoke", auth3, async (c) => {
40302
42152
  return c.json({ error: message }, 500);
40303
42153
  }
40304
42154
  });
40305
- app.post("/support/submit", auth3, async (c) => {
42155
+ app.post("/support/submit", auth4, async (c) => {
40306
42156
  try {
40307
42157
  const user = c.get("user");
40308
42158
  const body = await c.req.json().catch(() => ({}));
@@ -40330,7 +42180,7 @@ app.post("/support/submit", auth3, async (c) => {
40330
42180
  return c.json({ error: message }, 500);
40331
42181
  }
40332
42182
  });
40333
- app.get("/memory/usage", auth3, async (c) => {
42183
+ app.get("/memory/usage", auth4, async (c) => {
40334
42184
  const user = c.get("user");
40335
42185
  try {
40336
42186
  const result = await dbUsage(resolveMemoryIdentity(user), memoryPlanForReads(user));
@@ -40383,7 +42233,7 @@ async function checkHarvestLimits(user, reuseLockId) {
40383
42233
  }
40384
42234
  return null;
40385
42235
  }
40386
- app.post("/harvest", auth3, async (c) => {
42236
+ app.post("/harvest", auth4, async (c) => {
40387
42237
  const user = c.get("user");
40388
42238
  const raw = await c.req.json().catch(() => ({}));
40389
42239
  const bodyResult = HarvestBodySchema.safeParse(raw);
@@ -40466,7 +42316,7 @@ app.post("/harvest", auth3, async (c) => {
40466
42316
  }
40467
42317
  return c.json({ job_id: jobId2, status: "pending" }, 202);
40468
42318
  });
40469
- app.post("/harvest/sync", auth3, async (c) => {
42319
+ app.post("/harvest/sync", auth4, async (c) => {
40470
42320
  const user = c.get("user");
40471
42321
  const raw = await c.req.json().catch(() => ({}));
40472
42322
  const bodyResult = HarvestBodySchema.safeParse(raw);
@@ -40547,17 +42397,17 @@ app.post("/harvest/sync", auth3, async (c) => {
40547
42397
  return c.json({ job_id: jobId2, status: "failed", ...response, attempts: sanitizeAttempts(attempts) }, problem.httpStatus);
40548
42398
  }
40549
42399
  });
40550
- app.get("/jobs/:id", auth3, async (c) => {
42400
+ app.get("/jobs/:id", auth4, async (c) => {
40551
42401
  const job = await getJob(c.req.param("id"), c.get("user").id);
40552
42402
  if (!job) return c.json({ error: "Job not found" }, 404);
40553
42403
  const attempts = await listHarvestAttempts(job.id, c.get("user").id);
40554
42404
  const safeResult = job.result && typeof job.result === "object" ? sanitizeHarvestResult(job.result) : job.result;
40555
42405
  return c.json({ ...job, result: safeResult, attempts: sanitizeAttempts(attempts) });
40556
42406
  });
40557
- app.get("/jobs", auth3, async (c) => {
42407
+ app.get("/jobs", auth4, async (c) => {
40558
42408
  return c.json(await listJobs(c.get("user").id));
40559
42409
  });
40560
- app.get("/history", auth3, async (c) => {
42410
+ app.get("/history", auth4, async (c) => {
40561
42411
  const userId = c.get("user").id;
40562
42412
  const [jobs, events] = await Promise.all([
40563
42413
  listJobs(userId),
@@ -40585,7 +42435,7 @@ app.get("/history", auth3, async (c) => {
40585
42435
  const rows = [...jobRows, ...eventRows].sort((a, b) => String(b.ts).localeCompare(String(a.ts)));
40586
42436
  return c.json(rows.slice(0, 100));
40587
42437
  });
40588
- app.get("/ledger", auth3, async (c) => {
42438
+ app.get("/ledger", auth4, async (c) => {
40589
42439
  return c.json(await getLedger(c.get("user").id, 100));
40590
42440
  });
40591
42441
  app.post("/admin/users", adminAuth, async (c) => {
@@ -40614,7 +42464,7 @@ app.post("/admin/backfill-signup-credits", adminAuth, async (c) => {
40614
42464
  retired: true
40615
42465
  });
40616
42466
  });
40617
- app.post("/extract-url", auth3, async (c) => {
42467
+ app.post("/extract-url", auth4, async (c) => {
40618
42468
  const raw = await c.req.json().catch(() => ({}));
40619
42469
  const allowLocalRequest = permitsLocalApiRequest(c.req.raw);
40620
42470
  const bodyResult = (allowLocalRequest ? ExtractUrlLocalBodySchema : ExtractUrlBodySchema).safeParse(raw);
@@ -40750,7 +42600,7 @@ app.post("/extract-url", auth3, async (c) => {
40750
42600
  await releaseConcurrencyGate(gate.lockId);
40751
42601
  }
40752
42602
  });
40753
- app.post("/diff-page", auth3, async (c) => {
42603
+ app.post("/diff-page", auth4, async (c) => {
40754
42604
  const raw = await c.req.json().catch(() => ({}));
40755
42605
  const allowLocalRequest = permitsLocalApiRequest(c.req.raw);
40756
42606
  const bodyResult = (allowLocalRequest ? DiffPageLocalBodySchema : DiffPageBodySchema).safeParse(raw);
@@ -40837,7 +42687,7 @@ app.post("/diff-page", auth3, async (c) => {
40837
42687
  await releaseConcurrencyGate(gate.lockId);
40838
42688
  }
40839
42689
  });
40840
- app.post("/archive/read", auth3, async (c) => {
42690
+ app.post("/archive/read", auth4, async (c) => {
40841
42691
  const raw = await c.req.json().catch(() => ({}));
40842
42692
  const bodyResult = ArchiveReadBodySchema.safeParse(raw);
40843
42693
  if (!bodyResult.success) {
@@ -40912,7 +42762,7 @@ app.post("/archive/read", auth3, async (c) => {
40912
42762
  await releaseConcurrencyGate(gate.lockId);
40913
42763
  }
40914
42764
  });
40915
- app.post("/map-urls", auth3, async (c) => {
42765
+ app.post("/map-urls", auth4, async (c) => {
40916
42766
  const raw = await c.req.json().catch(() => ({}));
40917
42767
  const bodyResult = MapUrlsBodySchema.safeParse(raw);
40918
42768
  if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
@@ -40961,7 +42811,7 @@ app.post("/map-urls", auth3, async (c) => {
40961
42811
  await releaseConcurrencyGate(gate.lockId);
40962
42812
  }
40963
42813
  });
40964
- app.post("/wayback/snapshots", auth3, async (c) => {
42814
+ app.post("/wayback/snapshots", auth4, async (c) => {
40965
42815
  const raw = await c.req.json().catch(() => ({}));
40966
42816
  const bodyResult = WaybackInventoryBodySchema.safeParse(raw);
40967
42817
  if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
@@ -41031,7 +42881,7 @@ app.post("/wayback/snapshots", auth3, async (c) => {
41031
42881
  await releaseConcurrencyGate(gate.lockId);
41032
42882
  }
41033
42883
  });
41034
- app.post("/extract-site", auth3, async (c) => {
42884
+ app.post("/extract-site", auth4, async (c) => {
41035
42885
  const raw = await c.req.json().catch(() => ({}));
41036
42886
  const bodyResult = ExtractSiteBodySchema.safeParse(raw);
41037
42887
  if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
@@ -41254,7 +43104,7 @@ app.post("/extract-site", auth3, async (c) => {
41254
43104
  await releaseConcurrencyGate(gate.lockId);
41255
43105
  }
41256
43106
  });
41257
- app.get("/extract-site/status/:id", auth3, async (c) => {
43107
+ app.get("/extract-site/status/:id", auth4, async (c) => {
41258
43108
  const user = c.get("user");
41259
43109
  const job = await getExtractJob(c.req.param("id"));
41260
43110
  if (!job || job.userId !== user.id) return c.json({ error: "Job not found" }, 404);
@@ -41288,9 +43138,9 @@ app.post("/billing/concurrency/checkout", requireAllowedOrigin, sessionAuth, asy
41288
43138
  try {
41289
43139
  const user = c.get("sessionUser");
41290
43140
  if (user.concurrency_stripe_sub_id) return c.json({ error: "Already subscribed \u2014 cancel existing slot first to change." }, 409);
41291
- const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
41292
- if (!secret2) return c.json({ error: "Stripe is not configured." }, 503);
41293
- const stripeClient = new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
43141
+ const secret = process.env.STRIPE_SECRET_KEY?.trim();
43142
+ if (!secret) return c.json({ error: "Stripe is not configured." }, 503);
43143
+ const stripeClient = new Stripe2(secret, { apiVersion: STRIPE_API_VERSION });
41294
43144
  let customerId = user.stripe_customer_id;
41295
43145
  if (!customerId) {
41296
43146
  const customer = await stripeClient.customers.create({ email: user.email });
@@ -41352,9 +43202,9 @@ app.post("/billing/subscribe", requireAllowedOrigin, sessionAuth, async (c) => {
41352
43202
  requested_plan: getSubscriptionPlanView(tier.tier)
41353
43203
  }, 409);
41354
43204
  }
41355
- const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
41356
- if (!secret2) return c.json({ error: "Stripe is not configured." }, 503);
41357
- const stripeClient = new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
43205
+ const secret = process.env.STRIPE_SECRET_KEY?.trim();
43206
+ if (!secret) return c.json({ error: "Stripe is not configured." }, 503);
43207
+ const stripeClient = new Stripe2(secret, { apiVersion: STRIPE_API_VERSION });
41358
43208
  let customerId = user.stripe_customer_id;
41359
43209
  if (!customerId) {
41360
43210
  const customer = await stripeClient.customers.create({ email: user.email });
@@ -41415,9 +43265,9 @@ app.post("/billing/portal", requireAllowedOrigin, sessionAuth, async (c) => {
41415
43265
  try {
41416
43266
  const user = c.get("sessionUser");
41417
43267
  if (!user.stripe_customer_id) return c.json({ error: "No billing account yet \u2014 subscribe first." }, 409);
41418
- const secret2 = requireStripeSecret();
41419
- if (!secret2) return c.json({ error: "Stripe is not configured." }, 503);
41420
- const stripeClient = new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
43268
+ const secret = requireStripeSecret();
43269
+ if (!secret) return c.json({ error: "Stripe is not configured." }, 503);
43270
+ const stripeClient = new Stripe2(secret, { apiVersion: STRIPE_API_VERSION });
41421
43271
  const session = await stripeClient.billingPortal.sessions.create({
41422
43272
  customer: user.stripe_customer_id,
41423
43273
  return_url: `${appOrigin()}/billing`
@@ -41429,7 +43279,7 @@ app.post("/billing/portal", requireAllowedOrigin, sessionAuth, async (c) => {
41429
43279
  return c.json({ error: message }, 500);
41430
43280
  }
41431
43281
  });
41432
- app.post("/billing/concurrency/terminal-checkout", auth3, async (c) => {
43282
+ app.post("/billing/concurrency/terminal-checkout", auth4, async (c) => {
41433
43283
  try {
41434
43284
  const user = c.get("user");
41435
43285
  const upgrade = concurrencySlotBillingInfo();
@@ -41445,9 +43295,9 @@ app.post("/billing/concurrency/terminal-checkout", auth3, async (c) => {
41445
43295
  }
41446
43296
  }, 409);
41447
43297
  }
41448
- const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
41449
- if (!secret2) return c.json({ error: "Stripe is not configured." }, 503);
41450
- const stripeClient = new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
43298
+ const secret = process.env.STRIPE_SECRET_KEY?.trim();
43299
+ if (!secret) return c.json({ error: "Stripe is not configured." }, 503);
43300
+ const stripeClient = new Stripe2(secret, { apiVersion: STRIPE_API_VERSION });
41451
43301
  let customerId = user.stripe_customer_id;
41452
43302
  if (!customerId) {
41453
43303
  const customer = await stripeClient.customers.create({ email: user.email });
@@ -41483,7 +43333,7 @@ app.post("/billing/concurrency/terminal-checkout", auth3, async (c) => {
41483
43333
  return c.json({ error: message }, 500);
41484
43334
  }
41485
43335
  });
41486
- app.post("/billing/subscribe/terminal-checkout", auth3, async (c) => {
43336
+ app.post("/billing/subscribe/terminal-checkout", auth4, async (c) => {
41487
43337
  try {
41488
43338
  const user = c.get("user");
41489
43339
  const raw = await c.req.json().catch(() => ({}));
@@ -41499,9 +43349,9 @@ app.post("/billing/subscribe/terminal-checkout", auth3, async (c) => {
41499
43349
  requested_plan: getSubscriptionPlanView(tier.tier)
41500
43350
  }, 409);
41501
43351
  }
41502
- const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
41503
- if (!secret2) return c.json({ error: "Stripe is not configured." }, 503);
41504
- const stripeClient = new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
43352
+ const secret = process.env.STRIPE_SECRET_KEY?.trim();
43353
+ if (!secret) return c.json({ error: "Stripe is not configured." }, 503);
43354
+ const stripeClient = new Stripe2(secret, { apiVersion: STRIPE_API_VERSION });
41505
43355
  let customerId = user.stripe_customer_id;
41506
43356
  if (!customerId) {
41507
43357
  const customer = await stripeClient.customers.create({ email: user.email });
@@ -41585,7 +43435,7 @@ app.post("/billing/concurrency/cancel", requireAllowedOrigin, sessionAuth, async
41585
43435
  await setConcurrencySubId(user.id, null);
41586
43436
  return c.json({ ok: true, extra_concurrency_slots: nextSlots, concurrency_limit: 1 + nextSlots });
41587
43437
  });
41588
- app.get("/billing/balance", auth3, async (c) => {
43438
+ app.get("/billing/balance", auth4, async (c) => {
41589
43439
  const user = c.get("user");
41590
43440
  const balanceMc = user.balance_mc;
41591
43441
  const ledger = await getLedger(user.id, 20);
@@ -41597,15 +43447,15 @@ app.get("/billing/balance", auth3, async (c) => {
41597
43447
  ledger
41598
43448
  });
41599
43449
  });
41600
- app.get("/billing/summary", auth3, billingSummaryHandler);
41601
- app.get("/billing/connected-usage/history", auth3, async (c) => {
43450
+ app.get("/billing/summary", auth4, billingSummaryHandler);
43451
+ app.get("/billing/connected-usage/history", auth4, async (c) => {
41602
43452
  return c.json({
41603
43453
  ok: true,
41604
43454
  ratePolicy: CONNECTED_USAGE_RATE_POLICY,
41605
43455
  receipts: await listConnectedUsageHistory(c.get("user").id, 100)
41606
43456
  });
41607
43457
  });
41608
- app.post("/billing/credits", auth3, async (c) => {
43458
+ app.post("/billing/credits", auth4, async (c) => {
41609
43459
  const user = c.get("user");
41610
43460
  const balanceMc = user.balance_mc;
41611
43461
  const body = await c.req.json().catch(() => ({}));
@@ -41637,14 +43487,14 @@ app.post("/billing/credits", auth3, async (c) => {
41637
43487
  });
41638
43488
  });
41639
43489
  app.get("/cron/tick", async (c) => {
41640
- const secret2 = c.req.header("authorization");
41641
- if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
43490
+ const secret = c.req.header("authorization");
43491
+ if (!process.env.CRON_SECRET || secret !== `Bearer ${process.env.CRON_SECRET}`) {
41642
43492
  return c.json({ error: "Unauthorized" }, 401);
41643
43493
  }
41644
- const { drainQueue } = await import("./worker-3QX7LG62.js");
43494
+ const { drainQueue } = await import("./worker-4MHZS3OB.js");
41645
43495
  const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
41646
43496
  const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
41647
- const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup, connectedAccountBilling, directoryWorkflowDispatch, directoryWorkflowReconciliation, directoryArtifactCleanup, siteExtractArtifactCleanup, siteExtractRedispatch, siteExtractReconciliation, paaHarvestRedispatch, paaHarvestReconciliation, localSourcebookRedispatch] = await Promise.all([
43497
+ const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup, connectedAccountBilling, directoryWorkflowDispatch, directoryWorkflowReconciliation, directoryArtifactCleanup, siteExtractArtifactCleanup, siteExtractRedispatch, siteExtractReconciliation, paaHarvestRedispatch, paaHarvestReconciliation, localSourcebookRedispatch, analyticsRollup, analyticsFormDelivery] = await Promise.all([
41648
43498
  drainQueue(budget),
41649
43499
  runMonthlyRefreshSweep(),
41650
43500
  reapIdleBrowserSessions(120).catch(() => ({ reaped: 0 })),
@@ -41660,7 +43510,9 @@ app.get("/cron/tick", async (c) => {
41660
43510
  reconcileSiteExtractSettlements(25).catch(() => ({ checked: 0, settled: 0, failed: 0 })),
41661
43511
  redispatchPendingPaaHarvests(25).catch(() => ({ checked: 0, dispatched: 0, failed: 0 })),
41662
43512
  reconcilePaaHarvestSettlements(25).catch(() => ({ checked: 0, staleFailed: 0, settled: 0, failed: 0 })),
41663
- redispatchQueuedLocalSourcebookAcquisitions(25).catch(() => ({ checked: 0, dispatched: 0, failed: 0 }))
43513
+ redispatchQueuedLocalSourcebookAcquisitions(25).catch(() => ({ checked: 0, dispatched: 0, failed: 0 })),
43514
+ refreshAnalyticsDailyRollupsIfDue().catch(() => ({ status: "failed", rows: 0 })),
43515
+ deliverPendingAnalyticsForms(10).catch(() => ({ claimed: 0, delivered: 0, deferred: 0 }))
41664
43516
  ]);
41665
43517
  return c.json({
41666
43518
  drained: results.length,
@@ -41680,12 +43532,14 @@ app.get("/cron/tick", async (c) => {
41680
43532
  paaHarvestRedispatch,
41681
43533
  paaHarvestReconciliation,
41682
43534
  localSourcebookRedispatch,
43535
+ analyticsRollup,
43536
+ analyticsFormDelivery,
41683
43537
  workflowDispatch: workflowDispatchResult
41684
43538
  });
41685
43539
  });
41686
43540
  app.post("/api/internal/extract-refinalize/:id", async (c) => {
41687
- const secret2 = c.req.header("authorization");
41688
- if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
43541
+ const secret = c.req.header("authorization");
43542
+ if (!process.env.CRON_SECRET || secret !== `Bearer ${process.env.CRON_SECRET}`) {
41689
43543
  return c.json({ error: "Unauthorized" }, 401);
41690
43544
  }
41691
43545
  const jobId2 = c.req.param("id");
@@ -41696,8 +43550,8 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
41696
43550
  finishExtractJob: finishExtractJob2,
41697
43551
  terminalExtractJobStatus: terminalExtractJobStatus2,
41698
43552
  extractJobLimitInfo: extractJobLimitInfo2
41699
- } = await import("./site-extract-repository-UULQAGO4.js");
41700
- const { assembleExtractArtifacts } = await import("./extract-bundle-DXWWQER6.js");
43553
+ } = await import("./site-extract-repository-STNOI5OH.js");
43554
+ const { assembleExtractArtifacts } = await import("./extract-bundle-2HONVC5T.js");
41701
43555
  const existing = await getExtractJob2(jobId2);
41702
43556
  if (!existing) return c.json({ error: "job not found" }, 404);
41703
43557
  if (existing.status !== "failed") {
@@ -41724,7 +43578,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
41724
43578
  if (!updated) return c.json({ error: "job state changed during re-finalization" }, 409);
41725
43579
  let settlement = "already_settled_or_refunded";
41726
43580
  if (job.billedMc == null && job.userId != null) {
41727
- const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-UULQAGO4.js");
43581
+ const { settleExtractJob: settleExtractJob3, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-STNOI5OH.js");
41728
43582
  const heldMc = Number(job.options.heldMc ?? 0);
41729
43583
  const successful = await countSuccessfulPages2(jobId2);
41730
43584
  const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
@@ -41775,6 +43629,7 @@ app.route("/resend", resendInboundApp);
41775
43629
  app.route("/workshops", workshopRegistrationApp);
41776
43630
  app.route("/editorial-reading-room", editorialReadingRoomApp);
41777
43631
  app.route("/commons", commonsApp);
43632
+ app.route("/analytics", analyticsApp);
41778
43633
  app.route("/", scheduledResultApp);
41779
43634
  app.route("/public", publicScheduledResultApp);
41780
43635
  app.route("/api/internal/scheduled-artifacts", scheduledArtifactInternalApp);
@@ -41908,4 +43763,4 @@ app.get("/blog/:slug/", (c) => {
41908
43763
  export {
41909
43764
  app
41910
43765
  };
41911
- //# sourceMappingURL=server-OHMZJRSB.js.map
43766
+ //# sourceMappingURL=server-EVTCL3KB.js.map