mcp-scraper 0.66.6 → 0.67.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 (34) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/README.md +1 -1
  3. package/dist/bin/api-server.cjs +1122 -625
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  6. package/dist/bin/mcp-scraper-cli.js +1 -1
  7. package/dist/bin/mcp-scraper-install.cjs +2 -2
  8. package/dist/bin/mcp-scraper-install.js +2 -2
  9. package/dist/bin/mcp-stdio-server.cjs +147 -17
  10. package/dist/bin/mcp-stdio-server.js +6 -6
  11. package/dist/bin/paa-harvest.cjs +20 -1
  12. package/dist/bin/paa-harvest.js +3 -3
  13. package/dist/{chunk-3EYALCYK.js → chunk-2L4C4DAZ.js} +1 -1
  14. package/dist/{chunk-CETDPNMZ.js → chunk-2LXTOAKS.js} +1 -1
  15. package/dist/{chunk-K7Y27ND3.js → chunk-62LEXS5O.js} +2 -2
  16. package/dist/{chunk-55B3T5L2.js → chunk-65FTMB7G.js} +92 -1
  17. package/dist/{chunk-3TKKJQB4.js → chunk-ES25GP6C.js} +1 -1
  18. package/dist/{chunk-NLEAYKOC.js → chunk-GT63KEUB.js} +1 -1
  19. package/dist/{chunk-KPE52NPT.js → chunk-IHDTONZV.js} +1 -1
  20. package/dist/{chunk-KXRWY7DZ.js → chunk-LCGTNWOT.js} +1 -1
  21. package/dist/{chunk-DBWWIK7O.js → chunk-LN6N3YLK.js} +1 -1
  22. package/dist/{chunk-ABYBPODM.js → chunk-UL4ZKAWZ.js} +1 -1
  23. package/dist/{chunk-VMWVEKZT.js → chunk-XOOKZYZG.js} +71 -18
  24. package/dist/{chunk-PMCT2YQM.js → chunk-Y46YNQMM.js} +1 -1
  25. package/dist/{db-4RVZ3NYZ.js → db-ZGZAKYBW.js} +1 -1
  26. package/dist/{extract-bundle-EA5URLXN.js → extract-bundle-QQIYJSBW.js} +3 -3
  27. package/dist/index.cjs +20 -1
  28. package/dist/index.js +3 -3
  29. package/dist/{lead-list-enrichment-repository-IBXNWM6H.js → lead-list-enrichment-repository-CXLVIV3R.js} +2 -2
  30. package/dist/{location-data-repository-WHHQUCUI.js → location-data-repository-G72M7DTN.js} +2 -2
  31. package/dist/{server-5WPS3LZD.js → server-MFVYRB6I.js} +818 -471
  32. package/dist/{site-extract-repository-NTM4MDQN.js → site-extract-repository-7EP6UX6I.js} +2 -2
  33. package/dist/{worker-UTBUWEXQ.js → worker-JGWV2LID.js} +5 -5
  34. package/package.json +1 -1
@@ -164,6 +164,26 @@ function publicErrorType(code) {
164
164
  case "site_export_read_failed":
165
165
  case "site_export_image_not_found":
166
166
  return "extraction";
167
+ case "image_url_invalid":
168
+ case "image_url_not_https":
169
+ case "image_host_unresolvable":
170
+ case "image_host_blocked":
171
+ case "image_redirect_invalid":
172
+ case "image_source_unavailable":
173
+ case "image_too_large":
174
+ case "image_redirect_limit":
175
+ case "image_base64_invalid":
176
+ case "image_source_missing":
177
+ case "image_source_ambiguous":
178
+ case "image_source_type_invalid":
179
+ case "image_artifact_not_found":
180
+ case "image_page_too_large":
181
+ case "page_image_not_found":
182
+ case "page_image_candidates_unusable":
183
+ case "image_format_unsupported":
184
+ case "image_storage_unconfigured":
185
+ case "featured_image_unhostable":
186
+ return "invalid_request";
167
187
  case "insufficient_balance":
168
188
  return "billing";
169
189
  case "vendor_unavailable":
@@ -237,6 +257,44 @@ function publicErrorMessage(code) {
237
257
  return "The stored site export could not be read or did not pass its integrity check.";
238
258
  case "site_export_image_not_found":
239
259
  return "The requested site-export image was not found, expired, or does not belong to this account.";
260
+ case "image_url_invalid":
261
+ return "The image source is not a valid URL. Use a stable public HTTPS URL or inline image bytes.";
262
+ case "image_url_not_https":
263
+ return "Commons image sources must use a public HTTPS URL or inline image bytes.";
264
+ case "image_host_unresolvable":
265
+ return "The image host could not be resolved. Use a different stable public URL or inline image bytes.";
266
+ case "image_host_blocked":
267
+ return "The image source resolves to a network address Commons cannot fetch safely.";
268
+ case "image_redirect_invalid":
269
+ return "The image source redirected to an invalid or unsafe destination.";
270
+ case "image_source_unavailable":
271
+ return "The image source could not be fetched. Host a different stable public URL or provide inline image bytes.";
272
+ case "image_too_large":
273
+ return "The image exceeds the Commons image size limit.";
274
+ case "image_redirect_limit":
275
+ return "The image source exceeded the Commons redirect limit.";
276
+ case "image_base64_invalid":
277
+ return "The supplied inline image bytes are invalid.";
278
+ case "image_source_missing":
279
+ return "Provide exactly one of a public image or webpage URL, inline image bytes, or an owner-scoped image artifact.";
280
+ case "image_source_ambiguous":
281
+ return "Provide only one image source: sourceUrl, imageBase64, or artifactId.";
282
+ case "image_source_type_invalid":
283
+ return "Use sourceType only with inline image bytes or an owner-scoped image artifact.";
284
+ case "image_artifact_not_found":
285
+ return "The image artifact was not found, expired, or does not belong to this account.";
286
+ case "image_page_too_large":
287
+ return "The webpage is too large for bounded Commons image discovery.";
288
+ case "page_image_not_found":
289
+ return "The webpage did not expose a usable featured or content image.";
290
+ case "page_image_candidates_unusable":
291
+ return "The webpage exposed image candidates, but none returned supported public image bytes.";
292
+ case "image_format_unsupported":
293
+ return "Commons accepts JPEG, PNG, GIF, or WebP image bytes.";
294
+ case "image_storage_unconfigured":
295
+ return "Commons image storage is temporarily unavailable.";
296
+ case "featured_image_unhostable":
297
+ return "The required featured image could not be hosted for Commons publication.";
240
298
  default:
241
299
  return PUBLIC_SERVICE_UNAVAILABLE_MESSAGE;
242
300
  }
@@ -299,7 +357,26 @@ var init_errors = __esm({
299
357
  "site_export_not_found",
300
358
  "site_export_format_unavailable",
301
359
  "site_export_read_failed",
302
- "site_export_image_not_found"
360
+ "site_export_image_not_found",
361
+ "image_url_invalid",
362
+ "image_url_not_https",
363
+ "image_host_unresolvable",
364
+ "image_host_blocked",
365
+ "image_redirect_invalid",
366
+ "image_source_unavailable",
367
+ "image_too_large",
368
+ "image_redirect_limit",
369
+ "image_base64_invalid",
370
+ "image_source_missing",
371
+ "image_source_ambiguous",
372
+ "image_source_type_invalid",
373
+ "image_artifact_not_found",
374
+ "image_page_too_large",
375
+ "page_image_not_found",
376
+ "page_image_candidates_unusable",
377
+ "image_format_unsupported",
378
+ "image_storage_unconfigured",
379
+ "featured_image_unhostable"
303
380
  ];
304
381
  PUBLIC_ERROR_TYPES = [
305
382
  "request_aborted",
@@ -4495,6 +4572,20 @@ async function migrate(options = {}) {
4495
4572
  PRIMARY KEY (user_id, connection_id, period_start)
4496
4573
  )
4497
4574
  `);
4575
+ await db.execute(`
4576
+ CREATE TABLE IF NOT EXISTS connected_account_charge_transfers (
4577
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
4578
+ user_id INTEGER NOT NULL,
4579
+ period_start TEXT NOT NULL,
4580
+ from_connection_id TEXT NOT NULL,
4581
+ to_connection_id TEXT NOT NULL,
4582
+ provider_config_key TEXT NOT NULL,
4583
+ transferred_at TEXT NOT NULL DEFAULT (datetime('now')),
4584
+ UNIQUE (user_id, period_start, from_connection_id),
4585
+ CHECK (from_connection_id <> to_connection_id)
4586
+ )
4587
+ `);
4588
+ await db.execute(`CREATE INDEX IF NOT EXISTS connected_account_charge_transfers_destination ON connected_account_charge_transfers(user_id, period_start, to_connection_id, transferred_at DESC)`);
4498
4589
  await db.execute(`
4499
4590
  CREATE TABLE IF NOT EXISTS billing_credits (
4500
4591
  idempotency_key TEXT PRIMARY KEY,
@@ -11327,7 +11418,8 @@ function firstJsonLdImage(value) {
11327
11418
  }
11328
11419
  return null;
11329
11420
  }
11330
- function firstContentImage(html) {
11421
+ function contentImages(html) {
11422
+ const images = [];
11331
11423
  for (const match of html.matchAll(/<img\b[^>]*>/gi)) {
11332
11424
  const tag = match[0];
11333
11425
  const attr = (name) => tag.match(new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]+)"|'([^']+)'|([^\\s>]+))`, "i"))?.slice(1).find(Boolean) ?? null;
@@ -11338,30 +11430,36 @@ function firstContentImage(html) {
11338
11430
  const width = Number(attr("width") ?? 0);
11339
11431
  const height = Number(attr("height") ?? 0);
11340
11432
  if (width > 0 && width < 200 || height > 0 && height < 120) continue;
11341
- return candidate;
11433
+ images.push(candidate);
11342
11434
  }
11343
- return null;
11435
+ return images;
11344
11436
  }
11345
- function resolveFeaturedImage(input) {
11437
+ function resolveFeaturedImageCandidates(input) {
11346
11438
  const replay = parseWaybackReplayUrl(input.pageUrl);
11347
11439
  const baseUrl = replay?.originalUrl ?? input.pageUrl;
11348
11440
  const candidates = [
11349
11441
  { value: input.meta["og:image"] ?? input.meta["og:image:url"] ?? input.meta["og:image:secure_url"], source: "og:image" },
11350
11442
  { value: input.meta["twitter:image"] ?? input.meta["twitter:image:src"], source: "twitter:image" },
11351
11443
  { value: firstJsonLdImage(input.schema), source: "json-ld" },
11352
- { value: firstContentImage(input.html), source: "content-image" }
11444
+ ...contentImages(input.html).map((value) => ({ value, source: "content-image" }))
11353
11445
  ];
11446
+ const seen = /* @__PURE__ */ new Set();
11447
+ const resolved = [];
11354
11448
  for (const candidate of candidates) {
11355
11449
  if (!candidate.value) continue;
11356
11450
  const url = safeHttpUrl2(candidate.value, baseUrl);
11357
- if (!url) continue;
11358
- return {
11451
+ if (!url || seen.has(url)) continue;
11452
+ seen.add(url);
11453
+ resolved.push({
11359
11454
  url,
11360
11455
  archivedUrl: replay ? buildWaybackReplayUrl(replay.timestamp, url, "im_") : null,
11361
11456
  source: candidate.source
11362
- };
11457
+ });
11363
11458
  }
11364
- return null;
11459
+ return resolved;
11460
+ }
11461
+ function resolveFeaturedImage(input) {
11462
+ return resolveFeaturedImageCandidates(input)[0] ?? null;
11365
11463
  }
11366
11464
  var import_p_limit, WAYBACK_HOSTS, MAX_CDX_RESPONSE_BYTES, MAX_CDX_ROWS, MAX_ARCHIVE_URL_CHARS, MAX_SELECTED_TIMELINE_CELLS, TIMELINE_CDX_CONCURRENCY;
11367
11465
  var init_wayback = __esm({
@@ -36288,6 +36386,357 @@ var init_subscription_status_policy = __esm({
36288
36386
  }
36289
36387
  });
36290
36388
 
36389
+ // src/api/service-connections.ts
36390
+ function ensureServiceConnectionsSchema() {
36391
+ const currentDb = getDb();
36392
+ if (schemaReady && schemaDb5 === currentDb) return schemaReady;
36393
+ schemaDb5 = currentDb;
36394
+ schemaReady = (async () => {
36395
+ const db = currentDb;
36396
+ await db.execute(`
36397
+ CREATE TABLE IF NOT EXISTS service_connections (
36398
+ id TEXT PRIMARY KEY,
36399
+ user_id INTEGER NOT NULL REFERENCES users(id),
36400
+ provider_config_key TEXT NOT NULL,
36401
+ provider TEXT,
36402
+ transport TEXT NOT NULL,
36403
+ upstream_connection_id TEXT,
36404
+ label TEXT,
36405
+ user_label TEXT,
36406
+ provider_account_id TEXT,
36407
+ provider_account_email TEXT,
36408
+ provider_account_name TEXT,
36409
+ provider_identity_checked_at TEXT,
36410
+ provider_identity_source_updated_at TEXT,
36411
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending',
36412
+ operational_status TEXT NOT NULL DEFAULT 'unknown',
36413
+ reconnect_required INTEGER NOT NULL DEFAULT 0,
36414
+ actions_enabled INTEGER NOT NULL DEFAULT 0,
36415
+ read_tools_json TEXT NOT NULL DEFAULT '[]',
36416
+ action_tools_json TEXT NOT NULL DEFAULT '[]',
36417
+ tool_revision TEXT,
36418
+ last_checked_at TEXT,
36419
+ last_successful_call_at TEXT,
36420
+ last_failure_at TEXT,
36421
+ last_failure_code TEXT,
36422
+ last_failure_retryable INTEGER,
36423
+ consecutive_failures INTEGER NOT NULL DEFAULT 0,
36424
+ vault_name TEXT,
36425
+ table_name TEXT,
36426
+ legacy_owner_identity TEXT,
36427
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
36428
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
36429
+ UNIQUE(user_id, provider_config_key, upstream_connection_id)
36430
+ )
36431
+ `);
36432
+ try {
36433
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN user_label TEXT`);
36434
+ } catch {
36435
+ }
36436
+ try {
36437
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_id TEXT`);
36438
+ } catch {
36439
+ }
36440
+ try {
36441
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_email TEXT`);
36442
+ } catch {
36443
+ }
36444
+ try {
36445
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_name TEXT`);
36446
+ } catch {
36447
+ }
36448
+ try {
36449
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_checked_at TEXT`);
36450
+ } catch {
36451
+ }
36452
+ try {
36453
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_source_updated_at TEXT`);
36454
+ } catch {
36455
+ }
36456
+ await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_user_updated ON service_connections(user_id, updated_at DESC)`);
36457
+ await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_upstream ON service_connections(provider_config_key, upstream_connection_id)`);
36458
+ await db.execute(`
36459
+ CREATE TABLE IF NOT EXISTS service_connection_health_events (
36460
+ id TEXT PRIMARY KEY,
36461
+ connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
36462
+ operational_status TEXT NOT NULL,
36463
+ failure_code TEXT,
36464
+ retryable INTEGER,
36465
+ evidence_source TEXT NOT NULL,
36466
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
36467
+ )
36468
+ `);
36469
+ await db.execute(`CREATE INDEX IF NOT EXISTS service_connection_health_events_connection ON service_connection_health_events(connection_id, created_at DESC)`);
36470
+ await db.execute(`
36471
+ CREATE TABLE IF NOT EXISTS service_connection_action_audit (
36472
+ id TEXT PRIMARY KEY,
36473
+ connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
36474
+ user_id INTEGER NOT NULL REFERENCES users(id),
36475
+ tool TEXT NOT NULL,
36476
+ request_id TEXT NOT NULL,
36477
+ status TEXT NOT NULL,
36478
+ error_code TEXT,
36479
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
36480
+ UNIQUE(user_id, request_id)
36481
+ )
36482
+ `);
36483
+ try {
36484
+ await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN request_digest TEXT`);
36485
+ } catch {
36486
+ }
36487
+ try {
36488
+ await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN result_json TEXT`);
36489
+ } catch {
36490
+ }
36491
+ })().catch((error) => {
36492
+ schemaReady = null;
36493
+ schemaDb5 = null;
36494
+ throw error;
36495
+ });
36496
+ return schemaReady;
36497
+ }
36498
+ function parseTools(value) {
36499
+ if (typeof value !== "string") return [];
36500
+ try {
36501
+ const parsed = JSON.parse(value);
36502
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
36503
+ } catch {
36504
+ return [];
36505
+ }
36506
+ }
36507
+ function rowToRecord(row) {
36508
+ return {
36509
+ id: String(row.id),
36510
+ userId: Number(row.user_id),
36511
+ providerConfigKey: String(row.provider_config_key),
36512
+ provider: typeof row.provider === "string" ? row.provider : null,
36513
+ transport: row.transport === "remote_mcp" ? "remote_mcp" : "nango",
36514
+ upstreamConnectionId: typeof row.upstream_connection_id === "string" ? row.upstream_connection_id : null,
36515
+ label: typeof row.label === "string" ? row.label : null,
36516
+ userLabel: typeof row.user_label === "string" ? row.user_label : null,
36517
+ providerAccountId: typeof row.provider_account_id === "string" ? row.provider_account_id : null,
36518
+ providerAccountEmail: typeof row.provider_account_email === "string" ? row.provider_account_email : null,
36519
+ providerAccountName: typeof row.provider_account_name === "string" ? row.provider_account_name : null,
36520
+ providerIdentityCheckedAt: typeof row.provider_identity_checked_at === "string" ? row.provider_identity_checked_at : null,
36521
+ providerIdentitySourceUpdatedAt: typeof row.provider_identity_source_updated_at === "string" ? row.provider_identity_source_updated_at : null,
36522
+ lifecycleStatus: String(row.lifecycle_status),
36523
+ operationalStatus: String(row.operational_status),
36524
+ reconnectRequired: Number(row.reconnect_required) === 1,
36525
+ actionsEnabled: Number(row.actions_enabled) === 1,
36526
+ readTools: parseTools(row.read_tools_json),
36527
+ actionTools: parseTools(row.action_tools_json),
36528
+ toolRevision: typeof row.tool_revision === "string" ? row.tool_revision : null,
36529
+ lastCheckedAt: typeof row.last_checked_at === "string" ? row.last_checked_at : null,
36530
+ lastSuccessfulCallAt: typeof row.last_successful_call_at === "string" ? row.last_successful_call_at : null,
36531
+ lastFailureAt: typeof row.last_failure_at === "string" ? row.last_failure_at : null,
36532
+ lastFailureCode: typeof row.last_failure_code === "string" ? row.last_failure_code : null,
36533
+ lastFailureRetryable: row.last_failure_retryable === null || row.last_failure_retryable === void 0 ? null : Number(row.last_failure_retryable) === 1,
36534
+ consecutiveFailures: Number(row.consecutive_failures ?? 0),
36535
+ vaultName: typeof row.vault_name === "string" ? row.vault_name : null,
36536
+ tableName: typeof row.table_name === "string" ? row.table_name : null,
36537
+ createdAt: String(row.created_at),
36538
+ updatedAt: String(row.updated_at)
36539
+ };
36540
+ }
36541
+ async function userIdForIdentity(identity) {
36542
+ const user = await getUserByEmail(identity.normalize("NFKC").trim().toLowerCase());
36543
+ if (!user) throw new Error("service_connection_user_not_found");
36544
+ return Number(user.id);
36545
+ }
36546
+ async function reconcileDiscoveredNangoConnections(identity, discovered) {
36547
+ await ensureServiceConnectionsSchema();
36548
+ const db = getDb();
36549
+ const userId = await userIdForIdentity(identity);
36550
+ for (const connection of discovered) {
36551
+ await db.execute({
36552
+ sql: `
36553
+ INSERT INTO service_connections (
36554
+ id, user_id, provider_config_key, provider, transport, upstream_connection_id,
36555
+ label, lifecycle_status, reconnect_required, actions_enabled, legacy_owner_identity, created_at, updated_at
36556
+ ) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, 1, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
36557
+ ON CONFLICT(user_id, provider_config_key, upstream_connection_id) DO UPDATE SET
36558
+ provider = excluded.provider,
36559
+ label = COALESCE(excluded.label, service_connections.label),
36560
+ lifecycle_status = excluded.lifecycle_status,
36561
+ reconnect_required = excluded.reconnect_required,
36562
+ updated_at = excluded.updated_at
36563
+ `,
36564
+ args: [
36565
+ (0, import_node_crypto21.randomUUID)(),
36566
+ userId,
36567
+ connection.providerConfigKey,
36568
+ connection.provider,
36569
+ connection.upstreamConnectionId,
36570
+ connection.label,
36571
+ connection.lifecycleStatus,
36572
+ connection.reconnectRequired ? 1 : 0,
36573
+ identity,
36574
+ connection.createdAt,
36575
+ connection.updatedAt
36576
+ ]
36577
+ });
36578
+ }
36579
+ return listServiceConnections(identity, "nango");
36580
+ }
36581
+ async function listServiceConnections(identity, transport) {
36582
+ await ensureServiceConnectionsSchema();
36583
+ const userId = await userIdForIdentity(identity);
36584
+ const result = await getDb().execute({
36585
+ sql: `SELECT * FROM service_connections WHERE user_id = ?${transport ? " AND transport = ?" : ""} AND lifecycle_status <> 'disconnected' ORDER BY updated_at DESC`,
36586
+ args: transport ? [userId, transport] : [userId]
36587
+ });
36588
+ return result.rows.map((row) => rowToRecord(row));
36589
+ }
36590
+ async function getOwnedServiceConnection(identity, connectionId) {
36591
+ await ensureServiceConnectionsSchema();
36592
+ const userId = await userIdForIdentity(identity);
36593
+ const result = await getDb().execute({
36594
+ sql: "SELECT * FROM service_connections WHERE id = ? AND user_id = ? LIMIT 1",
36595
+ args: [connectionId, userId]
36596
+ });
36597
+ const row = result.rows[0];
36598
+ return row ? rowToRecord(row) : null;
36599
+ }
36600
+ async function updateServiceConnectionTools(connectionId, readTools, actionTools, toolRevision) {
36601
+ await ensureServiceConnectionsSchema();
36602
+ await getDb().execute({
36603
+ sql: `UPDATE service_connections SET read_tools_json = ?, action_tools_json = ?, tool_revision = ?, updated_at = datetime('now') WHERE id = ?`,
36604
+ args: [JSON.stringify(readTools), JSON.stringify(actionTools), toolRevision, connectionId]
36605
+ });
36606
+ }
36607
+ async function setServiceConnectionProviderIdentity(args) {
36608
+ await ensureServiceConnectionsSchema();
36609
+ await getDb().execute({
36610
+ sql: `
36611
+ UPDATE service_connections SET
36612
+ provider_account_id = ?, provider_account_email = ?, provider_account_name = ?,
36613
+ provider_identity_checked_at = datetime('now'), provider_identity_source_updated_at = ?,
36614
+ updated_at = datetime('now')
36615
+ WHERE id = ?
36616
+ `,
36617
+ args: [
36618
+ args.providerAccountId ?? null,
36619
+ args.providerAccountEmail?.toLowerCase() ?? null,
36620
+ args.providerAccountName ?? null,
36621
+ args.sourceUpdatedAt,
36622
+ args.connectionId
36623
+ ]
36624
+ });
36625
+ }
36626
+ async function setServiceConnectionUserLabel(identity, connectionId, userLabel) {
36627
+ const connection = await getOwnedServiceConnection(identity, connectionId);
36628
+ if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
36629
+ await getDb().execute({
36630
+ sql: `UPDATE service_connections SET user_label = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
36631
+ args: [userLabel, connectionId, connection.userId]
36632
+ });
36633
+ return userLabel;
36634
+ }
36635
+ async function recordServiceConnectionHealth(args) {
36636
+ await ensureServiceConnectionsSchema();
36637
+ const now = (/* @__PURE__ */ new Date()).toISOString();
36638
+ await getDb().execute({
36639
+ sql: `
36640
+ UPDATE service_connections SET
36641
+ operational_status = ?, last_checked_at = ?,
36642
+ last_successful_call_at = CASE WHEN ? = 'available' THEN ? ELSE last_successful_call_at END,
36643
+ last_failure_at = CASE WHEN ? IN ('degraded', 'unavailable') THEN ? ELSE NULL END,
36644
+ last_failure_code = ?, last_failure_retryable = ?,
36645
+ consecutive_failures = CASE WHEN ? = 'available' THEN 0 ELSE consecutive_failures + 1 END,
36646
+ updated_at = datetime('now')
36647
+ WHERE id = ?
36648
+ `,
36649
+ args: [
36650
+ args.operationalStatus,
36651
+ now,
36652
+ args.operationalStatus,
36653
+ now,
36654
+ args.operationalStatus,
36655
+ now,
36656
+ args.failureCode ?? null,
36657
+ args.retryable == null ? null : args.retryable ? 1 : 0,
36658
+ args.operationalStatus,
36659
+ args.connectionId
36660
+ ]
36661
+ });
36662
+ await getDb().execute({
36663
+ sql: `INSERT INTO service_connection_health_events (id, connection_id, operational_status, failure_code, retryable, evidence_source) VALUES (?, ?, ?, ?, ?, ?)`,
36664
+ args: [(0, import_node_crypto21.randomUUID)(), args.connectionId, args.operationalStatus, args.failureCode ?? null, args.retryable == null ? null : args.retryable ? 1 : 0, args.evidenceSource]
36665
+ });
36666
+ }
36667
+ async function setServiceConnectionActions(identity, connectionId, enabled) {
36668
+ const connection = await getOwnedServiceConnection(identity, connectionId);
36669
+ if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
36670
+ await getDb().execute({
36671
+ sql: `UPDATE service_connections SET actions_enabled = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
36672
+ args: [enabled ? 1 : 0, connectionId, connection.userId]
36673
+ });
36674
+ return enabled;
36675
+ }
36676
+ async function markServiceConnectionDisconnected(identity, connectionId) {
36677
+ const connection = await getOwnedServiceConnection(identity, connectionId);
36678
+ if (!connection) throw new Error("service_connection_not_found");
36679
+ await getDb().execute({
36680
+ sql: `UPDATE service_connections SET lifecycle_status = 'disconnected', operational_status = 'unknown', reconnect_required = 0, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
36681
+ args: [connectionId, connection.userId]
36682
+ });
36683
+ const remaining = await getDb().execute({
36684
+ sql: `SELECT COUNT(*) AS count FROM service_connections WHERE user_id = ? AND transport = 'nango' AND lifecycle_status = 'connected'`,
36685
+ args: [connection.userId]
36686
+ });
36687
+ return Number(remaining.rows[0]?.count ?? 0);
36688
+ }
36689
+ async function recordServiceConnectionAction(args) {
36690
+ await ensureServiceConnectionsSchema();
36691
+ const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
36692
+ if (!connection) throw new Error("service_connection_not_found");
36693
+ await getDb().execute({
36694
+ sql: `UPDATE service_connection_action_audit SET status = ?, error_code = ?, result_json = ? WHERE user_id = ? AND request_id = ? AND connection_id = ?`,
36695
+ args: [args.status, args.errorCode ?? null, args.result === void 0 ? null : JSON.stringify(args.result), connection.userId, args.requestId, connection.id]
36696
+ });
36697
+ }
36698
+ async function claimServiceConnectionAction(args) {
36699
+ await ensureServiceConnectionsSchema();
36700
+ const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
36701
+ if (!connection) throw new Error("service_connection_not_found");
36702
+ const inserted = await getDb().execute({
36703
+ sql: `INSERT OR IGNORE INTO service_connection_action_audit (id, connection_id, user_id, tool, request_id, status, request_digest) VALUES (?, ?, ?, ?, ?, 'started', ?)`,
36704
+ args: [(0, import_node_crypto21.randomUUID)(), connection.id, connection.userId, args.tool, args.requestId, args.requestDigest]
36705
+ });
36706
+ if (Number(inserted.rowsAffected ?? 0) === 1) return { claimed: true };
36707
+ const existing = await getDb().execute({
36708
+ sql: `SELECT connection_id, tool, status, error_code, request_digest, result_json FROM service_connection_action_audit WHERE user_id = ? AND request_id = ? LIMIT 1`,
36709
+ args: [connection.userId, args.requestId]
36710
+ });
36711
+ const row = existing.rows[0];
36712
+ const conflict = String(row?.connection_id ?? "") !== connection.id || String(row?.tool ?? "") !== args.tool || String(row?.request_digest ?? "") !== args.requestDigest;
36713
+ let result = void 0;
36714
+ if (!conflict && typeof row?.result_json === "string") {
36715
+ try {
36716
+ result = JSON.parse(row.result_json);
36717
+ } catch {
36718
+ result = void 0;
36719
+ }
36720
+ }
36721
+ return {
36722
+ claimed: false,
36723
+ status: String(row?.status ?? "unknown"),
36724
+ errorCode: typeof row?.error_code === "string" ? row.error_code : null,
36725
+ conflict,
36726
+ ...result !== void 0 ? { result } : {}
36727
+ };
36728
+ }
36729
+ var import_node_crypto21, schemaReady, schemaDb5;
36730
+ var init_service_connections = __esm({
36731
+ "src/api/service-connections.ts"() {
36732
+ "use strict";
36733
+ import_node_crypto21 = require("crypto");
36734
+ init_db();
36735
+ schemaReady = null;
36736
+ schemaDb5 = null;
36737
+ }
36738
+ });
36739
+
36291
36740
  // src/api/connected-account-billing.ts
36292
36741
  function connectedBillingPeriodStart(now = /* @__PURE__ */ new Date()) {
36293
36742
  return `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}`;
@@ -36302,6 +36751,87 @@ async function chargedConnectionIds(userId, periodStart) {
36302
36751
  });
36303
36752
  return new Set(result.rows.map((row) => String(row.connection_id)));
36304
36753
  }
36754
+ async function inheritDisconnectedConnectionCharge(userId, connectionId, periodStart) {
36755
+ await ensureServiceConnectionsSchema();
36756
+ const normalizedUserId = Number(userId);
36757
+ const db = getDb();
36758
+ await db.batch([
36759
+ {
36760
+ sql: `
36761
+ INSERT OR IGNORE INTO connected_account_charge_transfers (
36762
+ user_id, period_start, from_connection_id, to_connection_id, provider_config_key
36763
+ )
36764
+ SELECT ?, ?, previous.id, replacement.id, replacement.provider_config_key
36765
+ FROM service_connections AS replacement
36766
+ JOIN service_connections AS previous
36767
+ ON previous.user_id = replacement.user_id
36768
+ AND previous.transport = 'nango'
36769
+ AND previous.provider_config_key = replacement.provider_config_key
36770
+ AND previous.lifecycle_status = 'disconnected'
36771
+ JOIN connected_account_charges AS charge
36772
+ ON charge.user_id = previous.user_id
36773
+ AND charge.connection_id = previous.id
36774
+ AND charge.period_start = ?
36775
+ WHERE replacement.id = ?
36776
+ AND replacement.user_id = ?
36777
+ AND replacement.transport = 'nango'
36778
+ AND replacement.lifecycle_status IN ('connected', 'needs_reauth')
36779
+ AND NOT EXISTS (
36780
+ SELECT 1 FROM connected_account_charges AS existing
36781
+ WHERE existing.user_id = replacement.user_id
36782
+ AND existing.connection_id = replacement.id
36783
+ AND existing.period_start = ?
36784
+ )
36785
+ ORDER BY previous.updated_at DESC, previous.id ASC
36786
+ LIMIT 1
36787
+ `,
36788
+ args: [normalizedUserId, periodStart, periodStart, connectionId, normalizedUserId, periodStart]
36789
+ },
36790
+ {
36791
+ sql: `
36792
+ UPDATE connected_account_charges
36793
+ SET connection_id = ?
36794
+ WHERE user_id = ?
36795
+ AND period_start = ?
36796
+ AND connection_id = (
36797
+ SELECT transfer.from_connection_id
36798
+ FROM connected_account_charge_transfers AS transfer
36799
+ JOIN connected_account_charges AS current_charge
36800
+ ON current_charge.user_id = transfer.user_id
36801
+ AND current_charge.period_start = transfer.period_start
36802
+ AND current_charge.connection_id = transfer.from_connection_id
36803
+ WHERE transfer.user_id = ?
36804
+ AND transfer.period_start = ?
36805
+ AND transfer.to_connection_id = ?
36806
+ ORDER BY transfer.transferred_at DESC, transfer.id DESC
36807
+ LIMIT 1
36808
+ )
36809
+ AND NOT EXISTS (
36810
+ SELECT 1 FROM connected_account_charges AS existing
36811
+ WHERE existing.user_id = ?
36812
+ AND existing.connection_id = ?
36813
+ AND existing.period_start = ?
36814
+ )
36815
+ `,
36816
+ args: [
36817
+ connectionId,
36818
+ normalizedUserId,
36819
+ periodStart,
36820
+ normalizedUserId,
36821
+ periodStart,
36822
+ connectionId,
36823
+ normalizedUserId,
36824
+ connectionId,
36825
+ periodStart
36826
+ ]
36827
+ }
36828
+ ], "write");
36829
+ const inherited = await db.execute({
36830
+ sql: "SELECT 1 FROM connected_account_charges WHERE user_id = ? AND connection_id = ? AND period_start = ? LIMIT 1",
36831
+ args: [normalizedUserId, connectionId, periodStart]
36832
+ });
36833
+ return inherited.rows.length > 0;
36834
+ }
36305
36835
  async function getConnectedAccountBillingState2(userId) {
36306
36836
  const result = await getDb().execute({
36307
36837
  sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
@@ -36430,6 +36960,7 @@ async function reconcileConnectedAccountBilling(user, connections) {
36430
36960
  const alreadyCharged = await chargedConnectionIds(user.id, periodStart);
36431
36961
  const unpaid = billableConnectionIds.filter((id) => !alreadyCharged.has(id));
36432
36962
  for (const connectionId of unpaid) {
36963
+ if (await inheritDisconnectedConnectionCharge(user.id, connectionId, periodStart)) continue;
36433
36964
  const feeMc = CONNECTED_ACTIVE_CONNECTION_MONTHLY_MC;
36434
36965
  const debit = await debitMcIdempotent(
36435
36966
  user.id,
@@ -36491,6 +37022,7 @@ var init_connected_account_billing = __esm({
36491
37022
  init_db();
36492
37023
  init_rates();
36493
37024
  init_connected_cost_telemetry();
37025
+ init_service_connections();
36494
37026
  DEFAULT_CONNECTED_ACCOUNT_PRICE_ID = "price_1TtYPAS8aAcsk3TGHOgrDZiQ";
36495
37027
  BILLABLE_LIFECYCLE = /* @__PURE__ */ new Set(["connected", "needs_reauth"]);
36496
37028
  ConnectedAccountBillingError = class extends Error {
@@ -40059,7 +40591,7 @@ async function applyAdjustment(input) {
40059
40591
  const credits = validateCredits(input.credits, input.confirmLarge === true);
40060
40592
  const reason = validateReason(input.reason);
40061
40593
  const actor = validateActor(input.actor);
40062
- const reference = input.reference?.trim() || `adj_${(0, import_node_crypto21.randomUUID)()}`;
40594
+ const reference = input.reference?.trim() || `adj_${(0, import_node_crypto22.randomUUID)()}`;
40063
40595
  if (reference.length > 120) throw new AdminCreditError(400, "reference must be at most 120 characters");
40064
40596
  const db = getDb();
40065
40597
  const existing = await db.execute({
@@ -40204,11 +40736,11 @@ async function lookupAccount(target, limit = 20) {
40204
40736
  }))
40205
40737
  };
40206
40738
  }
40207
- var import_node_crypto21, MIN_ADJUSTMENT_CREDITS, LARGE_ADJUSTMENT_CREDITS, MAX_ADJUSTMENT_CREDITS, MIN_REASON_LENGTH, MAX_REASON_LENGTH, MAX_ACTOR_LENGTH, AdminCreditError;
40739
+ var import_node_crypto22, MIN_ADJUSTMENT_CREDITS, LARGE_ADJUSTMENT_CREDITS, MAX_ADJUSTMENT_CREDITS, MIN_REASON_LENGTH, MAX_REASON_LENGTH, MAX_ACTOR_LENGTH, AdminCreditError;
40208
40740
  var init_admin_credits = __esm({
40209
40741
  "src/api/admin-credits.ts"() {
40210
40742
  "use strict";
40211
- import_node_crypto21 = require("crypto");
40743
+ import_node_crypto22 = require("crypto");
40212
40744
  init_db();
40213
40745
  init_rates();
40214
40746
  MIN_ADJUSTMENT_CREDITS = 1;
@@ -42173,7 +42705,7 @@ async function packageMapsMedia(args) {
42173
42705
  files.push({ path: "summary.json", content: Buffer.from(JSON.stringify(summary, null, 2)) });
42174
42706
  files.push({ path: "images.jsonl", content: Buffer.from(cleanImages.map((image) => JSON.stringify(image)).join("\n") + "\n") });
42175
42707
  const archive = await zipBuffer(files);
42176
- const id = (0, import_node_crypto22.randomBytes)(6).toString("hex");
42708
+ const id = (0, import_node_crypto23.randomBytes)(6).toString("hex");
42177
42709
  const pointer = await createPrivateArtifact({
42178
42710
  policy: policy3(),
42179
42711
  ownerId: args.ownerId,
@@ -42186,13 +42718,13 @@ async function packageMapsMedia(args) {
42186
42718
  const localPath = token() ? null : (0, import_node_path13.join)(process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0, import_node_path13.join)((0, import_node_os9.homedir)(), "Downloads", "mcp-scraper"), "blobs", pointer.artifactId);
42187
42719
  return { media: args.media, artifact: { ...pointer, localPath } };
42188
42720
  }
42189
- var import_node_os9, import_node_path13, import_node_crypto22, import_yazl2, import_p_limit5, MAPS_MEDIA_ARTIFACT_PREFIX, MAPS_MEDIA_ARTIFACT_TTL_MS, MAPS_MEDIA_DOWNLOAD_TTL_MS, MAX_IMAGE_BYTES2, MAX_ARCHIVE_IMAGE_BYTES, MAX_INLINE_IMAGE_BYTES, MAX_INLINE_TOTAL_BYTES, DOWNLOAD_CONCURRENCY, MAX_REDIRECTS;
42721
+ var import_node_os9, import_node_path13, import_node_crypto23, import_yazl2, import_p_limit5, MAPS_MEDIA_ARTIFACT_PREFIX, MAPS_MEDIA_ARTIFACT_TTL_MS, MAPS_MEDIA_DOWNLOAD_TTL_MS, MAX_IMAGE_BYTES2, MAX_ARCHIVE_IMAGE_BYTES, MAX_INLINE_IMAGE_BYTES, MAX_INLINE_TOTAL_BYTES, DOWNLOAD_CONCURRENCY, MAX_REDIRECTS;
42190
42722
  var init_maps_media_artifacts = __esm({
42191
42723
  "src/api/maps-media-artifacts.ts"() {
42192
42724
  "use strict";
42193
42725
  import_node_os9 = require("os");
42194
42726
  import_node_path13 = require("path");
42195
- import_node_crypto22 = require("crypto");
42727
+ import_node_crypto23 = require("crypto");
42196
42728
  import_yazl2 = require("yazl");
42197
42729
  import_p_limit5 = __toESM(require("p-limit"), 1);
42198
42730
  init_private_artifacts();
@@ -42816,7 +43348,7 @@ function retryDelaySeconds(attempts) {
42816
43348
  }
42817
43349
  async function dispatchPendingDirectoryWorkflows(limit = 25) {
42818
43350
  const rows = await claimDirectoryWorkflowOutbox({
42819
- workerId: `directory-dispatch-${process.pid}-${(0, import_node_crypto23.randomUUID)().slice(0, 8)}`,
43351
+ workerId: `directory-dispatch-${process.pid}-${(0, import_node_crypto24.randomUUID)().slice(0, 8)}`,
42820
43352
  limit
42821
43353
  });
42822
43354
  const result = { claimed: rows.length, dispatched: 0, failed: 0 };
@@ -42838,11 +43370,11 @@ async function dispatchPendingDirectoryWorkflows(limit = 25) {
42838
43370
  }
42839
43371
  return result;
42840
43372
  }
42841
- var import_node_crypto23;
43373
+ var import_node_crypto24;
42842
43374
  var init_directory_workflow_dispatch = __esm({
42843
43375
  "src/api/directory-workflow-dispatch.ts"() {
42844
43376
  "use strict";
42845
- import_node_crypto23 = require("crypto");
43377
+ import_node_crypto24 = require("crypto");
42846
43378
  init_client();
42847
43379
  init_directory_workflow_repository();
42848
43380
  }
@@ -42854,7 +43386,7 @@ function safeOptions(options) {
42854
43386
  return safe2;
42855
43387
  }
42856
43388
  function requestFingerprint(options) {
42857
- return (0, import_node_crypto24.createHash)("sha256").update(JSON.stringify(safeOptions(options))).digest("hex");
43389
+ return (0, import_node_crypto25.createHash)("sha256").update(JSON.stringify(safeOptions(options))).digest("hex");
42858
43390
  }
42859
43391
  function idempotencyKey(raw) {
42860
43392
  if (raw !== void 0) {
@@ -42863,14 +43395,14 @@ function idempotencyKey(raw) {
42863
43395
  if (trimmed.length > 500) return { ok: false, message: "Idempotency-Key must be 500 characters or fewer." };
42864
43396
  return { ok: true, key: trimmed };
42865
43397
  }
42866
- return { ok: true, key: `directory-${(0, import_node_crypto24.randomUUID)()}` };
43398
+ return { ok: true, key: `directory-${(0, import_node_crypto25.randomUUID)()}` };
42867
43399
  }
42868
43400
  function debitKeyFor(userId, responseKey) {
42869
- const digest2 = (0, import_node_crypto24.createHash)("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
43401
+ const digest2 = (0, import_node_crypto25.createHash)("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
42870
43402
  return `directory-workflow:${userId}:${digest2}`;
42871
43403
  }
42872
43404
  function jobId() {
42873
- return `dir_${(0, import_node_crypto24.randomUUID)().replace(/-/g, "")}`;
43405
+ return `dir_${(0, import_node_crypto25.randomUUID)().replace(/-/g, "")}`;
42874
43406
  }
42875
43407
  function publicStatus(job, result) {
42876
43408
  if (job.status === "failed") return "failed";
@@ -42979,7 +43511,7 @@ async function runSynchronously(c, user, options, plan, responseKey) {
42979
43511
  const csv = renderDirectoryWorkflowCsv(result);
42980
43512
  const artifact = await createDirectoryCsvArtifact({
42981
43513
  ownerId: String(user.id),
42982
- jobId: (0, import_node_crypto24.createHash)("sha256").update(debitKey2).digest("hex").slice(0, 32),
43514
+ jobId: (0, import_node_crypto25.createHash)("sha256").update(debitKey2).digest("hex").slice(0, 32),
42983
43515
  createdAt: result.extractedAt,
42984
43516
  filename: `${options.state}-${options.query}-directory.csv`,
42985
43517
  csv,
@@ -43035,11 +43567,11 @@ async function runSynchronously(c, user, options, plan, responseKey) {
43035
43567
  await releaseConcurrencyGate(gate.lockId);
43036
43568
  }
43037
43569
  }
43038
- var import_node_crypto24, import_hono16, directoryApp;
43570
+ var import_node_crypto25, import_hono16, directoryApp;
43039
43571
  var init_directory_routes = __esm({
43040
43572
  "src/api/directory-routes.ts"() {
43041
43573
  "use strict";
43042
- import_node_crypto24 = require("crypto");
43574
+ import_node_crypto25 = require("crypto");
43043
43575
  import_hono16 = require("hono");
43044
43576
  init_api_auth();
43045
43577
  init_db();
@@ -43257,15 +43789,15 @@ function bounded(value, field, min, max) {
43257
43789
  return normalized;
43258
43790
  }
43259
43791
  function newLeadListUploadId() {
43260
- return `upl_${(0, import_node_crypto25.randomUUID)().replace(/-/g, "")}`;
43792
+ return `upl_${(0, import_node_crypto26.randomUUID)().replace(/-/g, "")}`;
43261
43793
  }
43262
43794
  function newImportedLeadListId() {
43263
- return `lst_${(0, import_node_crypto25.randomUUID)().replace(/-/g, "")}`;
43795
+ return `lst_${(0, import_node_crypto26.randomUUID)().replace(/-/g, "")}`;
43264
43796
  }
43265
43797
  async function ensureLeadListImportRepositorySchema() {
43266
43798
  const db = getDb();
43267
- if (schemaDb5 !== db) {
43268
- schemaDb5 = db;
43799
+ if (schemaDb6 !== db) {
43800
+ schemaDb6 = db;
43269
43801
  schemaPromise5 = null;
43270
43802
  }
43271
43803
  if (!schemaPromise5) {
@@ -43460,13 +43992,13 @@ async function deleteExpiredLeadListInputRecords(now = /* @__PURE__ */ new Date(
43460
43992
  ], "write");
43461
43993
  return { uploads: Number(uploads.rowsAffected ?? 0), lists: Number(lists.rowsAffected ?? 0) };
43462
43994
  }
43463
- var import_node_crypto25, schemaDb5, schemaPromise5;
43995
+ var import_node_crypto26, schemaDb6, schemaPromise5;
43464
43996
  var init_lead_list_import_repository = __esm({
43465
43997
  "src/api/lead-list-import-repository.ts"() {
43466
43998
  "use strict";
43467
- import_node_crypto25 = require("crypto");
43999
+ import_node_crypto26 = require("crypto");
43468
44000
  init_db();
43469
- schemaDb5 = null;
44001
+ schemaDb6 = null;
43470
44002
  schemaPromise5 = null;
43471
44003
  }
43472
44004
  });
@@ -43506,7 +44038,7 @@ function safeFilenameHint(value) {
43506
44038
  return cleaned || null;
43507
44039
  }
43508
44040
  function requestFingerprint2(filenameHint) {
43509
- return (0, import_node_crypto26.createHash)("sha256").update(JSON.stringify({ filenameHint })).digest("hex");
44041
+ return (0, import_node_crypto27.createHash)("sha256").update(JSON.stringify({ filenameHint })).digest("hex");
43510
44042
  }
43511
44043
  function publicBaseUrl() {
43512
44044
  const configured = process.env.MCP_SCRAPER_PUBLIC_BASE_URL?.trim() || process.env.PUBLIC_BASE_URL?.trim() || (process.env.VERCEL_PROJECT_PRODUCTION_URL ? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}` : "") || "https://mcpscraper.dev";
@@ -43662,7 +44194,7 @@ async function inspectLeadListUpload(uploadId, ownerId2) {
43662
44194
  maxBytes: LEAD_LIST_UPLOAD_MAX_BYTES
43663
44195
  });
43664
44196
  if (!buffer) return null;
43665
- const sha2565 = (0, import_node_crypto26.createHash)("sha256").update(buffer).digest("hex");
44197
+ const sha2565 = (0, import_node_crypto27.createHash)("sha256").update(buffer).digest("hex");
43666
44198
  const completed = await completeLeadListUpload({
43667
44199
  id: record.id,
43668
44200
  ownerId: record.ownerId,
@@ -43786,11 +44318,11 @@ async function cleanupExpiredLeadListInputs(args = {}) {
43786
44318
  const records = await deleteExpiredLeadListInputRecords(now);
43787
44319
  return { deletedBlobs, deletedUploadRecords: records.uploads, deletedListRecords: records.lists };
43788
44320
  }
43789
- var import_node_crypto26, import_promises11, import_node_os10, import_node_path14, LEAD_LIST_UPLOAD_PREFIX, LEAD_LIST_NORMALIZED_PREFIX, LEAD_LIST_UPLOAD_MAX_BYTES, LEAD_LIST_UPLOAD_URL_TTL_MS, LEAD_LIST_UPLOAD_SOURCE_TTL_MS, LEAD_LIST_NORMALIZED_TTL_MS, LEAD_LIST_DOWNLOAD_TTL_MS, LEAD_LIST_UPLOAD_MIME_TYPES;
44321
+ var import_node_crypto27, import_promises11, import_node_os10, import_node_path14, LEAD_LIST_UPLOAD_PREFIX, LEAD_LIST_NORMALIZED_PREFIX, LEAD_LIST_UPLOAD_MAX_BYTES, LEAD_LIST_UPLOAD_URL_TTL_MS, LEAD_LIST_UPLOAD_SOURCE_TTL_MS, LEAD_LIST_NORMALIZED_TTL_MS, LEAD_LIST_DOWNLOAD_TTL_MS, LEAD_LIST_UPLOAD_MIME_TYPES;
43790
44322
  var init_lead_list_input_artifacts = __esm({
43791
44323
  "src/api/lead-list-input-artifacts.ts"() {
43792
44324
  "use strict";
43793
- import_node_crypto26 = require("crypto");
44325
+ import_node_crypto27 = require("crypto");
43794
44326
  import_promises11 = require("fs/promises");
43795
44327
  import_node_os10 = require("os");
43796
44328
  import_node_path14 = require("path");
@@ -44173,7 +44705,7 @@ function canonicalize(value) {
44173
44705
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalize(object[key])}`).join(",")}}`;
44174
44706
  }
44175
44707
  function fingerprint(value) {
44176
- return (0, import_node_crypto27.createHash)("sha256").update(canonicalize(value)).digest("hex");
44708
+ return (0, import_node_crypto28.createHash)("sha256").update(canonicalize(value)).digest("hex");
44177
44709
  }
44178
44710
  async function safeJson(c) {
44179
44711
  try {
@@ -44250,11 +44782,11 @@ function textDelimiter(raw, text2, observedMime) {
44250
44782
  if (observedMime === "text/tab-separated-values") return " ";
44251
44783
  return detectLeadListTextDelimiter(text2);
44252
44784
  }
44253
- var import_node_crypto27, import_hono17, leadListInputApp;
44785
+ var import_node_crypto28, import_hono17, leadListInputApp;
44254
44786
  var init_lead_list_input_routes = __esm({
44255
44787
  "src/api/lead-list-input-routes.ts"() {
44256
44788
  "use strict";
44257
- import_node_crypto27 = require("crypto");
44789
+ import_node_crypto28 = require("crypto");
44258
44790
  import_hono17 = require("hono");
44259
44791
  init_api_auth();
44260
44792
  init_lead_list_input_artifacts();
@@ -44446,7 +44978,7 @@ function retryDelaySeconds2(attempts) {
44446
44978
  return Math.min(3600, Math.max(15, 15 * 2 ** Math.min(8, Math.max(0, attempts - 1))));
44447
44979
  }
44448
44980
  async function dispatchPendingLeadListEnrichments(limit = 25) {
44449
- const rows = await claimLeadListEnrichmentOutbox({ workerId: `lead-list-dispatch-${process.pid}-${(0, import_node_crypto28.randomUUID)().slice(0, 8)}`, limit });
44981
+ const rows = await claimLeadListEnrichmentOutbox({ workerId: `lead-list-dispatch-${process.pid}-${(0, import_node_crypto29.randomUUID)().slice(0, 8)}`, limit });
44450
44982
  const result = { claimed: rows.length, dispatched: 0, failed: 0 };
44451
44983
  for (const row of rows) {
44452
44984
  if (!row.claimToken) continue;
@@ -44465,11 +44997,11 @@ async function dispatchPendingLeadListEnrichments(limit = 25) {
44465
44997
  }
44466
44998
  return result;
44467
44999
  }
44468
- var import_node_crypto28;
45000
+ var import_node_crypto29;
44469
45001
  var init_lead_list_enrichment_dispatch = __esm({
44470
45002
  "src/api/lead-list-enrichment-dispatch.ts"() {
44471
45003
  "use strict";
44472
- import_node_crypto28 = require("crypto");
45004
+ import_node_crypto29 = require("crypto");
44473
45005
  init_client();
44474
45006
  init_lead_list_enrichment_repository();
44475
45007
  }
@@ -44485,15 +45017,15 @@ function idempotencyKey2(raw) {
44485
45017
  return { ok: true, value };
44486
45018
  }
44487
45019
  function newJobId() {
44488
- return `lle_${(0, import_node_crypto29.randomUUID)().replace(/-/g, "")}`;
45020
+ return `lle_${(0, import_node_crypto30.randomUUID)().replace(/-/g, "")}`;
44489
45021
  }
44490
45022
  function debitKeyFor2(userId, key) {
44491
- const digest2 = (0, import_node_crypto29.createHash)("sha256").update(String(userId)).update("\0").update(key).digest("hex");
45023
+ const digest2 = (0, import_node_crypto30.createHash)("sha256").update(String(userId)).update("\0").update(key).digest("hex");
44492
45024
  return `lead-list-enrichment:${userId}:${digest2}`;
44493
45025
  }
44494
45026
  function inlineSourceDigest(headers, rows) {
44495
45027
  const values = rows.map((row) => headers.map((header) => row[header] ?? null));
44496
- return (0, import_node_crypto29.createHash)("sha256").update(stableCanonicalJson({ headers, values })).digest("hex");
45028
+ return (0, import_node_crypto30.createHash)("sha256").update(stableCanonicalJson({ headers, values })).digest("hex");
44497
45029
  }
44498
45030
  function issueMessage(error) {
44499
45031
  const issue = error.issues[0];
@@ -44691,11 +45223,11 @@ async function resolveInput(ownerId2, parsed) {
44691
45223
  sourceDigest: sourceDigest ?? inlineSourceDigest(normalized.headers, rows)
44692
45224
  };
44693
45225
  }
44694
- var import_node_crypto29, import_hono18, import_zod31, SourceSchema, StartSchema, TERMINAL, leadListEnrichmentApp;
45226
+ var import_node_crypto30, import_hono18, import_zod31, SourceSchema, StartSchema, TERMINAL, leadListEnrichmentApp;
44695
45227
  var init_lead_list_enrichment_routes = __esm({
44696
45228
  "src/api/lead-list-enrichment-routes.ts"() {
44697
45229
  "use strict";
44698
- import_node_crypto29 = require("crypto");
45230
+ import_node_crypto30 = require("crypto");
44699
45231
  import_hono18 = require("hono");
44700
45232
  import_zod31 = require("zod");
44701
45233
  init_api_auth();
@@ -48162,7 +48694,7 @@ async function readManifestFromSummary(summary) {
48162
48694
  function webhookSignature(body, timestamp2) {
48163
48695
  const secret2 = process.env.MCP_SCRAPER_WEBHOOK_SECRET?.trim();
48164
48696
  if (!secret2) return null;
48165
- return (0, import_node_crypto30.createHmac)("sha256", secret2).update(`${timestamp2}.${body}`).digest("hex");
48697
+ return (0, import_node_crypto31.createHmac)("sha256", secret2).update(`${timestamp2}.${body}`).digest("hex");
48166
48698
  }
48167
48699
  async function deliverWorkflowWebhook(input) {
48168
48700
  if (!input.webhookUrl) return;
@@ -48369,11 +48901,11 @@ async function dispatchDueWorkflowSchedules(apiUrl, limit = 3) {
48369
48901
  }
48370
48902
  return { dispatched: results.length, results };
48371
48903
  }
48372
- var import_node_crypto30, import_promises14, import_hono21, import_zod41, workflowApp, WorkflowInputSchema, WorkflowIdSchema, CadenceSchema, ScheduleStatusSchema, RunBodySchema, ScheduleCreateSchema, SchedulePatchSchema, TERMINAL_RUN_STATUSES;
48904
+ var import_node_crypto31, import_promises14, import_hono21, import_zod41, workflowApp, WorkflowInputSchema, WorkflowIdSchema, CadenceSchema, ScheduleStatusSchema, RunBodySchema, ScheduleCreateSchema, SchedulePatchSchema, TERMINAL_RUN_STATUSES;
48373
48905
  var init_workflow_routes = __esm({
48374
48906
  "src/api/workflow-routes.ts"() {
48375
48907
  "use strict";
48376
- import_node_crypto30 = require("crypto");
48908
+ import_node_crypto31 = require("crypto");
48377
48909
  import_promises14 = require("fs/promises");
48378
48910
  import_hono21 = require("hono");
48379
48911
  import_zod41 = require("zod");
@@ -48654,7 +49186,7 @@ var init_workflow_routes = __esm({
48654
49186
  // src/serp-intelligence/page-snapshot-extractor.ts
48655
49187
  function sha2562(value) {
48656
49188
  if (!value) return null;
48657
- return (0, import_node_crypto31.createHash)("sha256").update(value).digest("hex");
49189
+ return (0, import_node_crypto32.createHash)("sha256").update(value).digest("hex");
48658
49190
  }
48659
49191
  function countWords(markdown) {
48660
49192
  const matches = markdown.trim().match(/\b[\p{L}\p{N}][\p{L}\p{N}'-]*\b/gu);
@@ -48954,11 +49486,11 @@ async function capturePageSnapshots(targets, options = {}) {
48954
49486
  }
48955
49487
  };
48956
49488
  }
48957
- var import_node_crypto31, import_p_limit6, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_CONCURRENCY, DEFAULT_MAX_CONTENT_CHARS;
49489
+ var import_node_crypto32, import_p_limit6, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_CONCURRENCY, DEFAULT_MAX_CONTENT_CHARS;
48958
49490
  var init_page_snapshot_extractor = __esm({
48959
49491
  "src/serp-intelligence/page-snapshot-extractor.ts"() {
48960
49492
  "use strict";
48961
- import_node_crypto31 = require("crypto");
49493
+ import_node_crypto32 = require("crypto");
48962
49494
  import_p_limit6 = __toESM(require("p-limit"), 1);
48963
49495
  init_kpo_extractor();
48964
49496
  init_url_utils();
@@ -49455,21 +49987,21 @@ async function logRequestEventBestEffort(input) {
49455
49987
  }
49456
49988
  }
49457
49989
  function captureBillingKeys(userId, suppliedKey, body) {
49458
- const responseKey = suppliedKey?.trim() || (0, import_node_crypto32.randomUUID)();
49459
- const requestFingerprint3 = (0, import_node_crypto32.createHash)("sha256").update(JSON.stringify(body)).digest("hex");
49460
- const keyDigest = (0, import_node_crypto32.createHash)("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
49990
+ const responseKey = suppliedKey?.trim() || (0, import_node_crypto33.randomUUID)();
49991
+ const requestFingerprint3 = (0, import_node_crypto33.createHash)("sha256").update(JSON.stringify(body)).digest("hex");
49992
+ const keyDigest = (0, import_node_crypto33.createHash)("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
49461
49993
  return {
49462
49994
  responseKey,
49463
49995
  debitKey: `serp-capture:${userId}:${keyDigest}`,
49464
49996
  debitDescription: `${body.query} [request:${requestFingerprint3}]`
49465
49997
  };
49466
49998
  }
49467
- var import_hono22, import_node_crypto32, SERP_INTELLIGENCE_RATE_LIMIT, SERP_INTELLIGENCE_RATE_WINDOW_SECONDS, POST_CAPTURE_ROUTE_LABEL, POST_PAGE_SNAPSHOTS_ROUTE_LABEL, SERP_CAPTURE_BILLING_SOURCE, serpIntelligenceApp;
49999
+ var import_hono22, import_node_crypto33, SERP_INTELLIGENCE_RATE_LIMIT, SERP_INTELLIGENCE_RATE_WINDOW_SECONDS, POST_CAPTURE_ROUTE_LABEL, POST_PAGE_SNAPSHOTS_ROUTE_LABEL, SERP_CAPTURE_BILLING_SOURCE, serpIntelligenceApp;
49468
50000
  var init_serp_intelligence_routes = __esm({
49469
50001
  "src/api/serp-intelligence-routes.ts"() {
49470
50002
  "use strict";
49471
50003
  import_hono22 = require("hono");
49472
- import_node_crypto32 = require("crypto");
50004
+ import_node_crypto33 = require("crypto");
49473
50005
  init_browser_service_env();
49474
50006
  init_page_snapshot_extractor();
49475
50007
  init_serp_capture_service();
@@ -49706,7 +50238,7 @@ var PACKAGE_VERSION;
49706
50238
  var init_version = __esm({
49707
50239
  "src/version.ts"() {
49708
50240
  "use strict";
49709
- PACKAGE_VERSION = "0.66.6";
50241
+ PACKAGE_VERSION = "0.67.0";
49710
50242
  }
49711
50243
  });
49712
50244
 
@@ -49775,11 +50307,21 @@ seam is noted so you can chain them.
49775
50307
  - Plan a new or edited entity -> **commons_prepare_entity**. This is the Commons equivalent of
49776
50308
  prepare-memory-write: it returns the live entity profile contract, duplicate candidates, tag reuse/review/create
49777
50309
  guidance, recommended sections, optional sections, and heading guidance before any write occurs.
49778
- - Validate a composed entity without writing -> **commons_validate_entity**. Use it after composing and before submit
49779
- to catch missing featured images, missing source/body evidence, duplicate candidates, and
49780
- unsupported placeholder sections.
50310
+ - Host a featured image -> **commons_host_image**. The normal write sequence is **commons_prepare_entity ->
50311
+ compose the payload -> commons_host_image -> commons_validate_entity -> commons_submit_entity**. Put the
50312
+ permanent returned URL in featuredImage.url. Use imageBase64 for pasted or AI-generated bytes, artifactId for an
50313
+ owner-scoped MCP Scraper image artifact, or sourceUrl for either a public HTTPS image or a webpage containing one.
50314
+ Webpage discovery tries Open Graph, Twitter, JSON-LD, then meaningful content images. Never pass a caller-local
50315
+ path, opaque chat attachment reference, or private/authenticated URL.
50316
+ - Validate a composed entity without writing -> **commons_validate_entity**. Use it after image hosting and before
50317
+ submit. Validation does not fetch or store image bytes; an external/unregistered featured image is reported in
50318
+ imageDiagnostics as not publishable until commons_host_image returns its permanent Commons URL. Validation also
50319
+ catches missing source/body evidence, duplicate candidates, and unsupported placeholder sections.
49781
50320
  - Create or edit a public wiki entity -> **commons_submit_entity**. This is a governed proposal write,
49782
- not direct rendered HTML editing. Normal workflow is prepare -> validate -> submit. Pick the closest entity type
50321
+ not direct rendered HTML editing. Submit retains defensive auto-hosting for a stable direct public HTTPS image,
50322
+ but image ingress failure returns an actionable tool error: host different bytes or a different stable source,
50323
+ replace featuredImage.url, validate again, and retry the same intended write with the same idempotency key.
50324
+ Pick the closest entity type
49783
50325
  first: SoftwareApplication, Organization, Person, Event, Place, Taxon, ScienceConcept, MathConcept, TechArticle,
49784
50326
  Trail, or PublicArticle.
49785
50327
  Creating a new entity costs 10 Credits; editing an existing entity costs 2 Credits. Preparation, validation,
@@ -51521,7 +52063,7 @@ var init_contracts = __esm({
51521
52063
  });
51522
52064
 
51523
52065
  // src/mcp/mcp-tool-schemas.ts
51524
- var import_zod45, WEBSITE_URL_OR_DOMAIN_ERROR, WebsiteUrlOrDomainSchema, HarvestPaaInputSchema, HarvestPaaHostedInputSchema, ExtractUrlBaseInputSchema, ExtractUrlInputSchema, ExtractUrlLocalInputSchema, DiffPageBaseInputSchema, DiffPageInputSchema, DiffPageLocalInputSchema, MapSiteUrlsInputSchema, MapWaybackSnapshotsInputSchema, ExtractSiteInputSchema, AnalyzeSiteSimilarityInputSchema, AuditSiteInputSchema, CheckSiteExportInputSchema, SiteExportReadInputSchema, SiteExportImageInputSchema, ArchiveReadInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, RedditTrendingInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, LeadScalarSchema2, LeadRowSchema, LeadColumnMapSchema2, LeadRowsImportSourceSchema, LeadCsvTextImportSourceSchema, LeadUploadImportSourceSchema, LeadListUploadStartInputSchema, LeadListImportInputSchema, LeadRowsEnrichmentSourceSchema, ImportedLeadListSourceSchema, LeadListEnrichInputSchema, LeadListEnrichStatusInputSchema, LocationMarketsInputSchema, CommonsSearchEntitiesInputSchema, CommonsGetEntityInputSchema, CommonsGetEntityLinksetInputSchema, CommonsFeaturedImageInputSchema, CommonsMediaInputSchema, CommonsCitationInputSchema, CommonsSourceInputSchema, CommonsRelatedLinkInputSchema, CommonsClaimInputSchema, CommonsPrepareEntityInputSchema, CommonsSubmitEntityInputSchema, CommonsValidateEntityInputSchema, CommonsGetEntityLedgerInputSchema, CommonsHostImageInputSchema, CommonsGetProposalInputSchema, CommonsSaveFilterInputSchema, CommonsListFiltersInputSchema, CommonsListNeedsLinksInputSchema, CommonsGenericOutputSchema, DirectoryWorkflowStatusInputSchema, LocalSourcebookSubmitInputSchema, LocalSourcebookCategorySchema, LocalSourcebookSchemaTypeInputSchema, LocalSourcebookTagCandidateObjectSchema, LocalSourcebookTagDecisionObjectSchema, LocalSourcebookIdentityObjectSchema, GetLocalSourcebookContractInputSchema, ListLocalSourcebookTagsInputSchema, ResolveLocalSourcebookTagsInputSchema, PrepareLocalSourcebookWriteInputSchema, ValidateLocalSourcebookWriteInputSchema, LocalSourcebookCaptureInputSchema, LocalSourcebookSubmissionStatusInputSchema, LocalSourcebookRefreshInputSchema, LocalSourcebookOutputSchema, ArtifactPointerOutputSchema, EditorialReadingRoomSiteSchema, EditorialReadingRoomImageSchema, EditorialReadingRoomArticleSchema, EditorialReadingRoomGuideInputSchema, EditorialReadingRoomGuideOutputSchema, CreateEditorialReadingRoomInputSchema, EditorialReadingRoomArtifactSchema, CreateEditorialReadingRoomOutputSchema, RenewEditorialReadingRoomDownloadInputSchema, RenewEditorialReadingRoomDownloadOutputSchema, CommonsPublicationSubdomainSchema, CommonsPreparePublicationInputSchema, CommonsValidatePublicationInputSchema, CommonsClaimPublicationInputSchema, CommonsPublishEditorialInputSchema, CommonsUpdateEditorialArticleInputSchema, CommonsGetPublicationInputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryCsvArtifactOutput, DirectoryWorkflowOutputSchema, LeadSuggestedColumnOutputSchema, LeadColumnMapSuggestionOutputSchema, LeadArtifactOutputSchema, LeadListUploadStartOutputSchema, LeadListImportOutputSchema, LeadCandidateOutputSchema, LeadProgressOutputSchema, LeadBillingOutputSchema, LeadAssociatedPersonSourceOutputSchema, LeadAssociatedPersonOutputSchema, LeadSampleRowOutputSchema, LeadListEnrichmentOutputSchema, LocationDatasetProvenanceOutput, LocationMarketsOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, PageMediaAssetOutput, PageMediaArtifactOutput, ExtractUrlOutputSchema, DiffPageOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, CheckSiteExportOutputSchema, SiteExportReadOutputSchema, SiteExportImageOutputSchema, ArchiveEntryOutputSchema, ArchiveReadOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, WaybackCaptureOutputSchema, MapWaybackSnapshotsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, RedditTrendingOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, TranscriptSignalOutput, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema, ReportArtifactReadInputSchema, ReportArtifactReadOutputSchema, ListServiceConnectionsInputSchema, ListServiceConnectionsOutputSchema, TestServiceConnectionInputSchema, TestServiceConnectionOutputSchema, ReadServiceConnectionInputSchema, ReadServiceConnectionOutputSchema, MetaAdCreativeMediaInputSchema, MetaAdCreativeMediaOutputSchema, ImportServiceConnectionToMemoryInputSchema, ImportServiceConnectionToMemoryOutputSchema, DescribeServiceConnectionToolInputSchema, DescribeServiceConnectionToolOutputSchema, ConnectedDataContinuationSchema, ExportConnectedServiceDataInputSchema, ConnectedDataArtifactSchema, ExportConnectedServiceDataOutputSchema, SearchConsoleTableColumnSchema, SearchConsoleTableFilterSchema, ExportSearchConsoleTableDataInputSchema, ExportSearchConsoleTableDataOutputSchema, RenewConnectedDataExportDownloadInputSchema, RenewConnectedDataExportDownloadOutputSchema, CallServiceConnectionActionInputSchema, CallServiceConnectionActionOutputSchema, SetScheduledActionConnectionsInputSchema, SetScheduledActionConnectionsOutputSchema, SlackSendMessageInputSchema, SlackSendMessageOutputSchema, GmailSendMessageInputSchema, GmailSendMessageOutputSchema, GmailSearchContactsInputSchema, GmailSearchContactsOutputSchema, GoogleCalendarCreateEventInputSchema, GoogleCalendarCreateEventOutputSchema, ZoomCreateMeetingInputSchema, ZoomCreateMeetingOutputSchema;
52066
+ var import_zod45, WEBSITE_URL_OR_DOMAIN_ERROR, WebsiteUrlOrDomainSchema, HarvestPaaInputSchema, HarvestPaaHostedInputSchema, ExtractUrlBaseInputSchema, ExtractUrlInputSchema, ExtractUrlLocalInputSchema, DiffPageBaseInputSchema, DiffPageInputSchema, DiffPageLocalInputSchema, MapSiteUrlsInputSchema, MapWaybackSnapshotsInputSchema, ExtractSiteInputSchema, AnalyzeSiteSimilarityInputSchema, AuditSiteInputSchema, CheckSiteExportInputSchema, SiteExportReadInputSchema, SiteExportImageInputSchema, ArchiveReadInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, RedditTrendingInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, LeadScalarSchema2, LeadRowSchema, LeadColumnMapSchema2, LeadRowsImportSourceSchema, LeadCsvTextImportSourceSchema, LeadUploadImportSourceSchema, LeadListUploadStartInputSchema, LeadListImportInputSchema, LeadRowsEnrichmentSourceSchema, ImportedLeadListSourceSchema, LeadListEnrichInputSchema, LeadListEnrichStatusInputSchema, LocationMarketsInputSchema, CommonsSearchEntitiesInputSchema, CommonsGetEntityInputSchema, CommonsGetEntityLinksetInputSchema, CommonsFeaturedImageInputSchema, CommonsMediaInputSchema, CommonsCitationInputSchema, CommonsSourceInputSchema, CommonsRelatedLinkInputSchema, CommonsClaimInputSchema, CommonsPrepareEntityInputSchema, CommonsSubmitEntityInputSchema, CommonsValidateEntityInputSchema, CommonsGetEntityLedgerInputSchema, CommonsHostImageInputSchema, CommonsHostImageOutputSchema, CommonsGetProposalInputSchema, CommonsSaveFilterInputSchema, CommonsListFiltersInputSchema, CommonsListNeedsLinksInputSchema, CommonsGenericOutputSchema, CommonsFeaturedImageDiagnosticsOutputSchema, CommonsValidateEntityOutputSchema, DirectoryWorkflowStatusInputSchema, LocalSourcebookSubmitInputSchema, LocalSourcebookCategorySchema, LocalSourcebookSchemaTypeInputSchema, LocalSourcebookTagCandidateObjectSchema, LocalSourcebookTagDecisionObjectSchema, LocalSourcebookIdentityObjectSchema, GetLocalSourcebookContractInputSchema, ListLocalSourcebookTagsInputSchema, ResolveLocalSourcebookTagsInputSchema, PrepareLocalSourcebookWriteInputSchema, ValidateLocalSourcebookWriteInputSchema, LocalSourcebookCaptureInputSchema, LocalSourcebookSubmissionStatusInputSchema, LocalSourcebookRefreshInputSchema, LocalSourcebookOutputSchema, ArtifactPointerOutputSchema, EditorialReadingRoomSiteSchema, EditorialReadingRoomImageSchema, EditorialReadingRoomArticleSchema, EditorialReadingRoomGuideInputSchema, EditorialReadingRoomGuideOutputSchema, CreateEditorialReadingRoomInputSchema, EditorialReadingRoomArtifactSchema, CreateEditorialReadingRoomOutputSchema, RenewEditorialReadingRoomDownloadInputSchema, RenewEditorialReadingRoomDownloadOutputSchema, CommonsPublicationSubdomainSchema, CommonsPreparePublicationInputSchema, CommonsValidatePublicationInputSchema, CommonsClaimPublicationInputSchema, CommonsPublishEditorialInputSchema, CommonsUpdateEditorialArticleInputSchema, CommonsGetPublicationInputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryCsvArtifactOutput, DirectoryWorkflowOutputSchema, LeadSuggestedColumnOutputSchema, LeadColumnMapSuggestionOutputSchema, LeadArtifactOutputSchema, LeadListUploadStartOutputSchema, LeadListImportOutputSchema, LeadCandidateOutputSchema, LeadProgressOutputSchema, LeadBillingOutputSchema, LeadAssociatedPersonSourceOutputSchema, LeadAssociatedPersonOutputSchema, LeadSampleRowOutputSchema, LeadListEnrichmentOutputSchema, LocationDatasetProvenanceOutput, LocationMarketsOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, PageMediaAssetOutput, PageMediaArtifactOutput, ExtractUrlOutputSchema, DiffPageOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, CheckSiteExportOutputSchema, SiteExportReadOutputSchema, SiteExportImageOutputSchema, ArchiveEntryOutputSchema, ArchiveReadOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, WaybackCaptureOutputSchema, MapWaybackSnapshotsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, RedditTrendingOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, TranscriptSignalOutput, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema, ReportArtifactReadInputSchema, ReportArtifactReadOutputSchema, ListServiceConnectionsInputSchema, ListServiceConnectionsOutputSchema, TestServiceConnectionInputSchema, TestServiceConnectionOutputSchema, ReadServiceConnectionInputSchema, ReadServiceConnectionOutputSchema, MetaAdCreativeMediaInputSchema, MetaAdCreativeMediaOutputSchema, ImportServiceConnectionToMemoryInputSchema, ImportServiceConnectionToMemoryOutputSchema, DescribeServiceConnectionToolInputSchema, DescribeServiceConnectionToolOutputSchema, ConnectedDataContinuationSchema, ExportConnectedServiceDataInputSchema, ConnectedDataArtifactSchema, ExportConnectedServiceDataOutputSchema, SearchConsoleTableColumnSchema, SearchConsoleTableFilterSchema, ExportSearchConsoleTableDataInputSchema, ExportSearchConsoleTableDataOutputSchema, RenewConnectedDataExportDownloadInputSchema, RenewConnectedDataExportDownloadOutputSchema, CallServiceConnectionActionInputSchema, CallServiceConnectionActionOutputSchema, SetScheduledActionConnectionsInputSchema, SetScheduledActionConnectionsOutputSchema, SlackSendMessageInputSchema, SlackSendMessageOutputSchema, GmailSendMessageInputSchema, GmailSendMessageOutputSchema, GmailSearchContactsInputSchema, GmailSearchContactsOutputSchema, GoogleCalendarCreateEventInputSchema, GoogleCalendarCreateEventOutputSchema, ZoomCreateMeetingInputSchema, ZoomCreateMeetingOutputSchema;
51525
52067
  var init_mcp_tool_schemas = __esm({
51526
52068
  "src/mcp/mcp-tool-schemas.ts"() {
51527
52069
  "use strict";
@@ -51934,7 +52476,7 @@ var init_mcp_tool_schemas = __esm({
51934
52476
  idOrSlug: import_zod45.z.string().trim().min(1).max(180).describe("Transparent Commons TPW-Q entity id or /wiki/ slug whose governed RFC 9264 linkset should be read.")
51935
52477
  };
51936
52478
  CommonsFeaturedImageInputSchema = import_zod45.z.object({
51937
- url: import_zod45.z.string().url().describe("Required public image URL for a publishable entity. Use extract_url includeFeaturedImage or preserved media when available."),
52479
+ url: import_zod45.z.string().url().describe("For a publish-ready entity, use the permanent URL returned by commons_host_image. Validation reports any unregistered external URL as not ready. Do not use a chat attachment reference, caller-local path, private URL, or HTML page URL here; pass the original image or webpage source to commons_host_image first."),
51938
52480
  alt: import_zod45.z.string().trim().max(500).optional(),
51939
52481
  caption: import_zod45.z.string().trim().max(1e3).optional(),
51940
52482
  sourceUrl: import_zod45.z.string().url().optional(),
@@ -52026,14 +52568,14 @@ var init_mcp_tool_schemas = __esm({
52026
52568
  maxCandidates: import_zod45.z.number().int().min(1).max(20).default(8).describe("Maximum duplicate candidates to return.")
52027
52569
  };
52028
52570
  CommonsSubmitEntityInputSchema = {
52029
- idempotencyKey: import_zod45.z.string().trim().min(8).max(200).describe("Required unique opaque ID for this intended Commons write. Reuse only when retrying the same write after a timeout; use a new value for each intentional create or edit."),
52571
+ idempotencyKey: import_zod45.z.string().trim().min(8).max(200).describe("Required unique opaque ID for this intended Commons write. Reuse it when retrying the same intended write after a timeout or after correcting a pre-persistence image-ingress failure; use a new value for each intentional create or edit."),
52030
52572
  title: import_zod45.z.string().trim().min(3).max(180).describe("Public page title. This is also used for duplicate slug/entity checks when slug is omitted."),
52031
52573
  description: import_zod45.z.string().trim().min(8).max(600).describe("Short article summary and schema.org description."),
52032
52574
  slug: import_zod45.z.string().trim().max(180).optional().describe("Optional URL slug. Omit to derive one from title."),
52033
52575
  entityId: import_zod45.z.string().trim().max(80).optional().describe("Existing Transparent Public Wiki entity id when proposing an edit. New entities normally omit this and receive a TPW-Q id."),
52034
52576
  entityType: import_zod45.z.string().trim().max(120).default("PublicArticle").describe("Backend Wikidata-style type. Prefer precise entity classes such as SoftwareApplication, Organization, Person, Event, Place, Taxon, ScienceConcept, MathConcept, TechArticle, or PublicArticle; the public article structure should match the selected type."),
52035
52577
  disambiguationName: import_zod45.z.string().trim().max(240).optional().describe("Clarifying name used when the concept could be confused with another entity."),
52036
- featuredImage: CommonsFeaturedImageInputSchema.optional().describe("Required for auto-published public entities. The image is also added to the media manifest if absent."),
52578
+ featuredImage: CommonsFeaturedImageInputSchema.optional().describe("Required for auto-published public entities. First call commons_host_image with attachment bytes, an image artifact, a direct image URL, or a webpage URL, then place its returned permanent URL here. Validation does not fetch external images and reports unregistered URLs as not publishable. Submit retains a compatibility fallback for public URLs. The image is also added to the media manifest if absent."),
52037
52579
  source: CommonsSourceInputSchema.optional().describe("Source provenance. Store original URL, source byline, and origin canonical here; canonical does not replace rights review."),
52038
52580
  tags: import_zod45.z.array(import_zod45.z.string().trim().min(1).max(80)).max(24).optional().describe("Standardized topic tags. Use existing/searchable concepts when possible."),
52039
52581
  keywords: import_zod45.z.array(import_zod45.z.string().trim().min(1).max(120)).max(50).optional().describe("SEO and retrieval keywords."),
@@ -52095,12 +52637,31 @@ var init_mcp_tool_schemas = __esm({
52095
52637
  idOrSlug: import_zod45.z.string().trim().min(1).max(180).describe("Published entity id or /wiki/ slug whose contribution ledger should be read.")
52096
52638
  };
52097
52639
  CommonsHostImageInputSchema = {
52098
- sourceUrl: import_zod45.z.string().url().optional().describe("Public https image URL to download and host. The wiki never serves a third-party URL directly, so pass the original source here rather than putting it on the entity."),
52099
- imageBase64: import_zod45.z.string().min(16).optional().describe("Base64 image bytes for an image you already hold. Use for images under about 3 MB; larger files should be published from a URL."),
52640
+ sourceUrl: import_zod45.z.string().url().optional().describe("Public HTTPS direct image URL or webpage URL. A direct image is hosted as-is; an HTML page is inspected for Open Graph, Twitter, JSON-LD, and meaningful content images, then the first usable candidate is hosted. Temporary signed URLs are consumed immediately and query credentials are not retained as provenance."),
52641
+ imageBase64: import_zod45.z.string().min(16).optional().describe("Base64 image bytes for an image already available to the caller, including a pasted attachment or AI-generated image. Use sourceType to preserve whether it was uploaded or generated. Keep the complete serialized request within the client transport limit."),
52642
+ artifactId: import_zod45.z.string().trim().min(1).max(500).optional().describe("Owner-scoped image artifact returned by an MCP Scraper media or screenshot workflow. The server reauthorizes ownership and reads the bytes directly; do not construct an artifact ID."),
52643
+ sourceType: import_zod45.z.enum(["uploaded", "ai_generated"]).optional().describe("Provenance for imageBase64 or artifactId. Use ai_generated only when an image model created the supplied bytes; otherwise use uploaded or omit it."),
52100
52644
  alt: import_zod45.z.string().trim().max(500).optional().describe("Alt text describing the image for readers who cannot see it."),
52101
52645
  license: import_zod45.z.string().trim().max(240).optional().describe("License or usage terms of the original image, preserved with the hosted copy."),
52102
52646
  attribution: import_zod45.z.string().trim().max(500).optional().describe("Credit line for the original photographer, publication, or archive.")
52103
52647
  };
52648
+ CommonsHostImageOutputSchema = {
52649
+ ok: import_zod45.z.boolean(),
52650
+ data: import_zod45.z.object({
52651
+ id: import_zod45.z.string().describe("Stable Commons image ID."),
52652
+ url: import_zod45.z.string().url().describe("Permanent Commons URL to use as featuredImage.url."),
52653
+ contentType: import_zod45.z.enum(["image/jpeg", "image/png", "image/gif", "image/webp"]),
52654
+ bytes: import_zod45.z.number().int().positive(),
52655
+ digest: import_zod45.z.string(),
52656
+ sourceUrl: import_zod45.z.string().url().nullable().describe("Sanitized direct asset URL that supplied the hosted bytes, when applicable."),
52657
+ sourcePageUrl: import_zod45.z.string().url().nullable().describe("Sanitized webpage URL from which the image was discovered, when applicable."),
52658
+ sourceKind: import_zod45.z.enum(["uploaded", "ai_generated", "artifact", "direct_url", "webpage", "registered"]),
52659
+ selectionSource: import_zod45.z.enum(["provided_bytes", "artifact", "direct_url", "og:image", "twitter:image", "json-ld", "content-image", "registered"]),
52660
+ reused: import_zod45.z.boolean().describe("True when identical image bytes were already stored.")
52661
+ }).optional(),
52662
+ error: import_zod45.z.string().optional(),
52663
+ message: import_zod45.z.string().optional()
52664
+ };
52104
52665
  CommonsGetProposalInputSchema = {
52105
52666
  proposalId: import_zod45.z.string().trim().min(1).max(120).describe("Proposal id returned by commons_submit_entity, in the form commons-proposal-<uuid>.")
52106
52667
  };
@@ -52128,6 +52689,30 @@ var init_mcp_tool_schemas = __esm({
52128
52689
  error: import_zod45.z.string().optional(),
52129
52690
  message: import_zod45.z.string().optional()
52130
52691
  };
52692
+ CommonsFeaturedImageDiagnosticsOutputSchema = import_zod45.z.object({
52693
+ state: import_zod45.z.enum(["missing", "external_unhosted", "commons_hosted"]).describe("Storage-readiness state for the candidate featured image."),
52694
+ url: import_zod45.z.string().url().nullable().describe("Normalized candidate featured-image URL, or null when none was supplied."),
52695
+ commonsImageId: import_zod45.z.string().nullable().describe("Registered Commons image ID when metadata proves the image is hosted, otherwise null."),
52696
+ readyForPublish: import_zod45.z.boolean().describe("Whether Commons storage metadata proves this featured image is ready to publish."),
52697
+ submitWillAttemptAutoHost: import_zod45.z.boolean().describe("Whether submit will use its compatibility fallback to try hosting this external image."),
52698
+ requiredAction: import_zod45.z.string().nullable().describe("Action the caller must take before submit, or null when the image is ready.")
52699
+ }).describe("Featured-image storage readiness determined without fetching or storing external bytes.");
52700
+ CommonsValidateEntityOutputSchema = {
52701
+ ok: import_zod45.z.boolean(),
52702
+ data: import_zod45.z.object({
52703
+ ok: import_zod45.z.literal(true),
52704
+ valid: import_zod45.z.boolean(),
52705
+ publishable: import_zod45.z.boolean(),
52706
+ autoPublishEligible: import_zod45.z.boolean(),
52707
+ errors: import_zod45.z.array(import_zod45.z.string()),
52708
+ warnings: import_zod45.z.array(import_zod45.z.string()),
52709
+ imageDiagnostics: import_zod45.z.object({
52710
+ featuredImage: CommonsFeaturedImageDiagnosticsOutputSchema
52711
+ }).describe("Read-only image readiness and recovery guidance for this payload.")
52712
+ }).passthrough().optional(),
52713
+ error: import_zod45.z.string().optional(),
52714
+ message: import_zod45.z.string().optional()
52715
+ };
52131
52716
  DirectoryWorkflowStatusInputSchema = {
52132
52717
  jobId: import_zod45.z.string().trim().min(1).describe("The jobId returned by directory_workflow. Poll until status is complete, partial, empty, or failed.")
52133
52718
  };
@@ -54839,11 +55424,11 @@ function requireTasksCapability(capabilityValue) {
54839
55424
  );
54840
55425
  }
54841
55426
  function taskKey(secret2) {
54842
- return (0, import_node_crypto33.createHash)("sha256").update("mcp-scraper-task-handle\0", "utf8").update(secret2, "utf8").digest();
55427
+ return (0, import_node_crypto34.createHash)("sha256").update("mcp-scraper-task-handle\0", "utf8").update(secret2, "utf8").digest();
54843
55428
  }
54844
55429
  function encodeTaskHandle(payload, secret2) {
54845
- const nonce = (0, import_node_crypto33.randomBytes)(12);
54846
- const cipher = (0, import_node_crypto33.createCipheriv)("aes-256-gcm", taskKey(secret2), nonce);
55430
+ const nonce = (0, import_node_crypto34.randomBytes)(12);
55431
+ const cipher = (0, import_node_crypto34.createCipheriv)("aes-256-gcm", taskKey(secret2), nonce);
54847
55432
  const ciphertext = Buffer.concat([
54848
55433
  cipher.update(JSON.stringify(payload), "utf8"),
54849
55434
  cipher.final()
@@ -54860,7 +55445,7 @@ function decodeTaskHandle(taskId, secret2, ownerId2) {
54860
55445
  const nonce = bytes.subarray(0, 12);
54861
55446
  const tag = bytes.subarray(bytes.length - 16);
54862
55447
  const ciphertext = bytes.subarray(12, bytes.length - 16);
54863
- const decipher = (0, import_node_crypto33.createDecipheriv)("aes-256-gcm", taskKey(secret2), nonce);
55448
+ const decipher = (0, import_node_crypto34.createDecipheriv)("aes-256-gcm", taskKey(secret2), nonce);
54864
55449
  decipher.setAuthTag(tag);
54865
55450
  const parsed = JSON.parse(Buffer.concat([
54866
55451
  decipher.update(ciphertext),
@@ -55224,12 +55809,12 @@ async function handleMcpTasksHttpRequest(request, executor, options) {
55224
55809
  return taskHttpError(id, error instanceof import_server.ProtocolError ? error : new import_server.ProtocolError(-32603, error instanceof Error ? error.message : "Internal error"));
55225
55810
  }
55226
55811
  }
55227
- var import_server, import_node_crypto33, import_zod46, MCP_TASKS_EXTENSION_ID, TASK_HANDLE_VERSION, TASK_TTL_MS, TASK_POLL_INTERVAL_MS, TASK_INVALID_PARAMS, TASK_MISSING_CAPABILITY, TASK_START_TOOL_NAMES;
55812
+ var import_server, import_node_crypto34, import_zod46, MCP_TASKS_EXTENSION_ID, TASK_HANDLE_VERSION, TASK_TTL_MS, TASK_POLL_INTERVAL_MS, TASK_INVALID_PARAMS, TASK_MISSING_CAPABILITY, TASK_START_TOOL_NAMES;
55228
55813
  var init_mcp_tasks_extension = __esm({
55229
55814
  "src/mcp/mcp-tasks-extension.ts"() {
55230
55815
  "use strict";
55231
55816
  import_server = require("@modelcontextprotocol/server");
55232
- import_node_crypto33 = require("crypto");
55817
+ import_node_crypto34 = require("crypto");
55233
55818
  import_zod46 = require("zod");
55234
55819
  init_mcp_tool_schemas();
55235
55820
  MCP_TASKS_EXTENSION_ID = "io.modelcontextprotocol/tasks";
@@ -55537,7 +56122,7 @@ var init_analytics_mcp_tools = __esm({
55537
56122
 
55538
56123
  // src/mcp/paa-mcp-server.ts
55539
56124
  function hashOwnerId(callerKey) {
55540
- return (0, import_node_crypto34.createHash)("sha256").update(callerKey).digest("hex").slice(0, 24);
56125
+ return (0, import_node_crypto35.createHash)("sha256").update(callerKey).digest("hex").slice(0, 24);
55541
56126
  }
55542
56127
  function liveWebToolAnnotations(title) {
55543
56128
  return {
@@ -56024,9 +56609,9 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56024
56609
  }, async (input) => executor.commonsPrepareEntity(input));
56025
56610
  server.registerTool("commons_validate_entity", {
56026
56611
  title: "Transparent Commons Validate Entity",
56027
- description: "Validate a proposed Transparent Commons entity payload without writing. Checks publishable basics, featured image, source/body evidence, existing-entity conflict state, heading profile alignment, and unsupported placeholder sections. Call this after composing the page and before commons_submit_entity.",
56612
+ description: "Read-only validation for a proposed Transparent Commons entity payload. Call it after commons_prepare_entity, composing the page, and commons_host_image. It does not fetch or store image bytes: an external or otherwise unregistered featured image remains not publishable until commons_host_image returns the permanent Commons URL. Inspect imageDiagnostics for readiness and the required action before commons_submit_entity. Also checks source/body evidence, existing-entity conflict state, heading profile alignment, and unsupported placeholder sections.",
56028
56613
  inputSchema: CommonsValidateEntityInputSchema,
56029
- outputSchema: recordOutputSchema("commons_validate_entity", CommonsGenericOutputSchema),
56614
+ outputSchema: recordOutputSchema("commons_validate_entity", CommonsValidateEntityOutputSchema),
56030
56615
  annotations: {
56031
56616
  title: "Transparent Commons Validate Entity",
56032
56617
  readOnlyHint: true,
@@ -56037,7 +56622,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56037
56622
  }, async (input) => executor.commonsValidateEntity(input));
56038
56623
  server.registerTool("commons_submit_entity", {
56039
56624
  title: "Transparent Commons Governed Entity Write",
56040
- description: "Create or propose an edit to a Transparent Commons public wiki entity through the governed MCP Scraper write plane. A new entity costs 10 Credits; an edit to an existing entity costs 2 Credits. Preparation and validation are free, and idempotent retries are not charged twice. This never edits rendered HTML directly. Normal workflow is commons_prepare_entity, compose, commons_validate_entity, then submit. commons_prepare_entity returns the entity profile contract and section guidance to compose against. Transparent Commons is an open wiki: any account may edit any entity, and every edit publishes and is recorded on the public contribution ledger. Publishing requires a featured image and body/source evidence. Pass baseRevision when editing so the ledger can show whether the edit was written on top of a newer revision. Requires idempotencyKey.",
56625
+ description: "Charged governed write that creates or proposes an edit to a Transparent Commons public wiki entity. A new entity costs 10 Credits; an edit costs 2 Credits. Preparation, image hosting, and validation are free, and idempotent retries are not charged twice. Normal workflow is commons_prepare_entity, compose the page, commons_host_image, commons_validate_entity, then submit with the returned permanent featuredImage.url. Submit retains a compatibility fallback that auto-hosts a stable direct public HTTPS image URL; if image ingress fails, it returns an actionable image tool error so you can host different bytes or a different stable source, replace the URL, validate again, and retry the same intended write with the same idempotencyKey. This never edits rendered HTML directly. Transparent Commons is an open wiki: any account may edit any entity, and every applied edit is recorded on the public contribution ledger. Publishing requires a ready featured image and body/source evidence. Pass baseRevision when editing so the ledger can show whether the edit was written on top of a newer revision. Requires idempotencyKey.",
56041
56626
  inputSchema: CommonsSubmitEntityInputSchema,
56042
56627
  outputSchema: recordOutputSchema("commons_submit_entity", CommonsGenericOutputSchema),
56043
56628
  annotations: {
@@ -56063,9 +56648,9 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56063
56648
  }, async (input) => executor.commonsGetEntityLedger(input));
56064
56649
  server.registerTool("commons_host_image", {
56065
56650
  title: "Host a Transparent Commons Image",
56066
- description: "Host an image from a public https URL or base64 bytes, returning a permanent Commons URL. Commons never serves a third-party image URL \u2014 commons_submit_entity hosts external images automatically, so use this only to host one before or independently of a write. JPEG, PNG, GIF, WebP up to 10 MB; SVG rejected. Identical bytes are stored once.",
56651
+ description: "Turn one supplied image source into a permanent Transparent Commons image before validation and submission. Provide exactly one of imageBase64 for a pasted or AI-generated image, artifactId for an owner-scoped MCP Scraper image artifact, or sourceUrl for either a direct public HTTPS image or a webpage containing the desired image. Webpages are inspected in deterministic order for Open Graph, Twitter, JSON-LD, and meaningful content images; unusable candidates fall through to the next candidate. Returns the permanent URL for featuredImage.url plus source and selection provenance. JPEG, PNG, GIF, and WebP are accepted up to 10 MB; SVG is rejected. Identical bytes are stored once. Never pass a caller-local path or private/authenticated URL.",
56067
56652
  inputSchema: CommonsHostImageInputSchema,
56068
- outputSchema: recordOutputSchema("commons_host_image", CommonsGenericOutputSchema),
56653
+ outputSchema: recordOutputSchema("commons_host_image", CommonsHostImageOutputSchema),
56069
56654
  annotations: {
56070
56655
  title: "Host a Transparent Commons Image",
56071
56656
  readOnlyHint: false,
@@ -56525,7 +57110,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56525
57110
  annotations: { title: "Set Scheduled Action Connections", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
56526
57111
  }, async (input) => executor.setScheduledActionConnections(input));
56527
57112
  }
56528
- var import_server2, import_zod48, import_node_fs11, import_node_path17, import_node_crypto34, ACTION_CONFIRMATION_SCHEMA;
57113
+ var import_server2, import_zod48, import_node_fs11, import_node_path17, import_node_crypto35, ACTION_CONFIRMATION_SCHEMA;
56529
57114
  var init_paa_mcp_server = __esm({
56530
57115
  "src/mcp/paa-mcp-server.ts"() {
56531
57116
  "use strict";
@@ -56533,7 +57118,7 @@ var init_paa_mcp_server = __esm({
56533
57118
  import_zod48 = require("zod");
56534
57119
  import_node_fs11 = require("fs");
56535
57120
  import_node_path17 = require("path");
56536
- import_node_crypto34 = require("crypto");
57121
+ import_node_crypto35 = require("crypto");
56537
57122
  init_version();
56538
57123
  init_rates();
56539
57124
  init_mcp_response_formatter();
@@ -56655,11 +57240,11 @@ function analyticsReportPath(input, report) {
56655
57240
  const suffix2 = query.size ? `?${query.toString()}` : "";
56656
57241
  return `/analytics/sites/${encodeURIComponent(input.siteId)}/${report}${suffix2}`;
56657
57242
  }
56658
- var import_node_crypto35, HttpMcpToolExecutor;
57243
+ var import_node_crypto36, HttpMcpToolExecutor;
56659
57244
  var init_http_mcp_tool_executor = __esm({
56660
57245
  "src/mcp/http-mcp-tool-executor.ts"() {
56661
57246
  "use strict";
56662
- import_node_crypto35 = require("crypto");
57247
+ import_node_crypto36 = require("crypto");
56663
57248
  init_harvest_timeout();
56664
57249
  init_browser_service_env();
56665
57250
  init_errors();
@@ -56728,13 +57313,13 @@ var init_http_mcp_tool_executor = __esm({
56728
57313
  });
56729
57314
  }
56730
57315
  async callDirectoryWorkflowStart(body, explicitIdempotencyKey) {
56731
- const idempotencyKey4 = `mcp-directory-${(0, import_node_crypto35.createHash)("sha256").update(explicitIdempotencyKey).digest("hex")}`;
57316
+ const idempotencyKey4 = `mcp-directory-${(0, import_node_crypto36.createHash)("sha256").update(explicitIdempotencyKey).digest("hex")}`;
56732
57317
  return this.call("/directory/run", body, this.timeoutMs, "POST", {
56733
57318
  "Idempotency-Key": idempotencyKey4
56734
57319
  });
56735
57320
  }
56736
57321
  async callSiteExtractStart(toolName, body, explicitIdempotencyKey) {
56737
- const idempotencyKey4 = `mcp-site-${(0, import_node_crypto35.createHash)("sha256").update(toolName).update("\0").update(explicitIdempotencyKey).digest("hex")}`;
57322
+ const idempotencyKey4 = `mcp-site-${(0, import_node_crypto36.createHash)("sha256").update(toolName).update("\0").update(explicitIdempotencyKey).digest("hex")}`;
56738
57323
  return this.call("/extract-site", body, this.timeoutMs, "POST", {
56739
57324
  "Idempotency-Key": idempotencyKey4
56740
57325
  });
@@ -57178,7 +57763,7 @@ var init_http_mcp_tool_executor = __esm({
57178
57763
  report: input.report,
57179
57764
  format: input.format
57180
57765
  }, this.timeoutMs, "POST", {
57181
- "Idempotency-Key": `analytics-export-${(0, import_node_crypto35.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57766
+ "Idempotency-Key": `analytics-export-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57182
57767
  });
57183
57768
  }
57184
57769
  commonsSearchEntities(input) {
@@ -57217,7 +57802,7 @@ var init_http_mcp_tool_executor = __esm({
57217
57802
  commonsSubmitEntity(input) {
57218
57803
  const { idempotencyKey: idempotencyKey4, ...body } = input;
57219
57804
  return this.call("/commons/entities/propose", { ...body, idempotencyKey: idempotencyKey4 }, this.timeoutMs, "POST", {
57220
- "Idempotency-Key": `commons-${(0, import_node_crypto35.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57805
+ "Idempotency-Key": `commons-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57221
57806
  });
57222
57807
  }
57223
57808
  commonsGetEntityLedger(input) {
@@ -57232,7 +57817,7 @@ var init_http_mcp_tool_executor = __esm({
57232
57817
  commonsUpdateEditorialArticle(input) {
57233
57818
  const { idempotencyKey: idempotencyKey4, ...body } = input;
57234
57819
  return this.call("/commons/publications/articles", { ...body, idempotencyKey: idempotencyKey4 }, this.timeoutMs, "POST", {
57235
- "Idempotency-Key": `commons-article-${(0, import_node_crypto35.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57820
+ "Idempotency-Key": `commons-article-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57236
57821
  });
57237
57822
  }
57238
57823
  commonsSaveFilter(input) {
@@ -57253,13 +57838,13 @@ var init_http_mcp_tool_executor = __esm({
57253
57838
  commonsClaimPublication(input) {
57254
57839
  const { idempotencyKey: idempotencyKey4, ...body } = input;
57255
57840
  return this.call("/commons/publications/claim", { ...body, idempotencyKey: idempotencyKey4 }, this.timeoutMs, "POST", {
57256
- "Idempotency-Key": `commons-publication-claim-${(0, import_node_crypto35.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57841
+ "Idempotency-Key": `commons-publication-claim-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57257
57842
  });
57258
57843
  }
57259
57844
  commonsPublishEditorial(input) {
57260
57845
  const { idempotencyKey: idempotencyKey4, ...body } = input;
57261
57846
  return this.call("/commons/publications/publish", { ...body, idempotencyKey: idempotencyKey4 }, this.timeoutMs, "POST", {
57262
- "Idempotency-Key": `commons-publication-publish-${(0, import_node_crypto35.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57847
+ "Idempotency-Key": `commons-publication-publish-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57263
57848
  });
57264
57849
  }
57265
57850
  commonsGetPublication(input) {
@@ -57267,13 +57852,13 @@ var init_http_mcp_tool_executor = __esm({
57267
57852
  return this.getJson(input.subdomain ? `/commons/publications/${encodeURIComponent(input.subdomain)}?${query}` : `/commons/publications/me?${query}`);
57268
57853
  }
57269
57854
  async captureSerpSnapshot(input) {
57270
- const fingerprint2 = (0, import_node_crypto35.createHash)("sha256").update(JSON.stringify(input)).digest("hex");
57855
+ const fingerprint2 = (0, import_node_crypto36.createHash)("sha256").update(JSON.stringify(input)).digest("hex");
57271
57856
  const now = Date.now();
57272
57857
  for (const [pendingFingerprint, pendingEntry] of this.pendingSerpCaptureBillingKeys) {
57273
57858
  if (pendingEntry.expiresAt <= now) this.pendingSerpCaptureBillingKeys.delete(pendingFingerprint);
57274
57859
  }
57275
57860
  const pending = this.pendingSerpCaptureBillingKeys.get(fingerprint2);
57276
- const idempotencyKey4 = pending && pending.expiresAt > now ? pending.key : (0, import_node_crypto35.randomUUID)();
57861
+ const idempotencyKey4 = pending && pending.expiresAt > now ? pending.key : (0, import_node_crypto36.randomUUID)();
57277
57862
  this.pendingSerpCaptureBillingKeys.set(fingerprint2, {
57278
57863
  key: idempotencyKey4,
57279
57864
  expiresAt: now + 15 * 6e4
@@ -63283,7 +63868,7 @@ async function createImageSourceArtifact(args) {
63283
63868
  if (args.content.length === 0 || args.content.length > IMAGE_SOURCE_MAX_BYTES) {
63284
63869
  throw new Error("image_source_size_invalid");
63285
63870
  }
63286
- const id = (0, import_node_crypto36.randomUUID)().replaceAll("-", "");
63871
+ const id = (0, import_node_crypto37.randomUUID)().replaceAll("-", "");
63287
63872
  return createPrivateArtifact({
63288
63873
  policy: policy4(),
63289
63874
  ownerId: args.ownerId,
@@ -63302,11 +63887,11 @@ async function readOwnedImageSourceArtifact(args) {
63302
63887
  maxBytes: IMAGE_SOURCE_MAX_BYTES
63303
63888
  });
63304
63889
  }
63305
- var import_node_crypto36, IMAGE_SOURCE_ARTIFACT_PREFIX, IMAGE_SOURCE_ARTIFACT_TTL_MS, IMAGE_SOURCE_DOWNLOAD_TTL_MS, IMAGE_SOURCE_MAX_BYTES, ALLOWED_IMAGE_TYPES;
63890
+ var import_node_crypto37, IMAGE_SOURCE_ARTIFACT_PREFIX, IMAGE_SOURCE_ARTIFACT_TTL_MS, IMAGE_SOURCE_DOWNLOAD_TTL_MS, IMAGE_SOURCE_MAX_BYTES, ALLOWED_IMAGE_TYPES;
63306
63891
  var init_image_source_artifacts = __esm({
63307
63892
  "src/api/image-source-artifacts.ts"() {
63308
63893
  "use strict";
63309
- import_node_crypto36 = require("crypto");
63894
+ import_node_crypto37 = require("crypto");
63310
63895
  init_private_artifacts();
63311
63896
  IMAGE_SOURCE_ARTIFACT_PREFIX = "image-sources/";
63312
63897
  IMAGE_SOURCE_ARTIFACT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
@@ -64920,7 +65505,7 @@ async function runBrowserAgentMigration() {
64920
65505
  }
64921
65506
  async function createExtensionRow(input) {
64922
65507
  const db = getDb();
64923
- const id = `bext_${(0, import_node_crypto37.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
65508
+ const id = `bext_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
64924
65509
  await db.execute({
64925
65510
  sql: `INSERT INTO browser_agent_extensions (id, user_id, name, backend_id, backend_name, source, source_url, size_bytes)
64926
65511
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -64955,7 +65540,7 @@ async function deleteExtensionRow(userId, name) {
64955
65540
  }
64956
65541
  async function createAuthConnectionRow(input) {
64957
65542
  const db = getDb();
64958
- const connectionId = `authc_${(0, import_node_crypto37.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
65543
+ const connectionId = `authc_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
64959
65544
  await db.execute({
64960
65545
  sql: `INSERT INTO browser_auth_connections (connection_id, domain, profile, account_email, note, status, browser_agent_session_id)
64961
65546
  VALUES (?, ?, ?, ?, ?, 'NEEDS_AUTH', ?)`,
@@ -65040,7 +65625,7 @@ async function deleteProfileLabel(userId, profile) {
65040
65625
  }
65041
65626
  async function createSessionRow(input) {
65042
65627
  const db = getDb();
65043
- const id = `bas_${(0, import_node_crypto37.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
65628
+ const id = `bas_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
65044
65629
  await db.execute({
65045
65630
  sql: `INSERT INTO browser_agent_sessions (id, runtime_session_id, live_view_url, cdp_ws_url, status, label, user_id, concurrency_lock_id, last_action_at)
65046
65631
  VALUES (?, ?, ?, ?, 'open', ?, ?, ?, datetime('now'))`,
@@ -65117,7 +65702,7 @@ async function recordAction(input) {
65117
65702
  sql: `INSERT INTO browser_agent_actions (id, session_id, type, params_json, ok, error)
65118
65703
  VALUES (?, ?, ?, ?, ?, ?)`,
65119
65704
  args: [
65120
- `baa_${(0, import_node_crypto37.randomUUID)().replace(/-/g, "").slice(0, 20)}`,
65705
+ `baa_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`,
65121
65706
  input.sessionId,
65122
65707
  input.type,
65123
65708
  input.params == null ? null : JSON.stringify(input.params),
@@ -65157,11 +65742,11 @@ async function listReplayRows(sessionId) {
65157
65742
  });
65158
65743
  return res.rows;
65159
65744
  }
65160
- var import_node_crypto37, _ready2, _migrationPromise2, ORPHANED_SESSION_STATUS;
65745
+ var import_node_crypto38, _ready2, _migrationPromise2, ORPHANED_SESSION_STATUS;
65161
65746
  var init_browser_agent_db = __esm({
65162
65747
  "src/api/browser-agent-db.ts"() {
65163
65748
  "use strict";
65164
- import_node_crypto37 = require("crypto");
65749
+ import_node_crypto38 = require("crypto");
65165
65750
  init_db();
65166
65751
  _ready2 = false;
65167
65752
  _migrationPromise2 = null;
@@ -66303,7 +66888,7 @@ var init_browser_agent_service = __esm({
66303
66888
 
66304
66889
  // src/api/serp-identity-db.ts
66305
66890
  async function createSerpIdentityRow(input) {
66306
- const id = `serpi_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
66891
+ const id = `serpi_${(0, import_node_crypto39.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
66307
66892
  await getDb().execute({
66308
66893
  sql: `INSERT INTO serp_identities
66309
66894
  (id, user_id, name, kernel_profile_name, kernel_proxy_id, proxy_type, country, status)
@@ -66348,11 +66933,11 @@ async function deleteSerpIdentityRow(userId, name) {
66348
66933
  args: [userId, name]
66349
66934
  });
66350
66935
  }
66351
- var import_node_crypto38;
66936
+ var import_node_crypto39;
66352
66937
  var init_serp_identity_db = __esm({
66353
66938
  "src/api/serp-identity-db.ts"() {
66354
66939
  "use strict";
66355
- import_node_crypto38 = require("crypto");
66940
+ import_node_crypto39 = require("crypto");
66356
66941
  init_db();
66357
66942
  }
66358
66943
  });
@@ -66364,8 +66949,8 @@ function kernelClient() {
66364
66949
  return new import_sdk10.default({ apiKey });
66365
66950
  }
66366
66951
  function backendName(userId, name, resource) {
66367
- const digest2 = (0, import_node_crypto39.createHash)("sha256").update(`${userId}:${name}`).digest("hex").slice(0, 16);
66368
- const nonce = (0, import_node_crypto39.randomUUID)().replace(/-/g, "").slice(0, 8);
66952
+ const digest2 = (0, import_node_crypto40.createHash)("sha256").update(`${userId}:${name}`).digest("hex").slice(0, 16);
66953
+ const nonce = (0, import_node_crypto40.randomUUID)().replace(/-/g, "").slice(0, 8);
66369
66954
  return `mcp-serp-${resource}-${digest2}-${nonce}`;
66370
66955
  }
66371
66956
  function isNotFound2(error) {
@@ -66443,11 +67028,11 @@ async function deleteSerpIdentity(userId, name) {
66443
67028
  throw error;
66444
67029
  }
66445
67030
  }
66446
- var import_node_crypto39, import_sdk10, MAX_SERP_IDENTITIES_PER_USER;
67031
+ var import_node_crypto40, import_sdk10, MAX_SERP_IDENTITIES_PER_USER;
66447
67032
  var init_serp_identity_service = __esm({
66448
67033
  "src/api/serp-identity-service.ts"() {
66449
67034
  "use strict";
66450
- import_node_crypto39 = require("crypto");
67035
+ import_node_crypto40 = require("crypto");
66451
67036
  import_sdk10 = __toESM(require("@onkernel/sdk"), 1);
66452
67037
  init_browser_service_env();
66453
67038
  init_serp_identity_db();
@@ -67158,7 +67743,7 @@ function buildBrowserAgentRoutes() {
67158
67743
  }
67159
67744
  const existing = await getExtensionRow(user.id, name);
67160
67745
  if (existing) return c.json({ error: `an extension named "${name}" already exists \u2014 delete it first or pick another name` }, 409);
67161
- const backendName2 = `u${user.id}_${(0, import_node_crypto40.randomUUID)().replace(/-/g, "")}`;
67746
+ const backendName2 = `u${user.id}_${(0, import_node_crypto41.randomUUID)().replace(/-/g, "")}`;
67162
67747
  try {
67163
67748
  const imported = await importExtensionFromStore(storeUrl, backendName2);
67164
67749
  const row = await createExtensionRow({
@@ -67217,11 +67802,11 @@ function buildBrowserAgentRoutes() {
67217
67802
  });
67218
67803
  return app2;
67219
67804
  }
67220
- var import_node_crypto40, import_hono24, auth, DEFAULT_BROWSER_SESSION_LOCK_TTL_SECONDS, EXTENSION_NAME_RE, SERP_IDENTITY_NAME_RE;
67805
+ var import_node_crypto41, import_hono24, auth, DEFAULT_BROWSER_SESSION_LOCK_TTL_SECONDS, EXTENSION_NAME_RE, SERP_IDENTITY_NAME_RE;
67221
67806
  var init_browser_agent_routes = __esm({
67222
67807
  "src/api/browser-agent-routes.ts"() {
67223
67808
  "use strict";
67224
- import_node_crypto40 = require("crypto");
67809
+ import_node_crypto41 = require("crypto");
67225
67810
  import_hono24 = require("hono");
67226
67811
  init_api_auth();
67227
67812
  init_errors();
@@ -67779,7 +68364,7 @@ async function getKeys() {
67779
68364
  const privateKey = await (0, import_jose2.importPKCS8)(pem, "RS256", { extractable: true });
67780
68365
  const full = await (0, import_jose2.exportJWK)(privateKey);
67781
68366
  const publicJwk = { kty: full.kty, n: full.n, e: full.e };
67782
- const kid = (0, import_node_crypto41.createHash)("sha256").update(JSON.stringify({ e: publicJwk.e, kty: publicJwk.kty, n: publicJwk.n })).digest("base64url").slice(0, 16);
68367
+ const kid = (0, import_node_crypto42.createHash)("sha256").update(JSON.stringify({ e: publicJwk.e, kty: publicJwk.kty, n: publicJwk.n })).digest("base64url").slice(0, 16);
67783
68368
  publicJwk.kid = kid;
67784
68369
  publicJwk.alg = "RS256";
67785
68370
  publicJwk.use = "sig";
@@ -67958,23 +68543,23 @@ async function validateAuthRequest(p) {
67958
68543
  }
67959
68544
  function pkceMatches(verifier, challenge) {
67960
68545
  if (!verifier) return false;
67961
- const computed = (0, import_node_crypto41.createHash)("sha256").update(verifier).digest("base64url");
68546
+ const computed = (0, import_node_crypto42.createHash)("sha256").update(verifier).digest("base64url");
67962
68547
  return computed === challenge;
67963
68548
  }
67964
68549
  async function mintAccessToken(identity, scope, plan, audience) {
67965
68550
  const { privateKey, kid } = await getKeys();
67966
- return new import_jose2.SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti((0, import_node_crypto41.randomUUID)()).setExpirationTime(`${ACCESS_TTL_SECONDS}s`).sign(privateKey);
68551
+ return new import_jose2.SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti((0, import_node_crypto42.randomUUID)()).setExpirationTime(`${ACCESS_TTL_SECONDS}s`).sign(privateKey);
67967
68552
  }
67968
68553
  function tokenErrorResponse(c, error, description, status) {
67969
68554
  return c.json({ error, error_description: description }, status);
67970
68555
  }
67971
- var import_hono26, import_cookie, import_node_crypto41, import_jose2, ISSUER, RESOURCE, SCRAPER_RESOURCE, MEMORY_SCOPES, SCRAPER_SCOPES, SUPPORTED_SCOPES, ACCESS_TTL_SECONDS, REFRESH_TTL_SECONDS, CODE_TTL_SECONDS, ROTATION_GRACE_SECONDS, OAUTH_DATABASE_TIMEOUT_MS, OAuthDatabaseUnavailableError, secureCookies, sessionCookieOptions, cachedKeys, oauthApp;
68556
+ var import_hono26, import_cookie, import_node_crypto42, import_jose2, ISSUER, RESOURCE, SCRAPER_RESOURCE, MEMORY_SCOPES, SCRAPER_SCOPES, SUPPORTED_SCOPES, ACCESS_TTL_SECONDS, REFRESH_TTL_SECONDS, CODE_TTL_SECONDS, ROTATION_GRACE_SECONDS, OAUTH_DATABASE_TIMEOUT_MS, OAuthDatabaseUnavailableError, secureCookies, sessionCookieOptions, cachedKeys, oauthApp;
67972
68557
  var init_oauth_routes = __esm({
67973
68558
  "src/api/oauth-routes.ts"() {
67974
68559
  "use strict";
67975
68560
  import_hono26 = require("hono");
67976
68561
  import_cookie = require("hono/cookie");
67977
- import_node_crypto41 = require("crypto");
68562
+ import_node_crypto42 = require("crypto");
67978
68563
  import_jose2 = require("jose");
67979
68564
  init_session();
67980
68565
  init_db();
@@ -68081,7 +68666,7 @@ var init_oauth_routes = __esm({
68081
68666
  }
68082
68667
  }
68083
68668
  const clientName = typeof body.client_name === "string" ? body.client_name : null;
68084
- const clientId = `client_${(0, import_node_crypto41.randomBytes)(16).toString("hex")}`;
68669
+ const clientId = `client_${(0, import_node_crypto42.randomBytes)(16).toString("hex")}`;
68085
68670
  await withOAuthDatabaseDeadline("register-client", registerClient(clientId, redirectUris, clientName));
68086
68671
  console.log("[oauth-dcr] register OK client_id=%s redirect_uris=%s", clientId, JSON.stringify(redirectUris));
68087
68672
  return c.json({
@@ -68134,7 +68719,7 @@ var init_oauth_routes = __esm({
68134
68719
  if (action3 === "deny") return redirectWithError(p.redirect_uri, p.state, "access_denied");
68135
68720
  if (action3 !== "approve") return c.text("unsupported action", 400);
68136
68721
  const scope = negotiateScope(p.scope, user, p.resource);
68137
- const code = `code_${(0, import_node_crypto41.randomBytes)(32).toString("base64url")}`;
68722
+ const code = `code_${(0, import_node_crypto42.randomBytes)(32).toString("base64url")}`;
68138
68723
  const expiresAt = new Date(Date.now() + CODE_TTL_SECONDS * 1e3).toISOString();
68139
68724
  await withOAuthDatabaseDeadline("put-authorization-code", putCode({
68140
68725
  code,
@@ -68173,7 +68758,7 @@ var init_oauth_routes = __esm({
68173
68758
  const plan = user ? resolvePlan(user) : "free";
68174
68759
  const audience = record.resource ?? RESOURCE();
68175
68760
  const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
68176
- const refreshToken = `rt_${(0, import_node_crypto41.randomBytes)(40).toString("base64url")}`;
68761
+ const refreshToken = `rt_${(0, import_node_crypto42.randomBytes)(40).toString("base64url")}`;
68177
68762
  await withOAuthDatabaseDeadline("put-refresh-token", putRefresh({
68178
68763
  refresh_token: refreshToken,
68179
68764
  client_id: clientId,
@@ -68203,7 +68788,7 @@ var init_oauth_routes = __esm({
68203
68788
  const plan = user ? resolvePlan(user) : "free";
68204
68789
  const audience = record.resource ?? RESOURCE();
68205
68790
  const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
68206
- const nextRefresh = `rt_${(0, import_node_crypto41.randomBytes)(40).toString("base64url")}`;
68791
+ const nextRefresh = `rt_${(0, import_node_crypto42.randomBytes)(40).toString("base64url")}`;
68207
68792
  await withOAuthDatabaseDeadline("rotate-refresh-token", rotateRefresh(refreshToken, {
68208
68793
  refresh_token: nextRefresh,
68209
68794
  client_id: record.client_id,
@@ -69623,7 +70208,7 @@ function quoteUntrusted(value) {
69623
70208
  return clean3.split("\n").map((line) => `> ${line}`).join("\n");
69624
70209
  }
69625
70210
  function shortHash(value, length = 24) {
69626
- return (0, import_node_crypto42.createHash)("sha256").update(value).digest("hex").slice(0, length);
70211
+ return (0, import_node_crypto43.createHash)("sha256").update(value).digest("hex").slice(0, length);
69627
70212
  }
69628
70213
  function safePathPart(value) {
69629
70214
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "contact";
@@ -70111,11 +70696,11 @@ async function captureSupportMessage(memoryKey, email, options) {
70111
70696
  direction: options.direction
70112
70697
  };
70113
70698
  }
70114
- var import_node_crypto42, SUPPORT_TAGS, ISSUE_TAGS;
70699
+ var import_node_crypto43, SUPPORT_TAGS, ISSUE_TAGS;
70115
70700
  var init_resend_support_thread = __esm({
70116
70701
  "src/api/resend-support-thread.ts"() {
70117
70702
  "use strict";
70118
- import_node_crypto42 = require("crypto");
70703
+ import_node_crypto43 = require("crypto");
70119
70704
  init_memory();
70120
70705
  SUPPORT_TAGS = [
70121
70706
  {
@@ -70700,7 +71285,7 @@ async function claimWorkshopRegistration(input) {
70700
71285
  ) < ?
70701
71286
  `,
70702
71287
  args: [
70703
- (0, import_node_crypto43.randomUUID)(),
71288
+ (0, import_node_crypto44.randomUUID)(),
70704
71289
  input.eventSlug,
70705
71290
  input.email,
70706
71291
  input.firstName,
@@ -70717,7 +71302,7 @@ async function claimWorkshopRegistration(input) {
70717
71302
  id, event_slug, email, first_name, last_name, status
70718
71303
  ) VALUES (?, ?, ?, ?, ?, 'waitlisted')
70719
71304
  `,
70720
- args: [(0, import_node_crypto43.randomUUID)(), input.eventSlug, input.email, input.firstName, input.lastName || null]
71305
+ args: [(0, import_node_crypto44.randomUUID)(), input.eventSlug, input.email, input.firstName, input.lastName || null]
70721
71306
  });
70722
71307
  const waitlisted = await getWorkshopRegistration(input.eventSlug, input.email);
70723
71308
  if (!waitlisted) throw new Error("workshop registration could not be claimed");
@@ -70732,7 +71317,7 @@ async function registerWorkshopInterest(input) {
70732
71317
  id, event_slug, email, first_name, last_name, status
70733
71318
  ) VALUES (?, ?, ?, ?, ?, 'interested')
70734
71319
  `,
70735
- args: [(0, import_node_crypto43.randomUUID)(), input.eventSlug, input.email, input.firstName, input.lastName || null]
71320
+ args: [(0, import_node_crypto44.randomUUID)(), input.eventSlug, input.email, input.firstName, input.lastName || null]
70736
71321
  });
70737
71322
  const registration = await getWorkshopRegistration(input.eventSlug, input.email);
70738
71323
  if (!registration) throw new Error("workshop interest could not be recorded");
@@ -70762,11 +71347,11 @@ async function updateWorkshopRegistration(id, patch) {
70762
71347
  args: [...entries.map(([, value]) => value), id]
70763
71348
  });
70764
71349
  }
70765
- var import_node_crypto43;
71350
+ var import_node_crypto44;
70766
71351
  var init_workshop_registration_repository = __esm({
70767
71352
  "src/api/workshop-registration-repository.ts"() {
70768
71353
  "use strict";
70769
- import_node_crypto43 = require("crypto");
71354
+ import_node_crypto44 = require("crypto");
70770
71355
  init_db();
70771
71356
  }
70772
71357
  });
@@ -71339,7 +71924,7 @@ function renderEditorialReadingRoom(input, now = /* @__PURE__ */ new Date()) {
71339
71924
  articleCount: articles.length,
71340
71925
  wordCount: totalWordCount,
71341
71926
  bytes,
71342
- sha256: (0, import_node_crypto44.createHash)("sha256").update(html).digest("hex"),
71927
+ sha256: (0, import_node_crypto45.createHash)("sha256").update(html).digest("hex"),
71343
71928
  warnings
71344
71929
  };
71345
71930
  }
@@ -71422,14 +72007,14 @@ ${provenance}`;
71422
72007
  ...rendered,
71423
72008
  html,
71424
72009
  bytes: Buffer.byteLength(html),
71425
- sha256: (0, import_node_crypto44.createHash)("sha256").update(html).digest("hex")
72010
+ sha256: (0, import_node_crypto45.createHash)("sha256").update(html).digest("hex")
71426
72011
  };
71427
72012
  }
71428
- var import_node_crypto44, import_node_fs14, import_node_path21, import_marked, runtimeEntryDir, assetCache;
72013
+ var import_node_crypto45, import_node_fs14, import_node_path21, import_marked, runtimeEntryDir, assetCache;
71429
72014
  var init_render = __esm({
71430
72015
  "src/editorial-reading-room/render.ts"() {
71431
72016
  "use strict";
71432
- import_node_crypto44 = require("crypto");
72017
+ import_node_crypto45 = require("crypto");
71433
72018
  import_node_fs14 = require("fs");
71434
72019
  import_node_path21 = require("path");
71435
72020
  import_marked = require("marked");
@@ -71573,8 +72158,9 @@ var init_editorial_reading_room_routes = __esm({
71573
72158
 
71574
72159
  // src/api/commons-image-store.ts
71575
72160
  async function ensureCommonsImageSchema() {
71576
- if (imageSchemaReady) return;
71577
- await getDb().execute(`
72161
+ const db = getDb();
72162
+ if (imageSchemaReady && imageSchemaDb === db) return;
72163
+ await db.execute(`
71578
72164
  CREATE TABLE IF NOT EXISTS commons_images (
71579
72165
  id TEXT PRIMARY KEY,
71580
72166
  user_id INTEGER NOT NULL,
@@ -71590,8 +72176,9 @@ async function ensureCommonsImageSchema() {
71590
72176
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
71591
72177
  )
71592
72178
  `);
71593
- await getDb().execute(`CREATE UNIQUE INDEX IF NOT EXISTS commons_images_digest ON commons_images(digest)`);
72179
+ await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS commons_images_digest ON commons_images(digest)`);
71594
72180
  imageSchemaReady = true;
72181
+ imageSchemaDb = db;
71595
72182
  }
71596
72183
  function sniffImageFormat(bytes) {
71597
72184
  if (bytes.length < 12) return null;
@@ -71621,44 +72208,85 @@ async function assertPublicHttpsUrl(rawUrl) {
71621
72208
  try {
71622
72209
  url = new URL(rawUrl);
71623
72210
  } catch {
71624
- throw new CommonsImageError("image_url_invalid", `"${rawUrl}" is not a valid URL.`);
72211
+ throw new CommonsImageError("image_url_invalid", "The image source is not a valid URL.");
71625
72212
  }
71626
72213
  if (url.protocol !== "https:") {
71627
72214
  throw new CommonsImageError("image_url_not_https", "Commons image sources must be https URLs.");
71628
72215
  }
72216
+ if (url.username || url.password) {
72217
+ throw new CommonsImageError("image_url_invalid", "Commons image source URLs cannot contain embedded credentials.");
72218
+ }
71629
72219
  const hostname = url.hostname.replace(/^\[|\]$/g, "");
71630
72220
  const addresses = (0, import_node_net2.isIP)(hostname) ? [{ address: hostname }] : await (0, import_promises16.lookup)(hostname, { all: true }).catch(() => []);
71631
72221
  if (!addresses.length) {
71632
- throw new CommonsImageError("image_host_unresolvable", `Could not resolve ${url.hostname}.`);
72222
+ throw new CommonsImageError("image_host_unresolvable", "The image source host could not be resolved.");
71633
72223
  }
71634
72224
  if (addresses.some((entry) => isBlockedAddress(entry.address))) {
71635
72225
  throw new CommonsImageError("image_host_blocked", "Commons image sources cannot resolve to private or loopback addresses.");
71636
72226
  }
71637
72227
  return url;
71638
72228
  }
71639
- async function downloadImage(sourceUrl) {
72229
+ async function readResponseBytes(response, maxBytes) {
72230
+ const declaredLength = Number(response.headers.get("content-length") ?? "0");
72231
+ if (declaredLength > maxBytes) {
72232
+ throw new CommonsImageError("image_too_large", `Source exceeds the ${maxBytes} byte limit.`, 413);
72233
+ }
72234
+ if (!response.body) return Buffer.alloc(0);
72235
+ const reader = response.body.getReader();
72236
+ const chunks = [];
72237
+ let total = 0;
72238
+ while (true) {
72239
+ const { done, value } = await reader.read();
72240
+ if (done) break;
72241
+ total += value.byteLength;
72242
+ if (total > maxBytes) {
72243
+ await reader.cancel().catch(() => void 0);
72244
+ throw new CommonsImageError("image_too_large", `Source exceeds the ${maxBytes} byte limit.`, 413);
72245
+ }
72246
+ chunks.push(Buffer.from(value));
72247
+ }
72248
+ return Buffer.concat(chunks, total);
72249
+ }
72250
+ async function downloadPublicSource(sourceUrl) {
71640
72251
  let current = sourceUrl;
71641
72252
  for (let redirect = 0; redirect <= COMMONS_IMAGE_MAX_REDIRECTS; redirect += 1) {
71642
72253
  const url = await assertPublicHttpsUrl(current);
71643
- const response = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(COMMONS_IMAGE_FETCH_TIMEOUT_MS) });
72254
+ let response;
72255
+ try {
72256
+ response = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(COMMONS_IMAGE_FETCH_TIMEOUT_MS) });
72257
+ } catch (error) {
72258
+ if (error instanceof CommonsImageError) throw error;
72259
+ throw new CommonsImageError(
72260
+ "image_source_unavailable",
72261
+ "The image source could not be reached by Commons.",
72262
+ 422,
72263
+ true
72264
+ );
72265
+ }
71644
72266
  if (response.status >= 300 && response.status < 400) {
71645
72267
  const location2 = response.headers.get("location");
71646
72268
  if (!location2) throw new CommonsImageError("image_redirect_invalid", "Image source redirected without a destination.");
71647
- current = new URL(location2, url).toString();
72269
+ try {
72270
+ current = new URL(location2, url).toString();
72271
+ } catch {
72272
+ throw new CommonsImageError("image_redirect_invalid", "Image source redirected to an invalid destination.");
72273
+ }
71648
72274
  continue;
71649
72275
  }
71650
72276
  if (!response.ok) {
71651
- throw new CommonsImageError("image_source_unavailable", `Image source returned HTTP ${response.status}.`, 422);
71652
- }
71653
- const declaredLength = Number(response.headers.get("content-length") ?? "0");
71654
- if (declaredLength > COMMONS_IMAGE_MAX_BYTES) {
71655
- throw new CommonsImageError("image_too_large", `Image exceeds the ${COMMONS_IMAGE_MAX_BYTES} byte limit.`, 413);
71656
- }
71657
- const bytes = Buffer.from(await response.arrayBuffer());
71658
- if (bytes.length > COMMONS_IMAGE_MAX_BYTES) {
71659
- throw new CommonsImageError("image_too_large", `Image exceeds the ${COMMONS_IMAGE_MAX_BYTES} byte limit.`, 413);
72277
+ const retryable = response.status === 408 || response.status === 425 || response.status === 429 || response.status >= 500;
72278
+ throw new CommonsImageError(
72279
+ "image_source_unavailable",
72280
+ `The image source returned HTTP ${response.status} and could not be hosted.`,
72281
+ 422,
72282
+ retryable
72283
+ );
71660
72284
  }
71661
- return bytes;
72285
+ return {
72286
+ bytes: await readResponseBytes(response, COMMONS_IMAGE_MAX_BYTES),
72287
+ finalUrl: url.toString(),
72288
+ contentType: (response.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase()
72289
+ };
71662
72290
  }
71663
72291
  throw new CommonsImageError("image_redirect_limit", "Image source exceeded the redirect limit.");
71664
72292
  }
@@ -71671,20 +72299,160 @@ function decodeBase64Image(value) {
71671
72299
  }
71672
72300
  return bytes;
71673
72301
  }
72302
+ function safeProvenanceUrl(rawUrl) {
72303
+ try {
72304
+ const url = new URL(rawUrl);
72305
+ url.username = "";
72306
+ url.password = "";
72307
+ url.search = "";
72308
+ url.hash = "";
72309
+ return url.toString();
72310
+ } catch {
72311
+ return null;
72312
+ }
72313
+ }
72314
+ function looksLikeHtml(bytes, declaredType) {
72315
+ if (declaredType === "text/html" || declaredType === "application/xhtml+xml") return true;
72316
+ const prefix = bytes.subarray(0, 512).toString("utf8").trimStart().toLowerCase();
72317
+ return prefix.startsWith("<!doctype html") || prefix.startsWith("<html") || prefix.startsWith("<head");
72318
+ }
72319
+ async function resolveCommonsImageSource(input) {
72320
+ const supplied = [input.sourceUrl, input.imageBase64, input.artifactId].filter((value) => Boolean(value)).length;
72321
+ if (supplied === 0) {
72322
+ throw new CommonsImageError("image_source_missing", "Provide exactly one of sourceUrl, imageBase64, or artifactId.");
72323
+ }
72324
+ if (supplied !== 1) {
72325
+ throw new CommonsImageError("image_source_ambiguous", "Provide only one image source: sourceUrl, imageBase64, or artifactId.");
72326
+ }
72327
+ if (input.sourceUrl && input.sourceType) {
72328
+ throw new CommonsImageError("image_source_type_invalid", "sourceType applies only to imageBase64 or artifactId, not sourceUrl.");
72329
+ }
72330
+ if (input.imageBase64) {
72331
+ return {
72332
+ bytes: decodeBase64Image(input.imageBase64),
72333
+ sourceUrl: null,
72334
+ sourcePageUrl: null,
72335
+ sourceKind: input.sourceType ?? "uploaded",
72336
+ selectionSource: "provided_bytes"
72337
+ };
72338
+ }
72339
+ if (input.artifactId) {
72340
+ const bytes = await readOwnedImageSourceArtifact({ artifactId: input.artifactId, ownerId: String(input.userId) });
72341
+ if (!bytes) {
72342
+ throw new CommonsImageError("image_artifact_not_found", "The image artifact was not found, expired, or is not owned by this account.", 422);
72343
+ }
72344
+ if (bytes.length > COMMONS_IMAGE_MAX_BYTES) {
72345
+ throw new CommonsImageError("image_too_large", `Image exceeds the ${COMMONS_IMAGE_MAX_BYTES} byte limit.`, 413);
72346
+ }
72347
+ return {
72348
+ bytes,
72349
+ sourceUrl: null,
72350
+ sourcePageUrl: null,
72351
+ sourceKind: input.sourceType ?? "artifact",
72352
+ selectionSource: "artifact"
72353
+ };
72354
+ }
72355
+ const requestedUrl = String(input.sourceUrl);
72356
+ const downloaded = await downloadPublicSource(requestedUrl);
72357
+ if (sniffImageFormat(downloaded.bytes)) {
72358
+ return {
72359
+ bytes: downloaded.bytes,
72360
+ sourceUrl: safeProvenanceUrl(downloaded.finalUrl),
72361
+ sourcePageUrl: null,
72362
+ sourceKind: "direct_url",
72363
+ selectionSource: "direct_url"
72364
+ };
72365
+ }
72366
+ if (!looksLikeHtml(downloaded.bytes, downloaded.contentType)) {
72367
+ throw new CommonsImageError(
72368
+ "image_format_unsupported",
72369
+ "The supplied URL returned neither a supported image nor an HTML webpage with a discoverable image.",
72370
+ 415
72371
+ );
72372
+ }
72373
+ if (downloaded.bytes.length > COMMONS_IMAGE_PAGE_MAX_BYTES) {
72374
+ throw new CommonsImageError("image_page_too_large", `Webpage exceeds the ${COMMONS_IMAGE_PAGE_MAX_BYTES} byte discovery limit.`, 413);
72375
+ }
72376
+ const html = downloaded.bytes.toString("utf8");
72377
+ const document2 = loadHtmlDocument(html);
72378
+ const candidates = resolveFeaturedImageCandidates({
72379
+ html,
72380
+ meta: document2.meta,
72381
+ schema: document2.schema,
72382
+ pageUrl: downloaded.finalUrl
72383
+ }).slice(0, COMMONS_IMAGE_MAX_PAGE_CANDIDATES);
72384
+ if (!candidates.length) {
72385
+ throw new CommonsImageError("page_image_not_found", "The webpage did not expose a usable featured or content image.", 422);
72386
+ }
72387
+ let sawRetryableFailure = false;
72388
+ for (const candidate of candidates) {
72389
+ try {
72390
+ const image = await downloadPublicSource(candidate.url);
72391
+ if (!sniffImageFormat(image.bytes)) continue;
72392
+ return {
72393
+ bytes: image.bytes,
72394
+ sourceUrl: safeProvenanceUrl(image.finalUrl),
72395
+ sourcePageUrl: safeProvenanceUrl(downloaded.finalUrl),
72396
+ sourceKind: "webpage",
72397
+ selectionSource: candidate.source
72398
+ };
72399
+ } catch (error) {
72400
+ if (error instanceof CommonsImageError && error.retryable) sawRetryableFailure = true;
72401
+ }
72402
+ }
72403
+ throw new CommonsImageError(
72404
+ "page_image_candidates_unusable",
72405
+ "The webpage exposed image candidates, but none returned supported public image bytes.",
72406
+ 422,
72407
+ sawRetryableFailure
72408
+ );
72409
+ }
71674
72410
  function commonsImageBlobStore() {
71675
72411
  const token6 = process.env.COMMONS_IMAGE_READ_WRITE_TOKEN?.trim() || process.env.COMMONS_IMAGE_BLOB_READ_WRITE_TOKEN?.trim() || process.env.BLOB_READ_WRITE_TOKEN?.trim();
71676
72412
  return token6 ? new VercelBlobStore(token6) : getBlobStore();
71677
72413
  }
71678
- function isCommonsHostedImageUrl(url) {
71679
- return /^https:\/\/[^/]*(\.public\.blob\.vercel-storage\.com|blob\.vercel-storage\.com)\//i.test(url);
72414
+ function normalizeCommonsImageUrl(rawUrl) {
72415
+ try {
72416
+ const url = new URL(rawUrl.trim());
72417
+ if (url.protocol !== "https:") return null;
72418
+ url.hash = "";
72419
+ return url.toString();
72420
+ } catch {
72421
+ return null;
72422
+ }
72423
+ }
72424
+ async function findCommonsHostedImageByUrl(rawUrl) {
72425
+ const url = normalizeCommonsImageUrl(rawUrl);
72426
+ if (!url) return null;
72427
+ await ensureCommonsImageSchema();
72428
+ const result = await getDb().execute({
72429
+ sql: "SELECT id, url, content_type, bytes, digest, source_url FROM commons_images WHERE url = ? LIMIT 1",
72430
+ args: [url]
72431
+ });
72432
+ const row = result.rows[0];
72433
+ if (!row) return null;
72434
+ return {
72435
+ id: String(row.id),
72436
+ url: String(row.url),
72437
+ contentType: String(row.content_type),
72438
+ bytes: Number(row.bytes),
72439
+ digest: String(row.digest),
72440
+ sourceUrl: row.source_url ? String(row.source_url) : null,
72441
+ sourcePageUrl: null,
72442
+ sourceKind: "registered",
72443
+ selectionSource: "registered",
72444
+ reused: true
72445
+ };
71680
72446
  }
71681
72447
  async function hostCommonsImage(input) {
71682
72448
  if (hostOverride) return hostOverride(input);
71683
72449
  await ensureCommonsImageSchema();
71684
- if (!input.sourceUrl && !input.imageBase64) {
71685
- throw new CommonsImageError("image_source_missing", "Provide either sourceUrl or imageBase64.");
72450
+ if (input.sourceUrl && !input.imageBase64 && !input.artifactId && !input.sourceType) {
72451
+ const registered6 = await findCommonsHostedImageByUrl(input.sourceUrl);
72452
+ if (registered6) return registered6;
71686
72453
  }
71687
- const bytes = input.imageBase64 ? decodeBase64Image(input.imageBase64) : await downloadImage(String(input.sourceUrl));
72454
+ const resolved = await resolveCommonsImageSource(input);
72455
+ const bytes = resolved.bytes;
71688
72456
  const contentType = sniffImageFormat(bytes);
71689
72457
  if (!contentType) {
71690
72458
  throw new CommonsImageError(
@@ -71693,7 +72461,7 @@ async function hostCommonsImage(input) {
71693
72461
  415
71694
72462
  );
71695
72463
  }
71696
- const digest2 = (0, import_node_crypto45.createHash)("sha256").update(bytes).digest("hex");
72464
+ const digest2 = (0, import_node_crypto46.createHash)("sha256").update(bytes).digest("hex");
71697
72465
  const existing = await getDb().execute({
71698
72466
  sql: "SELECT id, url, content_type, bytes, source_url FROM commons_images WHERE digest = ? LIMIT 1",
71699
72467
  args: [digest2]
@@ -71706,7 +72474,10 @@ async function hostCommonsImage(input) {
71706
72474
  contentType: String(reusable.content_type),
71707
72475
  bytes: Number(reusable.bytes),
71708
72476
  digest: digest2,
71709
- sourceUrl: reusable.source_url ? String(reusable.source_url) : null,
72477
+ sourceUrl: resolved.sourceUrl,
72478
+ sourcePageUrl: resolved.sourcePageUrl,
72479
+ sourceKind: resolved.sourceKind,
72480
+ selectionSource: resolved.selectionSource,
71710
72481
  reused: true
71711
72482
  };
71712
72483
  }
@@ -71735,19 +72506,31 @@ async function hostCommonsImage(input) {
71735
72506
  stored.url,
71736
72507
  contentType,
71737
72508
  bytes.length,
71738
- input.sourceUrl ?? null,
72509
+ resolved.sourceUrl,
71739
72510
  input.alt ?? null,
71740
72511
  input.license ?? null,
71741
72512
  input.attribution ?? null,
71742
72513
  (/* @__PURE__ */ new Date()).toISOString()
71743
72514
  ]
71744
72515
  });
71745
- return { id, url: stored.url, contentType, bytes: bytes.length, digest: digest2, sourceUrl: input.sourceUrl ?? null, reused: false };
72516
+ return {
72517
+ id,
72518
+ url: stored.url,
72519
+ contentType,
72520
+ bytes: bytes.length,
72521
+ digest: digest2,
72522
+ sourceUrl: resolved.sourceUrl,
72523
+ sourcePageUrl: resolved.sourcePageUrl,
72524
+ sourceKind: resolved.sourceKind,
72525
+ selectionSource: resolved.selectionSource,
72526
+ reused: false
72527
+ };
71746
72528
  }
71747
72529
  async function hostEntityImages(input) {
71748
72530
  const rewrite = { mediaUrls: /* @__PURE__ */ new Map(), dropped: [] };
71749
72531
  const featuredUrl = input.featuredImage?.url;
71750
- if (featuredUrl && !isCommonsHostedImageUrl(featuredUrl)) {
72532
+ const registeredFeatured = featuredUrl ? await findCommonsHostedImageByUrl(featuredUrl) : null;
72533
+ if (featuredUrl && !registeredFeatured) {
71751
72534
  const hosted = await hostCommonsImage({
71752
72535
  userId: input.userId,
71753
72536
  sourceUrl: featuredUrl,
@@ -71755,17 +72538,19 @@ async function hostEntityImages(input) {
71755
72538
  license: input.featuredImage?.license,
71756
72539
  attribution: input.featuredImage?.sourceUrl
71757
72540
  }).catch((error) => {
71758
- const message = error instanceof Error ? error.message : String(error);
72541
+ if (error instanceof CommonsImageError) throw error;
71759
72542
  throw new CommonsImageError(
71760
- error instanceof CommonsImageError ? error.code : "featured_image_unhostable",
71761
- `The featured image could not be hosted, so this entity would publish with a link that can break: ${message}`,
72543
+ "featured_image_unhostable",
72544
+ "The featured image could not be hosted, so the entity was not persisted with a link that could break.",
71762
72545
  422
71763
72546
  );
71764
72547
  });
71765
72548
  rewrite.featuredImageUrl = hosted.url;
72549
+ rewrite.mediaUrls.set(featuredUrl, hosted.url);
71766
72550
  }
71767
72551
  for (const item of input.media ?? []) {
71768
- if (!item.url || isCommonsHostedImageUrl(item.url)) continue;
72552
+ if (item.url && rewrite.mediaUrls.has(item.url)) continue;
72553
+ if (!item.url || await findCommonsHostedImageByUrl(item.url)) continue;
71769
72554
  try {
71770
72555
  const hosted = await hostCommonsImage({
71771
72556
  userId: input.userId,
@@ -71781,29 +72566,40 @@ async function hostEntityImages(input) {
71781
72566
  }
71782
72567
  return rewrite;
71783
72568
  }
71784
- var import_node_crypto45, import_promises16, import_node_net2, COMMONS_IMAGE_MAX_BYTES, COMMONS_IMAGE_MAX_REDIRECTS, COMMONS_IMAGE_FETCH_TIMEOUT_MS, CommonsImageError, imageSchemaReady, hostOverride;
72569
+ var import_node_crypto46, import_promises16, import_node_net2, COMMONS_IMAGE_MAX_BYTES, COMMONS_IMAGE_MAX_REDIRECTS, COMMONS_IMAGE_FETCH_TIMEOUT_MS, COMMONS_IMAGE_PAGE_MAX_BYTES, COMMONS_IMAGE_MAX_PAGE_CANDIDATES, COMMONS_IMAGE_RETRY_GUIDANCE, CommonsImageError, imageSchemaReady, imageSchemaDb, hostOverride;
71785
72570
  var init_commons_image_store = __esm({
71786
72571
  "src/api/commons-image-store.ts"() {
71787
72572
  "use strict";
71788
- import_node_crypto45 = require("crypto");
72573
+ import_node_crypto46 = require("crypto");
71789
72574
  import_promises16 = require("dns/promises");
71790
72575
  import_node_net2 = require("net");
71791
72576
  init_blob_store();
71792
72577
  init_db();
72578
+ init_image_source_artifacts();
72579
+ init_html_document();
72580
+ init_wayback();
71793
72581
  COMMONS_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
71794
72582
  COMMONS_IMAGE_MAX_REDIRECTS = 3;
71795
72583
  COMMONS_IMAGE_FETCH_TIMEOUT_MS = 2e4;
72584
+ COMMONS_IMAGE_PAGE_MAX_BYTES = 2 * 1024 * 1024;
72585
+ COMMONS_IMAGE_MAX_PAGE_CANDIDATES = 12;
72586
+ COMMONS_IMAGE_RETRY_GUIDANCE = "Call commons_host_image with imageBase64, an owner-authorized artifactId, a public HTTPS image URL, or a public HTTPS webpage containing a usable featured image. Replace featuredImage.url with the returned permanent URL, validate again, then retry commons_submit_entity with the same idempotency key.";
71796
72587
  CommonsImageError = class extends Error {
71797
- constructor(code, message, httpStatus = 400) {
72588
+ constructor(code, message, httpStatus = 400, retryable = false, retryGuidance = COMMONS_IMAGE_RETRY_GUIDANCE) {
71798
72589
  super(message);
71799
72590
  this.code = code;
71800
72591
  this.httpStatus = httpStatus;
72592
+ this.retryable = retryable;
72593
+ this.retryGuidance = retryGuidance;
71801
72594
  this.name = "CommonsImageError";
71802
72595
  }
71803
72596
  code;
71804
72597
  httpStatus;
72598
+ retryable;
72599
+ retryGuidance;
71805
72600
  };
71806
72601
  imageSchemaReady = false;
72602
+ imageSchemaDb = null;
71807
72603
  hostOverride = null;
71808
72604
  }
71809
72605
  });
@@ -71910,7 +72706,7 @@ function buildCommonsLinkset(entity, claims) {
71910
72706
  context[claim.predicate] = targets;
71911
72707
  }
71912
72708
  const document2 = { linkset: [context] };
71913
- const etag = `"${(0, import_node_crypto46.createHash)("sha256").update(JSON.stringify(document2)).digest("hex")}"`;
72709
+ const etag = `"${(0, import_node_crypto47.createHash)("sha256").update(JSON.stringify(document2)).digest("hex")}"`;
71914
72710
  return { document: document2, etag, publicUrl, linksetUrl, profile: COMMONS_RELATIONSHIP_PROFILE };
71915
72711
  }
71916
72712
  function commonsLinksetDiscoveryHeader(idOrSlug) {
@@ -71978,11 +72774,11 @@ function normalizeHreflang(value) {
71978
72774
  const languages = [...new Set(value.map((item) => optionalText(item, 80)).filter((item) => Boolean(item)))];
71979
72775
  return languages.length ? languages : void 0;
71980
72776
  }
71981
- var import_node_crypto46, COMMONS_LINKSET_MEDIA_TYPE, COMMONS_RELATIONSHIP_PROFILE, REGISTERED_RELATIONS;
72777
+ var import_node_crypto47, COMMONS_LINKSET_MEDIA_TYPE, COMMONS_RELATIONSHIP_PROFILE, REGISTERED_RELATIONS;
71982
72778
  var init_commons_linksets = __esm({
71983
72779
  "src/api/commons-linksets.ts"() {
71984
72780
  "use strict";
71985
- import_node_crypto46 = require("crypto");
72781
+ import_node_crypto47 = require("crypto");
71986
72782
  COMMONS_LINKSET_MEDIA_TYPE = "application/linkset+json";
71987
72783
  COMMONS_RELATIONSHIP_PROFILE = "https://mcpscraper.dev/commons/profiles/relationships/v1";
71988
72784
  REGISTERED_RELATIONS = /* @__PURE__ */ new Set([
@@ -72025,9 +72821,9 @@ var init_commons_linksets = __esm({
72025
72821
 
72026
72822
  // src/api/commons-repository.ts
72027
72823
  async function ensureCommonsSchema() {
72028
- if (schemaReady) return;
72824
+ if (schemaReady2) return;
72029
72825
  if (await schemaObjectsPresent(COMMONS_SCHEMA_OBJECTS)) {
72030
- schemaReady = true;
72826
+ schemaReady2 = true;
72031
72827
  return;
72032
72828
  }
72033
72829
  const db = getDb();
@@ -72250,7 +73046,7 @@ async function ensureCommonsSchema() {
72250
73046
  args: [COMMONS_SCHEMA_VERSION]
72251
73047
  }
72252
73048
  ], "write");
72253
- schemaReady = true;
73049
+ schemaReady2 = true;
72254
73050
  }
72255
73051
  async function searchCommonsEntities(filters = {}, userId) {
72256
73052
  await ensureCommonsSchema();
@@ -72458,7 +73254,7 @@ async function prepareCommonsEntity(input, user) {
72458
73254
  normalizedEntityType: entityType,
72459
73255
  proposedSlug: slug4 || null,
72460
73256
  publicUrl: slug4 ? publicUrlForSlug(slug4) : null,
72461
- writePath: "commons_prepare_entity -> commons_validate_entity -> commons_submit_entity"
73257
+ writePath: "commons_prepare_entity -> compose the entity -> commons_host_image -> commons_validate_entity -> commons_submit_entity"
72462
73258
  },
72463
73259
  contract,
72464
73260
  billing: {
@@ -72480,8 +73276,8 @@ async function prepareCommonsEntity(input, user) {
72480
73276
  "Search likely duplicates before submitting a new entity. If one matches, edit it with entityId and baseRevision instead of creating another page.",
72481
73277
  "Use the returned profile as a contract, not a rigid empty template: do not include History, Reception, Pricing, Timeline, or similar sections unless source evidence supports them.",
72482
73278
  "Write neutral encyclopedia prose. Do not paste raw scrape Markdown, marketing layout fragments, or empty/filler headings.",
72483
- "Attach a featured image for auto-publish eligibility, preserve original URL/source byline/relCanonical when republishing, and put media assets in the media array.",
72484
- "Call commons_validate_entity with the composed payload before commons_submit_entity.",
73279
+ "For a featured image not already hosted by Commons, call commons_host_image and replace featuredImage.url with its returned permanent URL. Preserve original URL/source byline/relCanonical when republishing, and put media assets in the media array.",
73280
+ "Call commons_validate_entity with the composed, image-ready payload before commons_submit_entity.",
72485
73281
  "Validation is free. Submitting a new entity costs 10 Credits; submitting an edit to an existing entity costs 2 Credits."
72486
73282
  ]
72487
73283
  };
@@ -72496,6 +73292,8 @@ async function validateCommonsEntity(input, user) {
72496
73292
  const contract = commonsEntityProfileFor(input.entityType, input.seo?.schemaOrgType);
72497
73293
  const source = normalizeSource(input.source);
72498
73294
  const featuredImage = normalizeFeaturedImage(input.featuredImage);
73295
+ const normalizedFeaturedImageUrl = featuredImage?.url ? normalizeCommonsImageUrl(featuredImage.url) : null;
73296
+ const registeredFeaturedImage = normalizedFeaturedImageUrl ? await findCommonsHostedImageByUrl(normalizedFeaturedImageUrl) : null;
72499
73297
  const bodyMarkdown = safeCandidateBody(input.bodyMarkdown, input.contentSections);
72500
73298
  const headings = extractMarkdownHeadings(bodyMarkdown);
72501
73299
  const placeholderSections = unsupportedPlaceholderSections(bodyMarkdown);
@@ -72533,15 +73331,44 @@ async function validateCommonsEntity(input, user) {
72533
73331
  if (input.reviewPolicy === "always_review") {
72534
73332
  warnings.push("reviewPolicy is always_review, so the proposal will not auto-publish even if the payload is otherwise valid.");
72535
73333
  }
73334
+ if (featuredImage?.url && !registeredFeaturedImage) {
73335
+ warnings.push("The featured image is not registered in permanent Commons image storage. Call commons_host_image, replace featuredImage.url with its returned URL, and validate again before submitting.");
73336
+ }
72536
73337
  if (user?.id == null) warnings.push("No authenticated user was supplied to validation; submit still requires API-key authenticated user context.");
72537
73338
  const valid = errors.length === 0;
72538
- const autoPublishEligible = valid && input.reviewPolicy !== "always_review";
73339
+ const imageReadyForPublish = Boolean(registeredFeaturedImage);
73340
+ const publishable = valid && imageReadyForPublish;
73341
+ const autoPublishEligible = publishable && input.reviewPolicy !== "always_review";
72539
73342
  const tagVocabulary = await commonsTagVocabulary();
72540
73343
  return {
72541
73344
  ok: true,
72542
73345
  valid,
72543
- publishable: valid,
73346
+ publishable,
72544
73347
  autoPublishEligible,
73348
+ imageDiagnostics: {
73349
+ featuredImage: registeredFeaturedImage ? {
73350
+ state: "commons_hosted",
73351
+ url: registeredFeaturedImage.url,
73352
+ commonsImageId: registeredFeaturedImage.id,
73353
+ readyForPublish: true,
73354
+ submitWillAttemptAutoHost: false,
73355
+ requiredAction: null
73356
+ } : featuredImage?.url ? {
73357
+ state: "external_unhosted",
73358
+ url: normalizedFeaturedImageUrl,
73359
+ commonsImageId: null,
73360
+ readyForPublish: false,
73361
+ submitWillAttemptAutoHost: true,
73362
+ requiredAction: "Call commons_host_image, replace featuredImage.url with its returned permanent URL, and validate again."
73363
+ } : {
73364
+ state: "missing",
73365
+ url: null,
73366
+ commonsImageId: null,
73367
+ readyForPublish: false,
73368
+ submitWillAttemptAutoHost: false,
73369
+ requiredAction: "Add a featured image by calling commons_host_image, then set featuredImage.url to its returned permanent URL."
73370
+ }
73371
+ },
72545
73372
  errors,
72546
73373
  warnings,
72547
73374
  contract,
@@ -72622,7 +73449,7 @@ async function submitCommonsEntity(input, user) {
72622
73449
  const existingClaims = existing && normalized.claims !== void 0 ? await getCommonsClaimsByEntityId(existing.id) : [];
72623
73450
  const safety = evaluatePublishSafety(normalized, existing);
72624
73451
  const shouldApply = safety.safe && normalized.reviewPolicy !== "always_review";
72625
- const proposalId = `commons-proposal-${(0, import_node_crypto47.randomUUID)()}`;
73452
+ const proposalId = `commons-proposal-${(0, import_node_crypto48.randomUUID)()}`;
72626
73453
  const now = (/* @__PURE__ */ new Date()).toISOString();
72627
73454
  const entityId = existing?.id ?? normalized.entityId ?? allocateEntityId();
72628
73455
  const proposalStatus = shouldApply ? "accepted" : "pending_review";
@@ -72677,7 +73504,7 @@ async function submitCommonsEntity(input, user) {
72677
73504
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
72678
73505
  `,
72679
73506
  args: [
72680
- `commons-ledger-${(0, import_node_crypto47.randomUUID)()}`,
73507
+ `commons-ledger-${(0, import_node_crypto48.randomUUID)()}`,
72681
73508
  nextEntity.id,
72682
73509
  proposalId,
72683
73510
  user.id,
@@ -72783,7 +73610,7 @@ async function saveCommonsFilter(input, user) {
72783
73610
  sql: "SELECT id FROM commons_saved_filters WHERE user_id = ? AND name = ? LIMIT 1",
72784
73611
  args: [user.id, name]
72785
73612
  });
72786
- const id = existing.rows[0]?.id != null ? String(existing.rows[0].id) : `commons-filter-${(0, import_node_crypto47.randomUUID)()}`;
73613
+ const id = existing.rows[0]?.id != null ? String(existing.rows[0].id) : `commons-filter-${(0, import_node_crypto48.randomUUID)()}`;
72787
73614
  await getDb().execute({
72788
73615
  sql: `
72789
73616
  INSERT INTO commons_saved_filters (id, user_id, name, description, filter_json, created_at, updated_at)
@@ -73561,7 +74388,7 @@ function commonsIndexDocuments(entity, now) {
73561
74388
  return documents;
73562
74389
  }
73563
74390
  function commonsIndexDocumentId(entityId, documentType, documentKey) {
73564
- return `commons-index-${(0, import_node_crypto47.createHash)("sha256").update(`${entityId}
74391
+ return `commons-index-${(0, import_node_crypto48.createHash)("sha256").update(`${entityId}
73565
74392
  ${documentType}
73566
74393
  ${documentKey}`).digest("hex").slice(0, 32)}`;
73567
74394
  }
@@ -74127,11 +74954,11 @@ function jsonLikeValue(value) {
74127
74954
  function escapeLike(value) {
74128
74955
  return value.replace(/[%_]/g, "");
74129
74956
  }
74130
- var import_node_crypto47, COMMONS_SCHEMA_VERSION, DEFAULT_COMMONS_BASE_URL, DEFAULT_ENTITY_TYPE, COMMONS_ENTITY_PROFILES, schemaReady, COMMONS_SCHEMA_OBJECTS, SEARCH_CANDIDATE_LIMIT, CommonsRepositoryError;
74957
+ var import_node_crypto48, COMMONS_SCHEMA_VERSION, DEFAULT_COMMONS_BASE_URL, DEFAULT_ENTITY_TYPE, COMMONS_ENTITY_PROFILES, schemaReady2, COMMONS_SCHEMA_OBJECTS, SEARCH_CANDIDATE_LIMIT, CommonsRepositoryError;
74131
74958
  var init_commons_repository = __esm({
74132
74959
  "src/api/commons-repository.ts"() {
74133
74960
  "use strict";
74134
- import_node_crypto47 = require("crypto");
74961
+ import_node_crypto48 = require("crypto");
74135
74962
  init_db();
74136
74963
  init_commons_image_store();
74137
74964
  init_commons_embeddings();
@@ -74286,7 +75113,7 @@ var init_commons_repository = __esm({
74286
75113
  ]
74287
75114
  }
74288
75115
  };
74289
- schemaReady = false;
75116
+ schemaReady2 = false;
74290
75117
  COMMONS_SCHEMA_OBJECTS = [
74291
75118
  "schema_migrations",
74292
75119
  "commons_entities",
@@ -74510,7 +75337,7 @@ async function claimCommonsPublication(input, user) {
74510
75337
  }
74511
75338
  const existingName = await getCommonsPublicationBySubdomain(subdomain);
74512
75339
  if (existingName) throw new CommonsPublicationError("publication_name_unavailable", "That publication name is already claimed.", 409);
74513
- const id = `tcpub_${(0, import_node_crypto48.randomUUID)()}`;
75340
+ const id = `tcpub_${(0, import_node_crypto49.randomUUID)()}`;
74514
75341
  const now = (/* @__PURE__ */ new Date()).toISOString();
74515
75342
  try {
74516
75343
  await getDb().execute({
@@ -74562,7 +75389,7 @@ async function publishCommonsEditorial(input, user) {
74562
75389
  const canonicalUrl = editionPublicUrl(subdomain, editionSlug);
74563
75390
  const rendered = renderEditorialReadingRoom(editionInput);
74564
75391
  const html = addPublicMetadata(rendered.html, canonicalUrl, publication.title);
74565
- const editionId = `tced_${(0, import_node_crypto48.randomUUID)()}`;
75392
+ const editionId = `tced_${(0, import_node_crypto49.randomUUID)()}`;
74566
75393
  const revision = (latest?.revision ?? 0) + 1;
74567
75394
  const now = (/* @__PURE__ */ new Date()).toISOString();
74568
75395
  await getDb().batch([
@@ -74586,7 +75413,7 @@ async function publishCommonsEditorial(input, user) {
74586
75413
  JSON.stringify(editionInput.articles),
74587
75414
  html,
74588
75415
  rendered.filename,
74589
- (0, import_node_crypto48.createHash)("sha256").update(html).digest("hex"),
75416
+ (0, import_node_crypto49.createHash)("sha256").update(html).digest("hex"),
74590
75417
  rendered.articleCount,
74591
75418
  rendered.wordCount,
74592
75419
  Buffer.byteLength(html),
@@ -74644,7 +75471,7 @@ async function updateCommonsEditorialArticle(input, user) {
74644
75471
  const canonicalUrl = editionPublicUrl(subdomain, editionSlug);
74645
75472
  const rendered = renderEditorialReadingRoom(editionInput);
74646
75473
  const html = addPublicMetadata(rendered.html, canonicalUrl, publication.title);
74647
- const editionId = `tced_${(0, import_node_crypto48.randomUUID)()}`;
75474
+ const editionId = `tced_${(0, import_node_crypto49.randomUUID)()}`;
74648
75475
  const revision = latest.revision + 1;
74649
75476
  const now = (/* @__PURE__ */ new Date()).toISOString();
74650
75477
  await getDb().batch([
@@ -74668,7 +75495,7 @@ async function updateCommonsEditorialArticle(input, user) {
74668
75495
  JSON.stringify(nextArticles),
74669
75496
  html,
74670
75497
  rendered.filename,
74671
- (0, import_node_crypto48.createHash)("sha256").update(html).digest("hex"),
75498
+ (0, import_node_crypto49.createHash)("sha256").update(html).digest("hex"),
74672
75499
  rendered.articleCount,
74673
75500
  rendered.wordCount,
74674
75501
  Buffer.byteLength(html),
@@ -74873,11 +75700,11 @@ function addPublicMetadata(html, canonicalUrl, publicationTitle) {
74873
75700
  "</head>"
74874
75701
  ].join("\n"));
74875
75702
  }
74876
- var import_node_crypto48, PUBLICATION_ROOT_DOMAIN, RESERVED_SUBDOMAINS, CommonsPublicationError;
75703
+ var import_node_crypto49, PUBLICATION_ROOT_DOMAIN, RESERVED_SUBDOMAINS, CommonsPublicationError;
74877
75704
  var init_commons_publication_repository = __esm({
74878
75705
  "src/api/commons-publication-repository.ts"() {
74879
75706
  "use strict";
74880
- import_node_crypto48 = require("crypto");
75707
+ import_node_crypto49 = require("crypto");
74881
75708
  init_db();
74882
75709
  init_commons_repository();
74883
75710
  init_render();
@@ -74964,6 +75791,18 @@ function repositoryStatus(error) {
74964
75791
  if (error.httpStatus === 409) return 409;
74965
75792
  return 400;
74966
75793
  }
75794
+ function imageErrorResponse(c, error, chargeStatus) {
75795
+ return c.json({
75796
+ ok: false,
75797
+ error: error.code,
75798
+ error_code: error.code,
75799
+ error_type: "invalid_request",
75800
+ retryable: error.retryable,
75801
+ ...chargeStatus ? { charge_status: chargeStatus } : {},
75802
+ message: `${error.message} ${error.retryGuidance}`,
75803
+ details: { retry_guidance: error.retryGuidance }
75804
+ }, error.httpStatus);
75805
+ }
74967
75806
  async function publicationOperation(c, operation, successStatus = 200) {
74968
75807
  try {
74969
75808
  return c.json({ ok: true, data: await operation() }, successStatus);
@@ -75290,11 +76129,13 @@ var init_commons_routes = __esm({
75290
76129
  HostImageSchema = import_zod54.z.object({
75291
76130
  sourceUrl: import_zod54.z.string().url().optional(),
75292
76131
  imageBase64: import_zod54.z.string().min(16).optional(),
76132
+ artifactId: import_zod54.z.string().min(1).max(500).optional(),
76133
+ sourceType: import_zod54.z.enum(["uploaded", "ai_generated"]).optional(),
75293
76134
  alt: import_zod54.z.string().max(500).optional(),
75294
76135
  license: import_zod54.z.string().max(240).optional(),
75295
76136
  attribution: import_zod54.z.string().max(500).optional()
75296
- }).strict().refine((body) => Boolean(body.sourceUrl || body.imageBase64), {
75297
- message: "Provide either sourceUrl or imageBase64."
76137
+ }).strict().refine((body) => [body.sourceUrl, body.imageBase64, body.artifactId].filter(Boolean).length === 1, {
76138
+ message: "Provide exactly one of sourceUrl, imageBase64, or artifactId."
75298
76139
  });
75299
76140
  commonsApp.post("/images", auth2, async (c) => {
75300
76141
  const parsed = HostImageSchema.safeParse(await c.req.json().catch(() => ({})));
@@ -75304,7 +76145,7 @@ var init_commons_routes = __esm({
75304
76145
  return c.json({ ok: true, data: hosted }, hosted.reused ? 200 : 201);
75305
76146
  } catch (error) {
75306
76147
  if (error instanceof CommonsImageError) {
75307
- return c.json({ ok: false, error: error.code, message: error.message }, error.httpStatus);
76148
+ return imageErrorResponse(c, error);
75308
76149
  }
75309
76150
  throw error;
75310
76151
  }
@@ -75368,7 +76209,14 @@ var init_commons_routes = __esm({
75368
76209
  }
75369
76210
  return c.json({ ok: true, data: result, billing: publicListingWriteBilling(billing) }, 201);
75370
76211
  } catch (error) {
75371
- if (billing) await refundListingWrite(billing, "Transparent Commons write failed before persistence.").catch(() => null);
76212
+ let chargeStatus = billing ? billing.duplicate ? "not_charged" : "refund_pending" : "not_charged";
76213
+ if (billing && !billing.duplicate) {
76214
+ const settlement = await refundListingWrite(billing, "Transparent Commons write failed before persistence.").catch(() => null);
76215
+ if (settlement) chargeStatus = "refunded";
76216
+ }
76217
+ if (error instanceof CommonsImageError) {
76218
+ return imageErrorResponse(c, error, chargeStatus);
76219
+ }
75372
76220
  if (error instanceof CommonsRepositoryError) {
75373
76221
  return c.json({ ok: false, error: error.code, message: error.message }, repositoryStatus(error));
75374
76222
  }
@@ -76110,7 +76958,7 @@ async function migrateAnalytics() {
76110
76958
  }
76111
76959
  function normalizeSlug2(value) {
76112
76960
  const slug4 = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
76113
- return slug4 || `site-${(0, import_node_crypto49.randomBytes)(4).toString("hex")}`;
76961
+ return slug4 || `site-${(0, import_node_crypto50.randomBytes)(4).toString("hex")}`;
76114
76962
  }
76115
76963
  function normalizeObservedHostname(origin) {
76116
76964
  if (!origin || origin === "null") return null;
@@ -76123,7 +76971,7 @@ function normalizeObservedHostname(origin) {
76123
76971
  }
76124
76972
  }
76125
76973
  function publicPixelId() {
76126
- return `px_${(0, import_node_crypto49.randomBytes)(18).toString("base64url")}`;
76974
+ return `px_${(0, import_node_crypto50.randomBytes)(18).toString("base64url")}`;
76127
76975
  }
76128
76976
  function mapRows(rows) {
76129
76977
  return rows;
@@ -76155,7 +77003,7 @@ async function requireEditor(client2, siteId, userId) {
76155
77003
  async function createAnalyticsSite(input) {
76156
77004
  const db = getAnalyticsPool();
76157
77005
  const client2 = await db.connect();
76158
- const id = (0, import_node_crypto49.randomUUID)();
77006
+ const id = (0, import_node_crypto50.randomUUID)();
76159
77007
  const baseSlug = normalizeSlug2(input.slug || input.name);
76160
77008
  try {
76161
77009
  await client2.query("BEGIN");
@@ -76171,7 +77019,7 @@ async function createAnalyticsSite(input) {
76171
77019
  } catch (error) {
76172
77020
  const code = error.code;
76173
77021
  if (code !== "23505" || attempt === 2) throw error;
76174
- slug4 = `${baseSlug}-${(0, import_node_crypto49.randomBytes)(2).toString("hex")}`;
77022
+ slug4 = `${baseSlug}-${(0, import_node_crypto50.randomBytes)(2).toString("hex")}`;
76175
77023
  }
76176
77024
  }
76177
77025
  await client2.query(
@@ -76386,7 +77234,7 @@ async function createAnalyticsPixel(input) {
76386
77234
  VALUES ($1, $2, $3, $4, $5)
76387
77235
  RETURNING id, site_id, public_id, name, environment, status, created_at::text, NULL::text AS last_event_at`,
76388
77236
  [
76389
- (0, import_node_crypto49.randomUUID)(),
77237
+ (0, import_node_crypto50.randomUUID)(),
76390
77238
  input.siteId,
76391
77239
  publicPixelId(),
76392
77240
  input.name.trim(),
@@ -76463,7 +77311,7 @@ async function setAnalyticsPixelDomainState(input) {
76463
77311
  SELECT $1, p.id, $4, $5 FROM analytics_pixels p WHERE p.id = $2 AND p.site_id = $3
76464
77312
  ON CONFLICT(pixel_id, hostname) DO UPDATE SET state = EXCLUDED.state, updated_at = now()
76465
77313
  RETURNING id`,
76466
- [(0, import_node_crypto49.randomUUID)(), input.pixelId, input.siteId, hostname, input.state]
77314
+ [(0, import_node_crypto50.randomUUID)(), input.pixelId, input.siteId, hostname, input.state]
76467
77315
  );
76468
77316
  if (!result.rowCount)
76469
77317
  throw new AnalyticsRepositoryError(
@@ -76513,7 +77361,7 @@ function sanitizeAnalyticsProperties(value) {
76513
77361
  }
76514
77362
  async function ingestAnalyticsEvents(input) {
76515
77363
  const db = getAnalyticsPool();
76516
- const requestId = `air_${(0, import_node_crypto49.randomUUID)()}`;
77364
+ const requestId = `air_${(0, import_node_crypto50.randomUUID)()}`;
76517
77365
  const hostname = normalizeObservedHostname(input.origin);
76518
77366
  const pixelResult = await db.query(
76519
77367
  `SELECT p.id, p.site_id, p.status,
@@ -76534,7 +77382,7 @@ async function ingestAnalyticsEvents(input) {
76534
77382
  `INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, hostname, rejected_count, reason_codes)
76535
77383
  VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,
76536
77384
  [
76537
- (0, import_node_crypto49.randomUUID)(),
77385
+ (0, import_node_crypto50.randomUUID)(),
76538
77386
  requestId,
76539
77387
  pixel?.site_id ?? null,
76540
77388
  pixel?.id ?? null,
@@ -76559,7 +77407,7 @@ async function ingestAnalyticsEvents(input) {
76559
77407
  `INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, hostname, rejected_count, reason_codes)
76560
77408
  VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,
76561
77409
  [
76562
- (0, import_node_crypto49.randomUUID)(),
77410
+ (0, import_node_crypto50.randomUUID)(),
76563
77411
  requestId,
76564
77412
  pixel.site_id,
76565
77413
  pixel.id,
@@ -76584,7 +77432,7 @@ async function ingestAnalyticsEvents(input) {
76584
77432
  `INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, rejected_count, reason_codes)
76585
77433
  VALUES ($1, $2, $3, $4, $5, $6::jsonb)`,
76586
77434
  [
76587
- (0, import_node_crypto49.randomUUID)(),
77435
+ (0, import_node_crypto50.randomUUID)(),
76588
77436
  requestId,
76589
77437
  pixel.site_id,
76590
77438
  pixel.id,
@@ -76608,7 +77456,7 @@ async function ingestAnalyticsEvents(input) {
76608
77456
  ON CONFLICT(pixel_id, hostname) DO UPDATE
76609
77457
  SET last_seen_at = now(), updated_at = now()
76610
77458
  RETURNING state`,
76611
- [(0, import_node_crypto49.randomUUID)(), pixel.id, hostname]
77459
+ [(0, import_node_crypto50.randomUUID)(), pixel.id, hostname]
76612
77460
  );
76613
77461
  if (domainResult.rows[0]?.state !== "approved") {
76614
77462
  await db.query(
@@ -76622,7 +77470,7 @@ async function ingestAnalyticsEvents(input) {
76622
77470
  `INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, hostname, rejected_count, reason_codes)
76623
77471
  VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,
76624
77472
  [
76625
- (0, import_node_crypto49.randomUUID)(),
77473
+ (0, import_node_crypto50.randomUUID)(),
76626
77474
  requestId,
76627
77475
  pixel.site_id,
76628
77476
  pixel.id,
@@ -76672,7 +77520,7 @@ async function ingestAnalyticsEvents(input) {
76672
77520
  $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33
76673
77521
  ) ON CONFLICT(site_id, event_id) DO NOTHING`,
76674
77522
  [
76675
- (0, import_node_crypto49.randomUUID)(),
77523
+ (0, import_node_crypto50.randomUUID)(),
76676
77524
  event2.eventId,
76677
77525
  pixel.site_id,
76678
77526
  pixel.id,
@@ -76730,7 +77578,7 @@ async function ingestAnalyticsEvents(input) {
76730
77578
  id, request_id, site_id, pixel_id, hostname, accepted_count, rejected_count, reason_codes
76731
77579
  ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb)`,
76732
77580
  [
76733
- (0, import_node_crypto49.randomUUID)(),
77581
+ (0, import_node_crypto50.randomUUID)(),
76734
77582
  requestId,
76735
77583
  pixel.site_id,
76736
77584
  pixel.id,
@@ -76762,7 +77610,7 @@ function normalizeGeographyCode(value, max) {
76762
77610
  return normalized && /^[A-Z0-9-]+$/.test(normalized) ? normalized.slice(0, max) : null;
76763
77611
  }
76764
77612
  function pageFingerprint(scope, siteId, filters) {
76765
- return (0, import_node_crypto49.createHash)("sha256").update(JSON.stringify({ scope, siteId, filters })).digest("base64url").slice(0, 18);
77613
+ return (0, import_node_crypto50.createHash)("sha256").update(JSON.stringify({ scope, siteId, filters })).digest("base64url").slice(0, 18);
76766
77614
  }
76767
77615
  function decodePageOffset(cursor, fingerprint2) {
76768
77616
  if (!cursor) return 0;
@@ -76812,7 +77660,7 @@ async function createAnalyticsConversion(input) {
76812
77660
  404
76813
77661
  );
76814
77662
  }
76815
- const id = (0, import_node_crypto49.randomUUID)();
77663
+ const id = (0, import_node_crypto50.randomUUID)();
76816
77664
  const resolvedPerson = input.sessionId ? await db.query(
76817
77665
  `SELECT e.person_id FROM analytics_identity_nodes n JOIN analytics_identity_edges e ON e.identity_node_id = n.id
76818
77666
  WHERE n.site_id = $1 AND n.kind = 'session_id' AND n.value_hmac = $2 ORDER BY e.confidence DESC LIMIT 1`,
@@ -77561,7 +78409,7 @@ async function createAnalyticsCampaignLink(input) {
77561
78409
  404
77562
78410
  );
77563
78411
  }
77564
- const shortCode = (input.shortCode?.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-") || (0, import_node_crypto49.randomBytes)(5).toString("base64url").toLowerCase()).slice(0, 48);
78412
+ const shortCode = (input.shortCode?.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-") || (0, import_node_crypto50.randomBytes)(5).toString("base64url").toLowerCase()).slice(0, 48);
77565
78413
  try {
77566
78414
  const result = await db.query(
77567
78415
  `INSERT INTO analytics_campaign_links(
@@ -77570,7 +78418,7 @@ async function createAnalyticsCampaignLink(input) {
77570
78418
  ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)
77571
78419
  RETURNING *, 0::int AS click_count`,
77572
78420
  [
77573
- (0, import_node_crypto49.randomUUID)(),
78421
+ (0, import_node_crypto50.randomUUID)(),
77574
78422
  input.siteId,
77575
78423
  input.pixelId ?? null,
77576
78424
  input.name.trim(),
@@ -77641,7 +78489,7 @@ async function resolveAnalyticsCampaignLink(shortCode, referrer) {
77641
78489
  if (!row) return null;
77642
78490
  await db.query(
77643
78491
  `INSERT INTO analytics_campaign_clicks(id, link_id, referrer) VALUES ($1, $2, $3)`,
77644
- [(0, import_node_crypto49.randomUUID)(), row.id, normalizeAnalyticsUrl(referrer || void 0)]
78492
+ [(0, import_node_crypto50.randomUUID)(), row.id, normalizeAnalyticsUrl(referrer || void 0)]
77645
78493
  );
77646
78494
  return buildTaggedCampaignUrl(row);
77647
78495
  }
@@ -77659,14 +78507,14 @@ async function createAnalyticsForm(input) {
77659
78507
  404
77660
78508
  );
77661
78509
  const baseSlug = normalizeSlug2(input.name);
77662
- const slug4 = `${baseSlug}-${(0, import_node_crypto49.randomBytes)(3).toString("hex")}`;
77663
- const publicId = `form_${(0, import_node_crypto49.randomBytes)(18).toString("base64url")}`;
78510
+ const slug4 = `${baseSlug}-${(0, import_node_crypto50.randomBytes)(3).toString("hex")}`;
78511
+ const publicId = `form_${(0, import_node_crypto50.randomBytes)(18).toString("base64url")}`;
77664
78512
  const result = await db.query(
77665
78513
  `INSERT INTO analytics_forms(id, public_id, site_id, pixel_id, name, slug, fields, brand, submit_label, success_message, redirect_url, consent_text, status, created_by_user_id)
77666
78514
  VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8::jsonb,$9,$10,$11,$12,$13,$14)
77667
78515
  RETURNING *, 0::int AS submission_count`,
77668
78516
  [
77669
- (0, import_node_crypto49.randomUUID)(),
78517
+ (0, import_node_crypto50.randomUUID)(),
77670
78518
  publicId,
77671
78519
  input.siteId,
77672
78520
  input.pixelId,
@@ -77711,7 +78559,7 @@ async function getPublicAnalyticsForm(publicId) {
77711
78559
  return result.rows[0] ?? null;
77712
78560
  }
77713
78561
  async function recordAnalyticsFormSubmission(input) {
77714
- const id = (0, import_node_crypto49.randomUUID)();
78562
+ const id = (0, import_node_crypto50.randomUUID)();
77715
78563
  await getAnalyticsPool().query(
77716
78564
  `INSERT INTO analytics_form_submissions(id, form_id, site_id, pixel_id, visitor_id, session_id, crm_person_ref, source, medium, campaign, crm_delivery_status, click_ids)
77717
78565
  VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12::jsonb)`,
@@ -77742,7 +78590,7 @@ function sanitizeClickIds(value) {
77742
78590
  return output;
77743
78591
  }
77744
78592
  function identityHmac(kind, value) {
77745
- return (0, import_node_crypto49.createHmac)("sha256", getSessionSecret()).update(`${kind}:${value.trim().toLowerCase()}`).digest("hex");
78593
+ return (0, import_node_crypto50.createHmac)("sha256", getSessionSecret()).update(`${kind}:${value.trim().toLowerCase()}`).digest("hex");
77746
78594
  }
77747
78595
  async function linkAnalyticsFormIdentity(input) {
77748
78596
  const client2 = await getAnalyticsPool().connect();
@@ -77751,7 +78599,7 @@ async function linkAnalyticsFormIdentity(input) {
77751
78599
  const person = await client2.query(
77752
78600
  `INSERT INTO analytics_people(id, site_id, crm_person_ref) VALUES ($1,$2,$3)
77753
78601
  ON CONFLICT(site_id, crm_person_ref) DO UPDATE SET last_seen_at = now() RETURNING id`,
77754
- [(0, import_node_crypto49.randomUUID)(), input.siteId, input.crmPersonRef]
78602
+ [(0, import_node_crypto50.randomUUID)(), input.siteId, input.crmPersonRef]
77755
78603
  );
77756
78604
  const personId = person.rows[0].id;
77757
78605
  const signals = [];
@@ -77798,7 +78646,7 @@ async function linkAnalyticsFormIdentity(input) {
77798
78646
  `INSERT INTO analytics_identity_nodes(id, site_id, kind, value_hmac) VALUES ($1,$2,$3,$4)
77799
78647
  ON CONFLICT(site_id, kind, value_hmac) DO UPDATE SET last_seen_at = now() RETURNING id`,
77800
78648
  [
77801
- (0, import_node_crypto49.randomUUID)(),
78649
+ (0, import_node_crypto50.randomUUID)(),
77802
78650
  input.siteId,
77803
78651
  signal.kind,
77804
78652
  identityHmac(signal.kind, signal.value)
@@ -77809,7 +78657,7 @@ async function linkAnalyticsFormIdentity(input) {
77809
78657
  VALUES ($1,$2,$3,$4,$5,$6)
77810
78658
  ON CONFLICT(person_id, identity_node_id, evidence_kind) DO UPDATE SET last_seen_at = now(), confidence = greatest(analytics_identity_edges.confidence, EXCLUDED.confidence)`,
77811
78659
  [
77812
- (0, import_node_crypto49.randomUUID)(),
78660
+ (0, import_node_crypto50.randomUUID)(),
77813
78661
  input.siteId,
77814
78662
  personId,
77815
78663
  node.rows[0].id,
@@ -77907,7 +78755,7 @@ async function createAnalyticsCrmImport(input) {
77907
78755
  const db = getAnalyticsPool();
77908
78756
  await requireEditor(db, input.siteId, input.userId);
77909
78757
  const client2 = await db.connect();
77910
- const id = (0, import_node_crypto49.randomUUID)();
78758
+ const id = (0, import_node_crypto50.randomUUID)();
77911
78759
  try {
77912
78760
  await client2.query("BEGIN");
77913
78761
  await client2.query(
@@ -77927,7 +78775,7 @@ async function createAnalyticsCrmImport(input) {
77927
78775
  await client2.query(
77928
78776
  `INSERT INTO analytics_crm_import_rows(id, import_id, crm_person_ref, payload_ciphertext)
77929
78777
  VALUES ($1,$2,$3,$4) ON CONFLICT(import_id, crm_person_ref) DO NOTHING`,
77930
- [(0, import_node_crypto49.randomUUID)(), id, row.crmPersonRef, row.payloadCiphertext]
78778
+ [(0, import_node_crypto50.randomUUID)(), id, row.crmPersonRef, row.payloadCiphertext]
77931
78779
  );
77932
78780
  }
77933
78781
  await client2.query("COMMIT");
@@ -77966,7 +78814,7 @@ async function createAnalyticsActivationDestination(input) {
77966
78814
  `INSERT INTO analytics_activation_destinations(id, site_id, platform, name, connection_ref, external_dataset_id, event_mapping, created_by_user_id)
77967
78815
  VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8) RETURNING *`,
77968
78816
  [
77969
- (0, import_node_crypto49.randomUUID)(),
78817
+ (0, import_node_crypto50.randomUUID)(),
77970
78818
  input.siteId,
77971
78819
  input.platform,
77972
78820
  input.name.trim(),
@@ -78018,7 +78866,7 @@ async function queueAnalyticsActivation(input) {
78018
78866
  `INSERT INTO analytics_activation_jobs(id, destination_id, conversion_id, person_id, payload_ciphertext)
78019
78867
  VALUES ($1,$2,$3,$4,$5) ON CONFLICT(destination_id, conversion_id) DO NOTHING`,
78020
78868
  [
78021
- (0, import_node_crypto49.randomUUID)(),
78869
+ (0, import_node_crypto50.randomUUID)(),
78022
78870
  destination.id,
78023
78871
  input.conversionId,
78024
78872
  input.personId ?? null,
@@ -78030,7 +78878,7 @@ async function queueAnalyticsActivation(input) {
78030
78878
  return queued;
78031
78879
  }
78032
78880
  async function queueAnalyticsFormDelivery(input) {
78033
- const id = (0, import_node_crypto49.randomUUID)();
78881
+ const id = (0, import_node_crypto50.randomUUID)();
78034
78882
  await getAnalyticsPool().query(
78035
78883
  `INSERT INTO analytics_form_delivery_jobs(id, submission_id, owner_user_id, payload_ciphertext, last_error_code)
78036
78884
  VALUES ($1,$2,$3,$4,$5)`,
@@ -78157,7 +79005,7 @@ async function analyticsHealth(siteId, userId) {
78157
79005
  }
78158
79006
  async function refreshAnalyticsDailyRollups(input) {
78159
79007
  const db = getAnalyticsPool();
78160
- const runId = (0, import_node_crypto49.randomUUID)();
79008
+ const runId = (0, import_node_crypto50.randomUUID)();
78161
79009
  await db.query(
78162
79010
  `INSERT INTO analytics_rollup_runs(id, window_start, window_end, status) VALUES ($1, $2, $3, 'running')`,
78163
79011
  [runId, input.start, input.end]
@@ -78354,7 +79202,7 @@ async function createAnalyticsExport(input) {
78354
79202
  `Generated ${(/* @__PURE__ */ new Date()).toISOString()} from the governed ${input.report} report contract.`
78355
79203
  ].join("\n");
78356
79204
  }
78357
- const id = (0, import_node_crypto49.randomUUID)();
79205
+ const id = (0, import_node_crypto50.randomUUID)();
78358
79206
  const inserted = await getAnalyticsPool().query(
78359
79207
  `INSERT INTO analytics_exports(id, site_id, requested_by_user_id, idempotency_key, report, format, filters, content)
78360
79208
  VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8)
@@ -78387,11 +79235,11 @@ async function createAnalyticsExport(input) {
78387
79235
  }
78388
79236
  return describeArtifact(replay.rows[0]);
78389
79237
  }
78390
- var import_node_crypto49, import_pg, AnalyticsRepositoryError, pool2, MAX_ENGAGED_MS, ENGAGED_SESSION_MS, blockedPropertyName, inferredFamilySql, ANALYTICS_CONTENT_SORTS, clickIdKeys;
79238
+ var import_node_crypto50, import_pg, AnalyticsRepositoryError, pool2, MAX_ENGAGED_MS, ENGAGED_SESSION_MS, blockedPropertyName, inferredFamilySql, ANALYTICS_CONTENT_SORTS, clickIdKeys;
78391
79239
  var init_analytics_repository = __esm({
78392
79240
  "src/api/analytics-repository.ts"() {
78393
79241
  "use strict";
78394
- import_node_crypto49 = require("crypto");
79242
+ import_node_crypto50 = require("crypto");
78395
79243
  import_pg = require("pg");
78396
79244
  init_session();
78397
79245
  init_analytics_attribution();
@@ -78630,8 +79478,8 @@ function dashboardCallbackUrl() {
78630
79478
  }
78631
79479
  async function createThorbitConnectUrl(userId) {
78632
79480
  if (!bridgeConfigured()) throw new Error("Thorbit X-Ray account bridge is not configured");
78633
- const state = (0, import_node_crypto50.randomBytes)(32).toString("base64url");
78634
- const stateHash = (0, import_node_crypto50.createHash)("sha256").update(state).digest("hex");
79481
+ const state = (0, import_node_crypto51.randomBytes)(32).toString("base64url");
79482
+ const stateHash = (0, import_node_crypto51.createHash)("sha256").update(state).digest("hex");
78635
79483
  await getAnalyticsPool().query(
78636
79484
  `INSERT INTO analytics_thorbit_connect_states(state_hash,user_id,expires_at)
78637
79485
  VALUES ($1,$2,now()+interval '10 minutes')
@@ -78646,9 +79494,9 @@ async function createThorbitConnectUrl(userId) {
78646
79494
  function verifyThorbitAssertion(token6) {
78647
79495
  const [encoded, signature] = token6.split(".");
78648
79496
  if (!encoded || !signature || !bridgeConfigured()) throw new Error("Invalid Thorbit assertion");
78649
- const expected = (0, import_node_crypto50.createHmac)("sha256", bridgeSecret()).update(encoded).digest();
79497
+ const expected = (0, import_node_crypto51.createHmac)("sha256", bridgeSecret()).update(encoded).digest();
78650
79498
  const actual = Buffer.from(signature, "base64url");
78651
- if (actual.length !== expected.length || !(0, import_node_crypto50.timingSafeEqual)(actual, expected)) {
79499
+ if (actual.length !== expected.length || !(0, import_node_crypto51.timingSafeEqual)(actual, expected)) {
78652
79500
  throw new Error("Invalid Thorbit assertion signature");
78653
79501
  }
78654
79502
  const parsed = AssertionSchema.safeParse(
@@ -78660,7 +79508,7 @@ function verifyThorbitAssertion(token6) {
78660
79508
  return parsed.data.entitlement;
78661
79509
  }
78662
79510
  async function consumeThorbitConnectCallback(input) {
78663
- const stateHash = (0, import_node_crypto50.createHash)("sha256").update(input.state).digest("hex");
79511
+ const stateHash = (0, import_node_crypto51.createHash)("sha256").update(input.state).digest("hex");
78664
79512
  const client2 = await getAnalyticsPool().connect();
78665
79513
  try {
78666
79514
  await client2.query("BEGIN");
@@ -78700,11 +79548,11 @@ async function disconnectAnalyticsEntitlement(userId) {
78700
79548
  [userId]
78701
79549
  );
78702
79550
  }
78703
- var import_node_crypto50, import_zod55, THORBIT_PRODUCT_URL, REFRESH_INTERVAL_MS, ELIGIBLE_GRACE_MS, TRIAL_LENGTH_MS, ThorbitEntitlementSchema, AssertionSchema;
79551
+ var import_node_crypto51, import_zod55, THORBIT_PRODUCT_URL, REFRESH_INTERVAL_MS, ELIGIBLE_GRACE_MS, TRIAL_LENGTH_MS, ThorbitEntitlementSchema, AssertionSchema;
78704
79552
  var init_analytics_entitlement = __esm({
78705
79553
  "src/api/analytics-entitlement.ts"() {
78706
79554
  "use strict";
78707
- import_node_crypto50 = require("crypto");
79555
+ import_node_crypto51 = require("crypto");
78708
79556
  import_zod55 = require("zod");
78709
79557
  init_analytics_repository();
78710
79558
  THORBIT_PRODUCT_URL = "https://thorbit.ai";
@@ -78844,14 +79692,14 @@ function renderPublicForm(form, placementUrl) {
78844
79692
  const brand = form.brand;
78845
79693
  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:${escapeHtml8(brand.textColor)};background:${escapeHtml8(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:${escapeHtml8(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">${escapeHtml8(form.consent_text)}</p>` : ""}<button>${escapeHtml8(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/${escapeHtml8(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>`;
78846
79694
  }
78847
- var import_hono34, import_factory6, import_zod56, import_node_crypto51, import_papaparse6, analyticsApp, auth3, entitlementGuard, ThorbitApiKeySchema, ThorbitCallbackSchema, SiteInputSchema, PixelInputSchema, PixelUpdateSchema, EventSchema, IngestionSchema, ConversionSchema, ExportSchema2, CampaignLinkSchema, FormFieldSchema, FormInputSchema, FormSubmissionSchema, CrmImportSchema, ActivationDestinationSchema, BusinessModelSchema, AdSpendSchema;
79695
+ var import_hono34, import_factory6, import_zod56, import_node_crypto52, import_papaparse6, analyticsApp, auth3, entitlementGuard, ThorbitApiKeySchema, ThorbitCallbackSchema, SiteInputSchema, PixelInputSchema, PixelUpdateSchema, EventSchema, IngestionSchema, ConversionSchema, ExportSchema2, CampaignLinkSchema, FormFieldSchema, FormInputSchema, FormSubmissionSchema, CrmImportSchema, ActivationDestinationSchema, BusinessModelSchema, AdSpendSchema;
78848
79696
  var init_analytics_routes = __esm({
78849
79697
  "src/api/analytics-routes.ts"() {
78850
79698
  "use strict";
78851
79699
  import_hono34 = require("hono");
78852
79700
  import_factory6 = require("hono/factory");
78853
79701
  import_zod56 = require("zod");
78854
- import_node_crypto51 = require("crypto");
79702
+ import_node_crypto52 = require("crypto");
78855
79703
  import_papaparse6 = __toESM(require("papaparse"), 1);
78856
79704
  init_api_auth();
78857
79705
  init_db();
@@ -79182,7 +80030,7 @@ var init_analytics_routes = __esm({
79182
80030
  const lastName = typeof data.last_name === "string" ? data.last_name.trim() : "";
79183
80031
  const fullName = [firstName, lastName].filter(Boolean).join(" ") || (typeof data.name === "string" ? data.name.trim() : "") || email || "Website lead";
79184
80032
  const identitySeed = email || `${fullName}:${Date.now()}`;
79185
- const suffix2 = (0, import_node_crypto51.createHash)("sha256").update(identitySeed).digest("hex").slice(0, 12);
80033
+ const suffix2 = (0, import_node_crypto52.createHash)("sha256").update(identitySeed).digest("hex").slice(0, 12);
79186
80034
  const path6 = `Leads/person-${suffix2}`;
79187
80035
  const { key: memoryKey, error: memoryKeyError } = await getOrCreateUserMemoryKey(user);
79188
80036
  const existing = memoryKey ? await memoryCall("getTool", { vault: "People", path: path6 }, memoryKey) : { ok: false, error: memoryKeyError || "memory_credential_unavailable" };
@@ -79281,9 +80129,9 @@ Submitted the published form.
79281
80129
  occurredAt: (/* @__PURE__ */ new Date()).toISOString()
79282
80130
  });
79283
80131
  const match = {
79284
- ...email ? { emailSha256: (0, import_node_crypto51.createHash)("sha256").update(email).digest("hex") } : {},
80132
+ ...email ? { emailSha256: (0, import_node_crypto52.createHash)("sha256").update(email).digest("hex") } : {},
79285
80133
  ...phone ? {
79286
- phoneSha256: (0, import_node_crypto51.createHash)("sha256").update(phone.replace(/\D/g, "")).digest("hex")
80134
+ phoneSha256: (0, import_node_crypto52.createHash)("sha256").update(phone.replace(/\D/g, "")).digest("hex")
79287
80135
  } : {}
79288
80136
  };
79289
80137
  const activationQueued = await queueAnalyticsActivation({
@@ -79711,7 +80559,7 @@ Submitted the published form.
79711
80559
  rejectedCount += 1;
79712
80560
  continue;
79713
80561
  }
79714
- const digest2 = (0, import_node_crypto51.createHash)("sha256").update(
80562
+ const digest2 = (0, import_node_crypto52.createHash)("sha256").update(
79715
80563
  `${parsed.data.sourceSystem}:${externalId || email || phone || `${fullName}:${index}`}`
79716
80564
  ).digest("hex").slice(0, 16);
79717
80565
  const path6 = `Leads/person-${digest2}`;
@@ -80169,19 +81017,19 @@ var init_analytics_delivery = __esm({
80169
81017
 
80170
81018
  // src/api/scheduled-artifact-owner.ts
80171
81019
  function scheduledArtifactOwnerIdForApiKey(apiKey) {
80172
- return (0, import_node_crypto52.createHash)("sha256").update(apiKey).digest("hex").slice(0, 24);
81020
+ return (0, import_node_crypto53.createHash)("sha256").update(apiKey).digest("hex").slice(0, 24);
80173
81021
  }
80174
- var import_node_crypto52;
81022
+ var import_node_crypto53;
80175
81023
  var init_scheduled_artifact_owner = __esm({
80176
81024
  "src/api/scheduled-artifact-owner.ts"() {
80177
81025
  "use strict";
80178
- import_node_crypto52 = require("crypto");
81026
+ import_node_crypto53 = require("crypto");
80179
81027
  }
80180
81028
  });
80181
81029
 
80182
81030
  // src/api/scheduled-run-view-links.ts
80183
81031
  async function ensureScheduledRunViewLinksSchema() {
80184
- if (schemaReady2) return;
81032
+ if (schemaReady3) return;
80185
81033
  const db = getDb();
80186
81034
  await db.execute(`
80187
81035
  CREATE TABLE IF NOT EXISTS scheduled_run_view_links (
@@ -80229,10 +81077,10 @@ async function ensureScheduledRunViewLinksSchema() {
80229
81077
  CREATE INDEX IF NOT EXISTS scheduled_run_view_links_expiry
80230
81078
  ON scheduled_run_view_links(expires_at, revoked_at)
80231
81079
  `);
80232
- schemaReady2 = true;
81080
+ schemaReady3 = true;
80233
81081
  }
80234
81082
  function tokenHash2(token6) {
80235
- return (0, import_node_crypto53.createHash)("sha256").update(token6).digest("hex");
81083
+ return (0, import_node_crypto54.createHash)("sha256").update(token6).digest("hex");
80236
81084
  }
80237
81085
  function mapRow(row) {
80238
81086
  return {
@@ -80252,9 +81100,9 @@ function mapRow(row) {
80252
81100
  async function createScheduledRunViewLink(input) {
80253
81101
  await ensureScheduledRunViewLinksSchema();
80254
81102
  const now = input.now ?? /* @__PURE__ */ new Date();
80255
- const token6 = (0, import_node_crypto53.randomBytes)(32).toString("base64url");
81103
+ const token6 = (0, import_node_crypto54.randomBytes)(32).toString("base64url");
80256
81104
  const record = {
80257
- shareId: (0, import_node_crypto53.randomUUID)(),
81105
+ shareId: (0, import_node_crypto54.randomUUID)(),
80258
81106
  ownerId: input.ownerId,
80259
81107
  runId: input.runId,
80260
81108
  artifactId: input.artifactId,
@@ -80317,13 +81165,13 @@ async function revokeScheduledRunViewLink(ownerId2, runId, shareId, now = /* @__
80317
81165
  });
80318
81166
  return result.rowsAffected > 0;
80319
81167
  }
80320
- var import_node_crypto53, schemaReady2;
81168
+ var import_node_crypto54, schemaReady3;
80321
81169
  var init_scheduled_run_view_links = __esm({
80322
81170
  "src/api/scheduled-run-view-links.ts"() {
80323
81171
  "use strict";
80324
- import_node_crypto53 = require("crypto");
81172
+ import_node_crypto54 = require("crypto");
80325
81173
  init_db();
80326
- schemaReady2 = false;
81174
+ schemaReady3 = false;
80327
81175
  }
80328
81176
  });
80329
81177
 
@@ -80387,15 +81235,15 @@ ${section(flags.finalCta && Boolean(finalCta), `<section class="section cta" id=
80387
81235
  html,
80388
81236
  filename: `${input.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "personal-authority"}.html`,
80389
81237
  bytes,
80390
- sha256: (0, import_node_crypto54.createHash)("sha256").update(html).digest("hex"),
81238
+ sha256: (0, import_node_crypto55.createHash)("sha256").update(html).digest("hex"),
80391
81239
  generatedAt: generatedAtIso
80392
81240
  };
80393
81241
  }
80394
- var import_node_crypto54;
81242
+ var import_node_crypto55;
80395
81243
  var init_render2 = __esm({
80396
81244
  "src/personal-authority/render.ts"() {
80397
81245
  "use strict";
80398
- import_node_crypto54 = require("crypto");
81246
+ import_node_crypto55 = require("crypto");
80399
81247
  }
80400
81248
  });
80401
81249
 
@@ -80498,15 +81346,15 @@ ${section2(flags.finalCta && Boolean(finalCta), `<section class="section cta" id
80498
81346
  html,
80499
81347
  filename: `${input.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "personal-authority"}.html`,
80500
81348
  bytes,
80501
- sha256: (0, import_node_crypto55.createHash)("sha256").update(html).digest("hex"),
81349
+ sha256: (0, import_node_crypto56.createHash)("sha256").update(html).digest("hex"),
80502
81350
  generatedAt: generatedAtIso
80503
81351
  };
80504
81352
  }
80505
- var import_node_crypto55, FONT_STACKS;
81353
+ var import_node_crypto56, FONT_STACKS;
80506
81354
  var init_render_v2 = __esm({
80507
81355
  "src/personal-authority/render-v2.ts"() {
80508
81356
  "use strict";
80509
- import_node_crypto55 = require("crypto");
81357
+ import_node_crypto56 = require("crypto");
80510
81358
  init_contracts();
80511
81359
  FONT_STACKS = Object.freeze({
80512
81360
  "editorial-serif": "Iowan Old Style, Baskerville, Times New Roman, serif",
@@ -80622,15 +81470,15 @@ body{overflow-x:hidden}.lead-story__body,.lead-support__item,.story-card__body{m
80622
81470
  @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}}
80623
81471
  </style></head><body data-theme="${escapeHtml11(config.theme)}" id="top" data-scheduled-renderer="newsroom_publisher_v1">${ticker}${masthead}${navigation}<main>${leadGrid}${latest}${sections}${newsletter}${pressRoom}${trustCenter}</main>${trustFooter}</body></html>`;
80624
81472
  const bytes = Buffer.byteLength(html);
80625
- const sha2565 = (0, import_node_crypto56.createHash)("sha256").update(html).digest("hex");
81473
+ const sha2565 = (0, import_node_crypto57.createHash)("sha256").update(html).digest("hex");
80626
81474
  const filename2 = `${brand.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "newsroom"}-news-site.html`;
80627
81475
  return { html, filename: filename2, bytes, sha256: sha2565, generatedAt: generatedAtIso };
80628
81476
  }
80629
- var import_node_crypto56;
81477
+ var import_node_crypto57;
80630
81478
  var init_render3 = __esm({
80631
81479
  "src/newsroom-publisher/render.ts"() {
80632
81480
  "use strict";
80633
- import_node_crypto56 = require("crypto");
81481
+ import_node_crypto57 = require("crypto");
80634
81482
  }
80635
81483
  });
80636
81484
 
@@ -80722,7 +81570,7 @@ function renderBlogArticleV1(input, config, generatedAt) {
80722
81570
  :root{--ink:#10213d;--ink2:#1c355a;--blue:#2563eb;--blue2:#1749b6;--mint:#45d5aa;--paper:#f7f5f0;--line:#dce4ef;--card:#fff;--body:#475569;--serif:Iowan Old Style,Palatino Linotype,Palatino,Georgia,serif;--sans:Inter,Avenir Next,ui-sans-serif,system-ui,sans-serif}body[data-theme="slate"]{--ink:#1e293b;--ink2:#334155;--blue:#475569;--blue2:#1e293b;--mint:#94a3b8;--paper:#f8fafc}body[data-theme="forest"]{--ink:#12352e;--ink2:#23584c;--blue:#147d64;--blue2:#0c5b48;--mint:#79d8bd;--paper:#f4f8f3}*{box-sizing:border-box}html{scroll-behavior:smooth;background:var(--paper)}body{margin:0;color:#252a33;background:var(--paper);font-family:var(--sans);line-height:1.6}a{color:inherit}button{font:inherit}button:focus-visible,a:focus-visible{outline:3px solid var(--mint);outline-offset:3px}.progress{position:fixed;z-index:50;top:0;left:0;height:4px;width:0;background:var(--mint)}.hero{position:relative;overflow:hidden;color:#fff;background:var(--ink)}.hero:after{position:absolute;right:-110px;top:-150px;width:420px;height:420px;border:1px solid #ffffff1a;border-radius:50%;content:""}.hero-inner{position:relative;z-index:1;max-width:1240px;margin:auto;padding:58px 42px 126px}.breadcrumb{display:flex;gap:8px;margin-bottom:34px;color:#dbeafecc;font-size:14px}.eyebrow{display:block;color:var(--blue);font-size:12px;font-weight:800;letter-spacing:.16em;text-transform:uppercase}.hero .eyebrow{color:var(--mint)}h1,h2,h3,p{margin-top:0}.hero h1{max-width:1000px;margin:14px 0 24px;font:700 clamp(44px,6vw,78px)/.98 var(--serif);letter-spacing:-.035em}.hero .dek{max-width:780px;color:#dbeafe;font-size:20px}.contributors{margin-top:30px;color:#eff6ff;font-size:14px}.contributors a{font-weight:800}.meta{display:flex;flex-wrap:wrap;gap:12px;margin-top:6px;color:#dbeafecc}.layout{position:relative;z-index:2;display:grid;grid-template-columns:minmax(0,760px) 290px;gap:64px;max-width:1240px;margin:-78px auto 0;padding:0 42px 80px}.main{min-width:0}.takeaways{padding:38px 44px;border-radius:24px;background:#fff;box-shadow:0 24px 70px #10213d24}.takeaways h2{margin:5px 0 20px;font:700 32px/1.1 var(--serif);color:var(--ink)}.takeaways ul{display:grid;gap:16px;margin:0;padding:0;list-style:none}.takeaways li{position:relative;padding-left:26px;color:var(--body);font-size:17px}.takeaways li:before{position:absolute;left:2px;top:.7em;width:8px;height:8px;border-radius:50%;background:var(--blue);content:""}.intro{padding:40px 0 4px}.intro p,.article-section>p{margin-bottom:22px;color:var(--body);font:400 18px/1.75 var(--sans)}.article-section{scroll-margin-top:28px;padding-top:45px}.article-section h2,.faq h2{margin:6px 0 20px;color:var(--ink);font:700 38px/1.08 var(--serif);letter-spacing:-.02em}.article-section ul{display:grid;gap:10px;margin:22px 0;padding-left:24px;color:var(--body);font-size:17px}.article-section li::marker{color:var(--blue)}.callout{margin:34px 0;padding:24px 28px;border-left:4px solid var(--blue);border-radius:0 16px 16px 0;background:#fff}.callout span{color:var(--blue);font-size:11px;font-weight:800;letter-spacing:.13em;text-transform:uppercase}.callout h3{margin:5px 0 8px;color:var(--ink);font:700 24px/1.15 var(--serif)}.callout p{margin:0;color:var(--body)}.rail{padding-top:0}.sidebar-media{overflow:hidden;margin:0 0 18px;border:1px solid #dbeafe;border-radius:16px;background:#fff}.sidebar-media img,.sidebar-media svg{display:block;width:100%;aspect-ratio:16/9;object-fit:cover}.sidebar-media figcaption{padding:10px 13px;color:#64748b;font-size:11px}.disclosure{margin-bottom:28px;border-top:1px solid #dbeafe;border-bottom:1px solid #dbeafe}.disclosure button{display:flex;width:100%;align-items:center;justify-content:space-between;gap:14px;padding:15px 0;border:0;color:#64748b;background:transparent;text-align:left;font-size:12px;cursor:pointer}.disclosure button b{color:var(--blue);font-size:18px;transition:transform .2s}.disclosure button[aria-expanded="true"] b{transform:rotate(180deg)}.disclosure p{padding:0 0 16px;margin:0;color:#64748b;font-size:12px}.toc{position:sticky;top:30px;padding:22px;border:1px solid var(--line);border-radius:16px;background:#fff;box-shadow:0 2px 5px #10213d12}.toc header{display:flex;align-items:center;justify-content:space-between}.toc header strong{color:var(--ink);font:700 21px/1 var(--serif)}.toc header button{width:34px;height:34px;border:0;border-radius:50%;color:var(--blue);background:transparent;cursor:pointer}.toc nav{display:grid;margin-top:14px}.toc nav a{display:flex;align-items:flex-start;gap:11px;padding:8px 0;color:#64748b;text-decoration:none;font-size:13px;line-height:1.35}.toc nav i{width:8px;height:8px;margin-top:5px;border:2px solid #bfdbfe;border-radius:50%}.toc nav a[aria-current="location"]{color:var(--ink);font-weight:800}.toc nav a[aria-current="location"] i{border-color:var(--blue);background:var(--blue)}.back-top{width:100%;margin-top:18px;padding:16px 0 0;border:0;border-top:1px solid var(--line);color:var(--blue);background:transparent;text-align:right;font-size:13px;font-weight:800;cursor:pointer}.faq{scroll-margin-top:28px;padding-top:64px}.faq>div{border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.faq article+article{border-top:1px solid var(--line)}.faq h3{margin:0}.faq h3 button{display:flex;width:100%;align-items:center;justify-content:space-between;gap:18px;padding:19px 0;border:0;color:var(--ink);background:transparent;text-align:left;font-weight:800;cursor:pointer}.faq h3 b{color:var(--blue);font-size:22px}.faq article>p{padding:0 0 20px;margin:0;color:var(--body);font-size:16px}.author-card{margin-top:64px;padding:32px 36px;border-radius:18px;background:#fff}.article-actions{display:flex;flex-wrap:wrap;justify-content:space-between;gap:14px}.article-actions button{display:inline-flex;min-height:46px;align-items:center;justify-content:center;gap:8px;padding:8px 18px;border:2px solid var(--blue);border-radius:999px;color:var(--ink);background:#fff;font-weight:800;cursor:pointer}.article-actions button:hover{color:#fff;background:var(--blue)}.action-status{min-height:20px;margin:8px 0 0;color:var(--blue);text-align:right;font-size:13px;font-weight:700}.author-grid{display:grid;grid-template-columns:auto 1fr auto;gap:22px;align-items:center;margin-top:24px}.author-portrait{width:88px;height:88px;border-radius:50%;object-fit:cover}.author-initials{display:grid;place-items:center;color:var(--ink);background:#fecdd3;font-size:22px;font-weight:900}.author-grid span{color:#64748b;font-size:13px}.author-grid h2{margin:2px 0;color:var(--ink);font:700 27px/1 var(--serif)}.author-grid h2 a{text-decoration:none}.author-grid p{margin:8px 0;color:#64748b;font-size:14px}.author-grid nav{display:flex;flex-wrap:wrap;gap:14px}.author-grid nav a{color:#64748b;font-size:12px}.author-more{display:inline-flex;min-height:48px;align-items:center;justify-content:center;padding:10px 24px;border:2px solid var(--blue);color:var(--blue);text-align:center;text-decoration:none;font-size:13px;font-weight:800}.author-bio{margin:24px 0 0!important;padding-top:20px;border-top:1px solid var(--line);color:var(--body);font-size:16px}dialog{width:min(720px,calc(100% - 30px));max-height:calc(100dvh - 30px);padding:0;border:0;border-radius:18px;color:var(--ink);background:#fff;box-shadow:0 24px 70px #10213d38}dialog::backdrop{background:#0f172a7a;backdrop-filter:blur(2px)}dialog>div{position:relative;padding:30px}dialog h2{margin:0 50px 2px 0;font:700 30px/1.1 var(--serif)}dialog>div>p{color:#64748b}.dialog-close{position:absolute;top:13px;right:13px;width:44px;height:44px;border:0;border-radius:50%;color:var(--blue);background:transparent;font-size:29px;cursor:pointer}dialog section{border-top:1px solid var(--line)}dialog section button{display:block;width:100%;padding:17px 5px;border:0;border-bottom:1px solid var(--line);color:var(--body);background:#fff;text-align:left;line-height:1.65;cursor:pointer}.citation-status{min-height:22px;margin:14px 0 0!important;color:var(--blue)!important;font-size:13px;font-weight:800}.generated{max-width:1156px;margin:-52px auto 50px;padding:0 42px;color:#94a3b8;font-size:11px}@media(max-width:900px){.layout{display:block}.rail{display:none}.hero-inner{padding-bottom:116px}}@media(max-width:620px){.hero-inner{padding:36px 20px 104px}.hero h1{font-size:46px}.hero .dek{font-size:17px}.layout{margin-top:-70px;padding:0 16px 54px}.takeaways{padding:28px 24px}.article-section h2,.faq h2{font-size:32px}.author-card{padding:24px}.article-actions{display:grid;justify-content:start}.author-grid{grid-template-columns:1fr;gap:12px}.author-more{width:100%}dialog>div{padding:24px}.generated{padding:0 20px}}
80723
81571
  </style></head><body data-theme="${escapeHtml12(config.theme)}" data-scheduled-renderer="blog_article_v1"><div class="progress" aria-hidden="true"></div><header class="hero"><div class="hero-inner"><nav class="breadcrumb" aria-label="Breadcrumb"><a href="${safeHref4(input.canonicalUrl)}">${escapeHtml12(input.category)}</a><span>/</span><span>Article</span></nav><span class="eyebrow">${escapeHtml12(input.eyebrow)}</span><h1>${escapeHtml12(input.title)}</h1>${input.dek ? `<p class="dek">${escapeHtml12(input.dek)}</p>` : ""}<div class="contributors">${contributorText}<div class="meta"><time datetime="${escapeHtml12(articleDate)}">Updated ${formatDate2(articleDate)}</time><span>\u2022</span><span>${input.readTimeMinutes} min read</span>${input.reviewer ? "<span>\u2022</span><strong>Expert reviewed</strong>" : ""}</div></div></div></header><main class="layout"><article class="main"><section class="takeaways"><span class="eyebrow">The short answer</span><h2>Key takeaways</h2><ul>${input.keyTakeaways.map((item) => `<li>${escapeHtml12(item)}</li>`).join("")}</ul></section><section class="intro">${input.introduction.map((paragraph) => `<p>${escapeHtml12(paragraph)}</p>`).join("")}</section>${sections}${faq}${author}</article><div class="rail">${media}${disclosure}${toc}</div></main>${config.showGeneratedAt ? `<p class="generated">Rendered ${formatDate2(generatedAtIso)} by MCP Scraper.</p>` : ""}${citationDialog}<script>(()=>{const citations=${jsonForScript(citationValues)};const canonical=${jsonForScript(input.canonicalUrl)};const title=${jsonForScript(input.title)};const progress=document.querySelector('.progress');const tocLinks=[...document.querySelectorAll('.toc nav a')];const targets=tocLinks.map(link=>document.querySelector(link.hash)).filter(Boolean);const update=()=>{const max=document.documentElement.scrollHeight-innerHeight;if(progress)progress.style.width=(max>0?scrollY/max*100:0)+'%';let active=targets[0]?.id;for(const target of targets){if(target.getBoundingClientRect().top<=innerHeight*.55)active=target.id}tocLinks.forEach(link=>link.toggleAttribute('aria-current',link.hash==='#'+active));tocLinks.forEach(link=>{if(link.hash==='#'+active)link.setAttribute('aria-current','location');else link.removeAttribute('aria-current')})};addEventListener('scroll',update,{passive:true});addEventListener('resize',update);update();document.querySelectorAll('.faq h3 button').forEach(button=>button.addEventListener('click',()=>{const panel=document.getElementById(button.getAttribute('aria-controls'));const open=button.getAttribute('aria-expanded')==='true';button.setAttribute('aria-expanded',String(!open));panel.hidden=open;button.querySelector('b').textContent=open?'+':'\u2212'}));const disclosure=document.querySelector('.disclosure button');disclosure?.addEventListener('click',()=>{const panel=document.getElementById(disclosure.getAttribute('aria-controls'));const open=disclosure.getAttribute('aria-expanded')==='true';disclosure.setAttribute('aria-expanded',String(!open));panel.hidden=open});const tocToggle=document.querySelector('.toc header button');tocToggle?.addEventListener('click',()=>{const nav=document.getElementById('toc-links');const open=tocToggle.getAttribute('aria-expanded')==='true';tocToggle.setAttribute('aria-expanded',String(!open));nav.hidden=open;tocToggle.textContent=open?'\u2304':'\u2303'});document.querySelector('.back-top')?.addEventListener('click',()=>scrollTo({top:0,behavior:'smooth'}));const copy=async text=>{try{await navigator.clipboard.writeText(text)}catch{const area=document.createElement('textarea');area.value=text;area.style.position='fixed';area.style.opacity='0';document.body.append(area);area.select();document.execCommand('copy');area.remove()}};const dialog=document.getElementById('citation-dialog');document.querySelector('[data-cite]')?.addEventListener('click',()=>dialog.showModal());document.querySelector('.dialog-close')?.addEventListener('click',()=>dialog.close());dialog?.addEventListener('click',event=>{if(event.target===dialog)dialog.close()});document.querySelectorAll('[data-citation]').forEach(button=>button.addEventListener('click',async()=>{const style=button.dataset.citation;await copy(citations[style]);document.querySelector('.citation-status').textContent=style+' citation copied to clipboard.'}));document.querySelector('[data-share]')?.addEventListener('click',async()=>{const status=document.querySelector('.action-status');if(navigator.share){try{await navigator.share({title,url:canonical});status.textContent='Sharing options opened.';return}catch(error){if(error.name==='AbortError')return}}await copy(canonical);status.textContent='Article link copied to clipboard.'})})()</script></body></html>`;
80724
81572
  const bytes = Buffer.byteLength(html);
80725
- const sha2565 = (0, import_node_crypto57.createHash)("sha256").update(html).digest("hex");
81573
+ const sha2565 = (0, import_node_crypto58.createHash)("sha256").update(html).digest("hex");
80726
81574
  return {
80727
81575
  html,
80728
81576
  filename: "blog-article.html",
@@ -80732,11 +81580,11 @@ function renderBlogArticleV1(input, config, generatedAt) {
80732
81580
  generatedAt: generatedAtIso
80733
81581
  };
80734
81582
  }
80735
- var import_node_crypto57;
81583
+ var import_node_crypto58;
80736
81584
  var init_render4 = __esm({
80737
81585
  "src/blog-article/render.ts"() {
80738
81586
  "use strict";
80739
- import_node_crypto57 = require("crypto");
81587
+ import_node_crypto58 = require("crypto");
80740
81588
  }
80741
81589
  });
80742
81590
 
@@ -81067,7 +81915,7 @@ function policy6() {
81067
81915
  };
81068
81916
  }
81069
81917
  function runStorageSegment(runId) {
81070
- return /^[a-zA-Z0-9_-]{1,160}$/.test(runId) ? runId : `run-${(0, import_node_crypto58.createHash)("sha256").update(runId).digest("hex").slice(0, 32)}`;
81918
+ return /^[a-zA-Z0-9_-]{1,160}$/.test(runId) ? runId : `run-${(0, import_node_crypto59.createHash)("sha256").update(runId).digest("hex").slice(0, 32)}`;
81071
81919
  }
81072
81920
  async function createScheduledRunArtifact(args) {
81073
81921
  if (args.rendered.bytes > SCHEDULED_RUN_ARTIFACT_MAX_BYTES) {
@@ -81108,12 +81956,12 @@ async function readScheduledRunArtifact(args) {
81108
81956
  if (!window2 || window2.nextOffset !== null || window2.totalBytes > SCHEDULED_RUN_ARTIFACT_MAX_BYTES) return null;
81109
81957
  return window2.text;
81110
81958
  }
81111
- var import_node_crypto58, SCHEDULED_RUN_ARTIFACT_PREFIX, SCHEDULED_RUN_ARTIFACT_DOWNLOAD_TTL_MS, SCHEDULED_RUN_ARTIFACT_MAX_BYTES;
81959
+ var import_node_crypto59, SCHEDULED_RUN_ARTIFACT_PREFIX, SCHEDULED_RUN_ARTIFACT_DOWNLOAD_TTL_MS, SCHEDULED_RUN_ARTIFACT_MAX_BYTES;
81112
81960
  var init_scheduled_run_artifact_store = __esm({
81113
81961
  "src/scheduled-artifacts/scheduled-run-artifact-store.ts"() {
81114
81962
  "use strict";
81115
81963
  init_private_artifacts();
81116
- import_node_crypto58 = require("crypto");
81964
+ import_node_crypto59 = require("crypto");
81117
81965
  SCHEDULED_RUN_ARTIFACT_PREFIX = "scheduled-run-artifacts/";
81118
81966
  SCHEDULED_RUN_ARTIFACT_DOWNLOAD_TTL_MS = 15 * 60 * 1e3;
81119
81967
  SCHEDULED_RUN_ARTIFACT_MAX_BYTES = 2e6;
@@ -81191,357 +82039,6 @@ var init_public_scheduled_result_routes = __esm({
81191
82039
  }
81192
82040
  });
81193
82041
 
81194
- // src/api/service-connections.ts
81195
- function ensureServiceConnectionsSchema() {
81196
- const currentDb = getDb();
81197
- if (schemaReady3 && schemaDb6 === currentDb) return schemaReady3;
81198
- schemaDb6 = currentDb;
81199
- schemaReady3 = (async () => {
81200
- const db = currentDb;
81201
- await db.execute(`
81202
- CREATE TABLE IF NOT EXISTS service_connections (
81203
- id TEXT PRIMARY KEY,
81204
- user_id INTEGER NOT NULL REFERENCES users(id),
81205
- provider_config_key TEXT NOT NULL,
81206
- provider TEXT,
81207
- transport TEXT NOT NULL,
81208
- upstream_connection_id TEXT,
81209
- label TEXT,
81210
- user_label TEXT,
81211
- provider_account_id TEXT,
81212
- provider_account_email TEXT,
81213
- provider_account_name TEXT,
81214
- provider_identity_checked_at TEXT,
81215
- provider_identity_source_updated_at TEXT,
81216
- lifecycle_status TEXT NOT NULL DEFAULT 'pending',
81217
- operational_status TEXT NOT NULL DEFAULT 'unknown',
81218
- reconnect_required INTEGER NOT NULL DEFAULT 0,
81219
- actions_enabled INTEGER NOT NULL DEFAULT 0,
81220
- read_tools_json TEXT NOT NULL DEFAULT '[]',
81221
- action_tools_json TEXT NOT NULL DEFAULT '[]',
81222
- tool_revision TEXT,
81223
- last_checked_at TEXT,
81224
- last_successful_call_at TEXT,
81225
- last_failure_at TEXT,
81226
- last_failure_code TEXT,
81227
- last_failure_retryable INTEGER,
81228
- consecutive_failures INTEGER NOT NULL DEFAULT 0,
81229
- vault_name TEXT,
81230
- table_name TEXT,
81231
- legacy_owner_identity TEXT,
81232
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
81233
- updated_at TEXT NOT NULL DEFAULT (datetime('now')),
81234
- UNIQUE(user_id, provider_config_key, upstream_connection_id)
81235
- )
81236
- `);
81237
- try {
81238
- await db.execute(`ALTER TABLE service_connections ADD COLUMN user_label TEXT`);
81239
- } catch {
81240
- }
81241
- try {
81242
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_id TEXT`);
81243
- } catch {
81244
- }
81245
- try {
81246
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_email TEXT`);
81247
- } catch {
81248
- }
81249
- try {
81250
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_name TEXT`);
81251
- } catch {
81252
- }
81253
- try {
81254
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_checked_at TEXT`);
81255
- } catch {
81256
- }
81257
- try {
81258
- await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_source_updated_at TEXT`);
81259
- } catch {
81260
- }
81261
- await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_user_updated ON service_connections(user_id, updated_at DESC)`);
81262
- await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_upstream ON service_connections(provider_config_key, upstream_connection_id)`);
81263
- await db.execute(`
81264
- CREATE TABLE IF NOT EXISTS service_connection_health_events (
81265
- id TEXT PRIMARY KEY,
81266
- connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
81267
- operational_status TEXT NOT NULL,
81268
- failure_code TEXT,
81269
- retryable INTEGER,
81270
- evidence_source TEXT NOT NULL,
81271
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
81272
- )
81273
- `);
81274
- await db.execute(`CREATE INDEX IF NOT EXISTS service_connection_health_events_connection ON service_connection_health_events(connection_id, created_at DESC)`);
81275
- await db.execute(`
81276
- CREATE TABLE IF NOT EXISTS service_connection_action_audit (
81277
- id TEXT PRIMARY KEY,
81278
- connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
81279
- user_id INTEGER NOT NULL REFERENCES users(id),
81280
- tool TEXT NOT NULL,
81281
- request_id TEXT NOT NULL,
81282
- status TEXT NOT NULL,
81283
- error_code TEXT,
81284
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
81285
- UNIQUE(user_id, request_id)
81286
- )
81287
- `);
81288
- try {
81289
- await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN request_digest TEXT`);
81290
- } catch {
81291
- }
81292
- try {
81293
- await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN result_json TEXT`);
81294
- } catch {
81295
- }
81296
- })().catch((error) => {
81297
- schemaReady3 = null;
81298
- schemaDb6 = null;
81299
- throw error;
81300
- });
81301
- return schemaReady3;
81302
- }
81303
- function parseTools(value) {
81304
- if (typeof value !== "string") return [];
81305
- try {
81306
- const parsed = JSON.parse(value);
81307
- return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
81308
- } catch {
81309
- return [];
81310
- }
81311
- }
81312
- function rowToRecord(row) {
81313
- return {
81314
- id: String(row.id),
81315
- userId: Number(row.user_id),
81316
- providerConfigKey: String(row.provider_config_key),
81317
- provider: typeof row.provider === "string" ? row.provider : null,
81318
- transport: row.transport === "remote_mcp" ? "remote_mcp" : "nango",
81319
- upstreamConnectionId: typeof row.upstream_connection_id === "string" ? row.upstream_connection_id : null,
81320
- label: typeof row.label === "string" ? row.label : null,
81321
- userLabel: typeof row.user_label === "string" ? row.user_label : null,
81322
- providerAccountId: typeof row.provider_account_id === "string" ? row.provider_account_id : null,
81323
- providerAccountEmail: typeof row.provider_account_email === "string" ? row.provider_account_email : null,
81324
- providerAccountName: typeof row.provider_account_name === "string" ? row.provider_account_name : null,
81325
- providerIdentityCheckedAt: typeof row.provider_identity_checked_at === "string" ? row.provider_identity_checked_at : null,
81326
- providerIdentitySourceUpdatedAt: typeof row.provider_identity_source_updated_at === "string" ? row.provider_identity_source_updated_at : null,
81327
- lifecycleStatus: String(row.lifecycle_status),
81328
- operationalStatus: String(row.operational_status),
81329
- reconnectRequired: Number(row.reconnect_required) === 1,
81330
- actionsEnabled: Number(row.actions_enabled) === 1,
81331
- readTools: parseTools(row.read_tools_json),
81332
- actionTools: parseTools(row.action_tools_json),
81333
- toolRevision: typeof row.tool_revision === "string" ? row.tool_revision : null,
81334
- lastCheckedAt: typeof row.last_checked_at === "string" ? row.last_checked_at : null,
81335
- lastSuccessfulCallAt: typeof row.last_successful_call_at === "string" ? row.last_successful_call_at : null,
81336
- lastFailureAt: typeof row.last_failure_at === "string" ? row.last_failure_at : null,
81337
- lastFailureCode: typeof row.last_failure_code === "string" ? row.last_failure_code : null,
81338
- lastFailureRetryable: row.last_failure_retryable === null || row.last_failure_retryable === void 0 ? null : Number(row.last_failure_retryable) === 1,
81339
- consecutiveFailures: Number(row.consecutive_failures ?? 0),
81340
- vaultName: typeof row.vault_name === "string" ? row.vault_name : null,
81341
- tableName: typeof row.table_name === "string" ? row.table_name : null,
81342
- createdAt: String(row.created_at),
81343
- updatedAt: String(row.updated_at)
81344
- };
81345
- }
81346
- async function userIdForIdentity(identity) {
81347
- const user = await getUserByEmail(identity.normalize("NFKC").trim().toLowerCase());
81348
- if (!user) throw new Error("service_connection_user_not_found");
81349
- return Number(user.id);
81350
- }
81351
- async function reconcileDiscoveredNangoConnections(identity, discovered) {
81352
- await ensureServiceConnectionsSchema();
81353
- const db = getDb();
81354
- const userId = await userIdForIdentity(identity);
81355
- for (const connection of discovered) {
81356
- await db.execute({
81357
- sql: `
81358
- INSERT INTO service_connections (
81359
- id, user_id, provider_config_key, provider, transport, upstream_connection_id,
81360
- label, lifecycle_status, reconnect_required, actions_enabled, legacy_owner_identity, created_at, updated_at
81361
- ) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, 1, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
81362
- ON CONFLICT(user_id, provider_config_key, upstream_connection_id) DO UPDATE SET
81363
- provider = excluded.provider,
81364
- label = COALESCE(excluded.label, service_connections.label),
81365
- lifecycle_status = excluded.lifecycle_status,
81366
- reconnect_required = excluded.reconnect_required,
81367
- updated_at = excluded.updated_at
81368
- `,
81369
- args: [
81370
- (0, import_node_crypto59.randomUUID)(),
81371
- userId,
81372
- connection.providerConfigKey,
81373
- connection.provider,
81374
- connection.upstreamConnectionId,
81375
- connection.label,
81376
- connection.lifecycleStatus,
81377
- connection.reconnectRequired ? 1 : 0,
81378
- identity,
81379
- connection.createdAt,
81380
- connection.updatedAt
81381
- ]
81382
- });
81383
- }
81384
- return listServiceConnections(identity, "nango");
81385
- }
81386
- async function listServiceConnections(identity, transport) {
81387
- await ensureServiceConnectionsSchema();
81388
- const userId = await userIdForIdentity(identity);
81389
- const result = await getDb().execute({
81390
- sql: `SELECT * FROM service_connections WHERE user_id = ?${transport ? " AND transport = ?" : ""} AND lifecycle_status <> 'disconnected' ORDER BY updated_at DESC`,
81391
- args: transport ? [userId, transport] : [userId]
81392
- });
81393
- return result.rows.map((row) => rowToRecord(row));
81394
- }
81395
- async function getOwnedServiceConnection(identity, connectionId) {
81396
- await ensureServiceConnectionsSchema();
81397
- const userId = await userIdForIdentity(identity);
81398
- const result = await getDb().execute({
81399
- sql: "SELECT * FROM service_connections WHERE id = ? AND user_id = ? LIMIT 1",
81400
- args: [connectionId, userId]
81401
- });
81402
- const row = result.rows[0];
81403
- return row ? rowToRecord(row) : null;
81404
- }
81405
- async function updateServiceConnectionTools(connectionId, readTools, actionTools, toolRevision) {
81406
- await ensureServiceConnectionsSchema();
81407
- await getDb().execute({
81408
- sql: `UPDATE service_connections SET read_tools_json = ?, action_tools_json = ?, tool_revision = ?, updated_at = datetime('now') WHERE id = ?`,
81409
- args: [JSON.stringify(readTools), JSON.stringify(actionTools), toolRevision, connectionId]
81410
- });
81411
- }
81412
- async function setServiceConnectionProviderIdentity(args) {
81413
- await ensureServiceConnectionsSchema();
81414
- await getDb().execute({
81415
- sql: `
81416
- UPDATE service_connections SET
81417
- provider_account_id = ?, provider_account_email = ?, provider_account_name = ?,
81418
- provider_identity_checked_at = datetime('now'), provider_identity_source_updated_at = ?,
81419
- updated_at = datetime('now')
81420
- WHERE id = ?
81421
- `,
81422
- args: [
81423
- args.providerAccountId ?? null,
81424
- args.providerAccountEmail?.toLowerCase() ?? null,
81425
- args.providerAccountName ?? null,
81426
- args.sourceUpdatedAt,
81427
- args.connectionId
81428
- ]
81429
- });
81430
- }
81431
- async function setServiceConnectionUserLabel(identity, connectionId, userLabel) {
81432
- const connection = await getOwnedServiceConnection(identity, connectionId);
81433
- if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
81434
- await getDb().execute({
81435
- sql: `UPDATE service_connections SET user_label = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
81436
- args: [userLabel, connectionId, connection.userId]
81437
- });
81438
- return userLabel;
81439
- }
81440
- async function recordServiceConnectionHealth(args) {
81441
- await ensureServiceConnectionsSchema();
81442
- const now = (/* @__PURE__ */ new Date()).toISOString();
81443
- await getDb().execute({
81444
- sql: `
81445
- UPDATE service_connections SET
81446
- operational_status = ?, last_checked_at = ?,
81447
- last_successful_call_at = CASE WHEN ? = 'available' THEN ? ELSE last_successful_call_at END,
81448
- last_failure_at = CASE WHEN ? IN ('degraded', 'unavailable') THEN ? ELSE NULL END,
81449
- last_failure_code = ?, last_failure_retryable = ?,
81450
- consecutive_failures = CASE WHEN ? = 'available' THEN 0 ELSE consecutive_failures + 1 END,
81451
- updated_at = datetime('now')
81452
- WHERE id = ?
81453
- `,
81454
- args: [
81455
- args.operationalStatus,
81456
- now,
81457
- args.operationalStatus,
81458
- now,
81459
- args.operationalStatus,
81460
- now,
81461
- args.failureCode ?? null,
81462
- args.retryable == null ? null : args.retryable ? 1 : 0,
81463
- args.operationalStatus,
81464
- args.connectionId
81465
- ]
81466
- });
81467
- await getDb().execute({
81468
- sql: `INSERT INTO service_connection_health_events (id, connection_id, operational_status, failure_code, retryable, evidence_source) VALUES (?, ?, ?, ?, ?, ?)`,
81469
- args: [(0, import_node_crypto59.randomUUID)(), args.connectionId, args.operationalStatus, args.failureCode ?? null, args.retryable == null ? null : args.retryable ? 1 : 0, args.evidenceSource]
81470
- });
81471
- }
81472
- async function setServiceConnectionActions(identity, connectionId, enabled) {
81473
- const connection = await getOwnedServiceConnection(identity, connectionId);
81474
- if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
81475
- await getDb().execute({
81476
- sql: `UPDATE service_connections SET actions_enabled = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
81477
- args: [enabled ? 1 : 0, connectionId, connection.userId]
81478
- });
81479
- return enabled;
81480
- }
81481
- async function markServiceConnectionDisconnected(identity, connectionId) {
81482
- const connection = await getOwnedServiceConnection(identity, connectionId);
81483
- if (!connection) throw new Error("service_connection_not_found");
81484
- await getDb().execute({
81485
- sql: `UPDATE service_connections SET lifecycle_status = 'disconnected', operational_status = 'unknown', reconnect_required = 0, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
81486
- args: [connectionId, connection.userId]
81487
- });
81488
- const remaining = await getDb().execute({
81489
- sql: `SELECT COUNT(*) AS count FROM service_connections WHERE user_id = ? AND transport = 'nango' AND lifecycle_status = 'connected'`,
81490
- args: [connection.userId]
81491
- });
81492
- return Number(remaining.rows[0]?.count ?? 0);
81493
- }
81494
- async function recordServiceConnectionAction(args) {
81495
- await ensureServiceConnectionsSchema();
81496
- const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
81497
- if (!connection) throw new Error("service_connection_not_found");
81498
- await getDb().execute({
81499
- sql: `UPDATE service_connection_action_audit SET status = ?, error_code = ?, result_json = ? WHERE user_id = ? AND request_id = ? AND connection_id = ?`,
81500
- args: [args.status, args.errorCode ?? null, args.result === void 0 ? null : JSON.stringify(args.result), connection.userId, args.requestId, connection.id]
81501
- });
81502
- }
81503
- async function claimServiceConnectionAction(args) {
81504
- await ensureServiceConnectionsSchema();
81505
- const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
81506
- if (!connection) throw new Error("service_connection_not_found");
81507
- const inserted = await getDb().execute({
81508
- sql: `INSERT OR IGNORE INTO service_connection_action_audit (id, connection_id, user_id, tool, request_id, status, request_digest) VALUES (?, ?, ?, ?, ?, 'started', ?)`,
81509
- args: [(0, import_node_crypto59.randomUUID)(), connection.id, connection.userId, args.tool, args.requestId, args.requestDigest]
81510
- });
81511
- if (Number(inserted.rowsAffected ?? 0) === 1) return { claimed: true };
81512
- const existing = await getDb().execute({
81513
- sql: `SELECT connection_id, tool, status, error_code, request_digest, result_json FROM service_connection_action_audit WHERE user_id = ? AND request_id = ? LIMIT 1`,
81514
- args: [connection.userId, args.requestId]
81515
- });
81516
- const row = existing.rows[0];
81517
- const conflict = String(row?.connection_id ?? "") !== connection.id || String(row?.tool ?? "") !== args.tool || String(row?.request_digest ?? "") !== args.requestDigest;
81518
- let result = void 0;
81519
- if (!conflict && typeof row?.result_json === "string") {
81520
- try {
81521
- result = JSON.parse(row.result_json);
81522
- } catch {
81523
- result = void 0;
81524
- }
81525
- }
81526
- return {
81527
- claimed: false,
81528
- status: String(row?.status ?? "unknown"),
81529
- errorCode: typeof row?.error_code === "string" ? row.error_code : null,
81530
- conflict,
81531
- ...result !== void 0 ? { result } : {}
81532
- };
81533
- }
81534
- var import_node_crypto59, schemaReady3, schemaDb6;
81535
- var init_service_connections = __esm({
81536
- "src/api/service-connections.ts"() {
81537
- "use strict";
81538
- import_node_crypto59 = require("crypto");
81539
- init_db();
81540
- schemaReady3 = null;
81541
- schemaDb6 = null;
81542
- }
81543
- });
81544
-
81545
82042
  // src/api/scheduler-integration-auth.ts
81546
82043
  function signingSecret() {
81547
82044
  const secret2 = process.env.SCHEDULER_INTEGRATION_SIGNING_SECRET?.trim();