mcp-scraper 0.66.6 → 0.66.7

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 +9 -1
  2. package/README.md +1 -1
  3. package/dist/bin/api-server.cjs +889 -597
  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 +102 -16
  10. package/dist/bin/mcp-stdio-server.js +6 -6
  11. package/dist/bin/paa-harvest.cjs +14 -1
  12. package/dist/bin/paa-harvest.js +3 -3
  13. package/dist/{chunk-NLEAYKOC.js → chunk-7KB5QTHP.js} +1 -1
  14. package/dist/{chunk-K7Y27ND3.js → chunk-CGOMGGOF.js} +2 -2
  15. package/dist/{chunk-DBWWIK7O.js → chunk-EVOBJYBE.js} +1 -1
  16. package/dist/{chunk-3EYALCYK.js → chunk-F434YJPV.js} +1 -1
  17. package/dist/{chunk-PMCT2YQM.js → chunk-HBZENASD.js} +1 -1
  18. package/dist/{chunk-KXRWY7DZ.js → chunk-HWGQVP3Q.js} +1 -1
  19. package/dist/{chunk-55B3T5L2.js → chunk-HZDJ5UZ4.js} +68 -1
  20. package/dist/{chunk-ABYBPODM.js → chunk-JZPSG4NR.js} +1 -1
  21. package/dist/{chunk-CETDPNMZ.js → chunk-LVXRWJJJ.js} +1 -1
  22. package/dist/{chunk-KPE52NPT.js → chunk-O7ZC5Y3Z.js} +1 -1
  23. package/dist/{chunk-3TKKJQB4.js → chunk-U2UHJWFW.js} +1 -1
  24. package/dist/{chunk-VMWVEKZT.js → chunk-Z5QQEWXA.js} +50 -17
  25. package/dist/{db-4RVZ3NYZ.js → db-MNNNIWVS.js} +1 -1
  26. package/dist/{extract-bundle-EA5URLXN.js → extract-bundle-RW7CVIRF.js} +3 -3
  27. package/dist/index.cjs +14 -1
  28. package/dist/index.js +3 -3
  29. package/dist/{lead-list-enrichment-repository-IBXNWM6H.js → lead-list-enrichment-repository-PZYE6TDP.js} +2 -2
  30. package/dist/{location-data-repository-WHHQUCUI.js → location-data-repository-EPCNGMOM.js} +2 -2
  31. package/dist/{server-5WPS3LZD.js → server-KSVJ4X4V.js} +633 -444
  32. package/dist/{site-extract-repository-NTM4MDQN.js → site-extract-repository-CZ7SVT3Z.js} +2 -2
  33. package/dist/{worker-UTBUWEXQ.js → worker-IBLGV272.js} +5 -5
  34. package/package.json +1 -1
@@ -164,6 +164,20 @@ 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_format_unsupported":
178
+ case "image_storage_unconfigured":
179
+ case "featured_image_unhostable":
180
+ return "invalid_request";
167
181
  case "insufficient_balance":
168
182
  return "billing";
169
183
  case "vendor_unavailable":
@@ -237,6 +251,32 @@ function publicErrorMessage(code) {
237
251
  return "The stored site export could not be read or did not pass its integrity check.";
238
252
  case "site_export_image_not_found":
239
253
  return "The requested site-export image was not found, expired, or does not belong to this account.";
254
+ case "image_url_invalid":
255
+ return "The image source is not a valid URL. Use a stable public HTTPS URL or inline image bytes.";
256
+ case "image_url_not_https":
257
+ return "Commons image sources must use a public HTTPS URL or inline image bytes.";
258
+ case "image_host_unresolvable":
259
+ return "The image host could not be resolved. Use a different stable public URL or inline image bytes.";
260
+ case "image_host_blocked":
261
+ return "The image source resolves to a network address Commons cannot fetch safely.";
262
+ case "image_redirect_invalid":
263
+ return "The image source redirected to an invalid or unsafe destination.";
264
+ case "image_source_unavailable":
265
+ return "The image source could not be fetched. Host a different stable public URL or provide inline image bytes.";
266
+ case "image_too_large":
267
+ return "The image exceeds the Commons image size limit.";
268
+ case "image_redirect_limit":
269
+ return "The image source exceeded the Commons redirect limit.";
270
+ case "image_base64_invalid":
271
+ return "The supplied inline image bytes are invalid.";
272
+ case "image_source_missing":
273
+ return "Provide either a stable public image URL or inline image bytes.";
274
+ case "image_format_unsupported":
275
+ return "Commons accepts JPEG, PNG, GIF, or WebP image bytes.";
276
+ case "image_storage_unconfigured":
277
+ return "Commons image storage is temporarily unavailable.";
278
+ case "featured_image_unhostable":
279
+ return "The required featured image could not be hosted for Commons publication.";
240
280
  default:
241
281
  return PUBLIC_SERVICE_UNAVAILABLE_MESSAGE;
242
282
  }
@@ -299,7 +339,20 @@ var init_errors = __esm({
299
339
  "site_export_not_found",
300
340
  "site_export_format_unavailable",
301
341
  "site_export_read_failed",
302
- "site_export_image_not_found"
342
+ "site_export_image_not_found",
343
+ "image_url_invalid",
344
+ "image_url_not_https",
345
+ "image_host_unresolvable",
346
+ "image_host_blocked",
347
+ "image_redirect_invalid",
348
+ "image_source_unavailable",
349
+ "image_too_large",
350
+ "image_redirect_limit",
351
+ "image_base64_invalid",
352
+ "image_source_missing",
353
+ "image_format_unsupported",
354
+ "image_storage_unconfigured",
355
+ "featured_image_unhostable"
303
356
  ];
304
357
  PUBLIC_ERROR_TYPES = [
305
358
  "request_aborted",
@@ -4495,6 +4548,20 @@ async function migrate(options = {}) {
4495
4548
  PRIMARY KEY (user_id, connection_id, period_start)
4496
4549
  )
4497
4550
  `);
4551
+ await db.execute(`
4552
+ CREATE TABLE IF NOT EXISTS connected_account_charge_transfers (
4553
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
4554
+ user_id INTEGER NOT NULL,
4555
+ period_start TEXT NOT NULL,
4556
+ from_connection_id TEXT NOT NULL,
4557
+ to_connection_id TEXT NOT NULL,
4558
+ provider_config_key TEXT NOT NULL,
4559
+ transferred_at TEXT NOT NULL DEFAULT (datetime('now')),
4560
+ UNIQUE (user_id, period_start, from_connection_id),
4561
+ CHECK (from_connection_id <> to_connection_id)
4562
+ )
4563
+ `);
4564
+ 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
4565
  await db.execute(`
4499
4566
  CREATE TABLE IF NOT EXISTS billing_credits (
4500
4567
  idempotency_key TEXT PRIMARY KEY,
@@ -36288,6 +36355,357 @@ var init_subscription_status_policy = __esm({
36288
36355
  }
36289
36356
  });
36290
36357
 
36358
+ // src/api/service-connections.ts
36359
+ function ensureServiceConnectionsSchema() {
36360
+ const currentDb = getDb();
36361
+ if (schemaReady && schemaDb5 === currentDb) return schemaReady;
36362
+ schemaDb5 = currentDb;
36363
+ schemaReady = (async () => {
36364
+ const db = currentDb;
36365
+ await db.execute(`
36366
+ CREATE TABLE IF NOT EXISTS service_connections (
36367
+ id TEXT PRIMARY KEY,
36368
+ user_id INTEGER NOT NULL REFERENCES users(id),
36369
+ provider_config_key TEXT NOT NULL,
36370
+ provider TEXT,
36371
+ transport TEXT NOT NULL,
36372
+ upstream_connection_id TEXT,
36373
+ label TEXT,
36374
+ user_label TEXT,
36375
+ provider_account_id TEXT,
36376
+ provider_account_email TEXT,
36377
+ provider_account_name TEXT,
36378
+ provider_identity_checked_at TEXT,
36379
+ provider_identity_source_updated_at TEXT,
36380
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending',
36381
+ operational_status TEXT NOT NULL DEFAULT 'unknown',
36382
+ reconnect_required INTEGER NOT NULL DEFAULT 0,
36383
+ actions_enabled INTEGER NOT NULL DEFAULT 0,
36384
+ read_tools_json TEXT NOT NULL DEFAULT '[]',
36385
+ action_tools_json TEXT NOT NULL DEFAULT '[]',
36386
+ tool_revision TEXT,
36387
+ last_checked_at TEXT,
36388
+ last_successful_call_at TEXT,
36389
+ last_failure_at TEXT,
36390
+ last_failure_code TEXT,
36391
+ last_failure_retryable INTEGER,
36392
+ consecutive_failures INTEGER NOT NULL DEFAULT 0,
36393
+ vault_name TEXT,
36394
+ table_name TEXT,
36395
+ legacy_owner_identity TEXT,
36396
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
36397
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
36398
+ UNIQUE(user_id, provider_config_key, upstream_connection_id)
36399
+ )
36400
+ `);
36401
+ try {
36402
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN user_label TEXT`);
36403
+ } catch {
36404
+ }
36405
+ try {
36406
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_id TEXT`);
36407
+ } catch {
36408
+ }
36409
+ try {
36410
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_email TEXT`);
36411
+ } catch {
36412
+ }
36413
+ try {
36414
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_account_name TEXT`);
36415
+ } catch {
36416
+ }
36417
+ try {
36418
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_checked_at TEXT`);
36419
+ } catch {
36420
+ }
36421
+ try {
36422
+ await db.execute(`ALTER TABLE service_connections ADD COLUMN provider_identity_source_updated_at TEXT`);
36423
+ } catch {
36424
+ }
36425
+ await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_user_updated ON service_connections(user_id, updated_at DESC)`);
36426
+ await db.execute(`CREATE INDEX IF NOT EXISTS service_connections_upstream ON service_connections(provider_config_key, upstream_connection_id)`);
36427
+ await db.execute(`
36428
+ CREATE TABLE IF NOT EXISTS service_connection_health_events (
36429
+ id TEXT PRIMARY KEY,
36430
+ connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
36431
+ operational_status TEXT NOT NULL,
36432
+ failure_code TEXT,
36433
+ retryable INTEGER,
36434
+ evidence_source TEXT NOT NULL,
36435
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
36436
+ )
36437
+ `);
36438
+ await db.execute(`CREATE INDEX IF NOT EXISTS service_connection_health_events_connection ON service_connection_health_events(connection_id, created_at DESC)`);
36439
+ await db.execute(`
36440
+ CREATE TABLE IF NOT EXISTS service_connection_action_audit (
36441
+ id TEXT PRIMARY KEY,
36442
+ connection_id TEXT NOT NULL REFERENCES service_connections(id) ON DELETE CASCADE,
36443
+ user_id INTEGER NOT NULL REFERENCES users(id),
36444
+ tool TEXT NOT NULL,
36445
+ request_id TEXT NOT NULL,
36446
+ status TEXT NOT NULL,
36447
+ error_code TEXT,
36448
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
36449
+ UNIQUE(user_id, request_id)
36450
+ )
36451
+ `);
36452
+ try {
36453
+ await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN request_digest TEXT`);
36454
+ } catch {
36455
+ }
36456
+ try {
36457
+ await db.execute(`ALTER TABLE service_connection_action_audit ADD COLUMN result_json TEXT`);
36458
+ } catch {
36459
+ }
36460
+ })().catch((error) => {
36461
+ schemaReady = null;
36462
+ schemaDb5 = null;
36463
+ throw error;
36464
+ });
36465
+ return schemaReady;
36466
+ }
36467
+ function parseTools(value) {
36468
+ if (typeof value !== "string") return [];
36469
+ try {
36470
+ const parsed = JSON.parse(value);
36471
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
36472
+ } catch {
36473
+ return [];
36474
+ }
36475
+ }
36476
+ function rowToRecord(row) {
36477
+ return {
36478
+ id: String(row.id),
36479
+ userId: Number(row.user_id),
36480
+ providerConfigKey: String(row.provider_config_key),
36481
+ provider: typeof row.provider === "string" ? row.provider : null,
36482
+ transport: row.transport === "remote_mcp" ? "remote_mcp" : "nango",
36483
+ upstreamConnectionId: typeof row.upstream_connection_id === "string" ? row.upstream_connection_id : null,
36484
+ label: typeof row.label === "string" ? row.label : null,
36485
+ userLabel: typeof row.user_label === "string" ? row.user_label : null,
36486
+ providerAccountId: typeof row.provider_account_id === "string" ? row.provider_account_id : null,
36487
+ providerAccountEmail: typeof row.provider_account_email === "string" ? row.provider_account_email : null,
36488
+ providerAccountName: typeof row.provider_account_name === "string" ? row.provider_account_name : null,
36489
+ providerIdentityCheckedAt: typeof row.provider_identity_checked_at === "string" ? row.provider_identity_checked_at : null,
36490
+ providerIdentitySourceUpdatedAt: typeof row.provider_identity_source_updated_at === "string" ? row.provider_identity_source_updated_at : null,
36491
+ lifecycleStatus: String(row.lifecycle_status),
36492
+ operationalStatus: String(row.operational_status),
36493
+ reconnectRequired: Number(row.reconnect_required) === 1,
36494
+ actionsEnabled: Number(row.actions_enabled) === 1,
36495
+ readTools: parseTools(row.read_tools_json),
36496
+ actionTools: parseTools(row.action_tools_json),
36497
+ toolRevision: typeof row.tool_revision === "string" ? row.tool_revision : null,
36498
+ lastCheckedAt: typeof row.last_checked_at === "string" ? row.last_checked_at : null,
36499
+ lastSuccessfulCallAt: typeof row.last_successful_call_at === "string" ? row.last_successful_call_at : null,
36500
+ lastFailureAt: typeof row.last_failure_at === "string" ? row.last_failure_at : null,
36501
+ lastFailureCode: typeof row.last_failure_code === "string" ? row.last_failure_code : null,
36502
+ lastFailureRetryable: row.last_failure_retryable === null || row.last_failure_retryable === void 0 ? null : Number(row.last_failure_retryable) === 1,
36503
+ consecutiveFailures: Number(row.consecutive_failures ?? 0),
36504
+ vaultName: typeof row.vault_name === "string" ? row.vault_name : null,
36505
+ tableName: typeof row.table_name === "string" ? row.table_name : null,
36506
+ createdAt: String(row.created_at),
36507
+ updatedAt: String(row.updated_at)
36508
+ };
36509
+ }
36510
+ async function userIdForIdentity(identity) {
36511
+ const user = await getUserByEmail(identity.normalize("NFKC").trim().toLowerCase());
36512
+ if (!user) throw new Error("service_connection_user_not_found");
36513
+ return Number(user.id);
36514
+ }
36515
+ async function reconcileDiscoveredNangoConnections(identity, discovered) {
36516
+ await ensureServiceConnectionsSchema();
36517
+ const db = getDb();
36518
+ const userId = await userIdForIdentity(identity);
36519
+ for (const connection of discovered) {
36520
+ await db.execute({
36521
+ sql: `
36522
+ INSERT INTO service_connections (
36523
+ id, user_id, provider_config_key, provider, transport, upstream_connection_id,
36524
+ label, lifecycle_status, reconnect_required, actions_enabled, legacy_owner_identity, created_at, updated_at
36525
+ ) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, 1, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
36526
+ ON CONFLICT(user_id, provider_config_key, upstream_connection_id) DO UPDATE SET
36527
+ provider = excluded.provider,
36528
+ label = COALESCE(excluded.label, service_connections.label),
36529
+ lifecycle_status = excluded.lifecycle_status,
36530
+ reconnect_required = excluded.reconnect_required,
36531
+ updated_at = excluded.updated_at
36532
+ `,
36533
+ args: [
36534
+ (0, import_node_crypto21.randomUUID)(),
36535
+ userId,
36536
+ connection.providerConfigKey,
36537
+ connection.provider,
36538
+ connection.upstreamConnectionId,
36539
+ connection.label,
36540
+ connection.lifecycleStatus,
36541
+ connection.reconnectRequired ? 1 : 0,
36542
+ identity,
36543
+ connection.createdAt,
36544
+ connection.updatedAt
36545
+ ]
36546
+ });
36547
+ }
36548
+ return listServiceConnections(identity, "nango");
36549
+ }
36550
+ async function listServiceConnections(identity, transport) {
36551
+ await ensureServiceConnectionsSchema();
36552
+ const userId = await userIdForIdentity(identity);
36553
+ const result = await getDb().execute({
36554
+ sql: `SELECT * FROM service_connections WHERE user_id = ?${transport ? " AND transport = ?" : ""} AND lifecycle_status <> 'disconnected' ORDER BY updated_at DESC`,
36555
+ args: transport ? [userId, transport] : [userId]
36556
+ });
36557
+ return result.rows.map((row) => rowToRecord(row));
36558
+ }
36559
+ async function getOwnedServiceConnection(identity, connectionId) {
36560
+ await ensureServiceConnectionsSchema();
36561
+ const userId = await userIdForIdentity(identity);
36562
+ const result = await getDb().execute({
36563
+ sql: "SELECT * FROM service_connections WHERE id = ? AND user_id = ? LIMIT 1",
36564
+ args: [connectionId, userId]
36565
+ });
36566
+ const row = result.rows[0];
36567
+ return row ? rowToRecord(row) : null;
36568
+ }
36569
+ async function updateServiceConnectionTools(connectionId, readTools, actionTools, toolRevision) {
36570
+ await ensureServiceConnectionsSchema();
36571
+ await getDb().execute({
36572
+ sql: `UPDATE service_connections SET read_tools_json = ?, action_tools_json = ?, tool_revision = ?, updated_at = datetime('now') WHERE id = ?`,
36573
+ args: [JSON.stringify(readTools), JSON.stringify(actionTools), toolRevision, connectionId]
36574
+ });
36575
+ }
36576
+ async function setServiceConnectionProviderIdentity(args) {
36577
+ await ensureServiceConnectionsSchema();
36578
+ await getDb().execute({
36579
+ sql: `
36580
+ UPDATE service_connections SET
36581
+ provider_account_id = ?, provider_account_email = ?, provider_account_name = ?,
36582
+ provider_identity_checked_at = datetime('now'), provider_identity_source_updated_at = ?,
36583
+ updated_at = datetime('now')
36584
+ WHERE id = ?
36585
+ `,
36586
+ args: [
36587
+ args.providerAccountId ?? null,
36588
+ args.providerAccountEmail?.toLowerCase() ?? null,
36589
+ args.providerAccountName ?? null,
36590
+ args.sourceUpdatedAt,
36591
+ args.connectionId
36592
+ ]
36593
+ });
36594
+ }
36595
+ async function setServiceConnectionUserLabel(identity, connectionId, userLabel) {
36596
+ const connection = await getOwnedServiceConnection(identity, connectionId);
36597
+ if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
36598
+ await getDb().execute({
36599
+ sql: `UPDATE service_connections SET user_label = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
36600
+ args: [userLabel, connectionId, connection.userId]
36601
+ });
36602
+ return userLabel;
36603
+ }
36604
+ async function recordServiceConnectionHealth(args) {
36605
+ await ensureServiceConnectionsSchema();
36606
+ const now = (/* @__PURE__ */ new Date()).toISOString();
36607
+ await getDb().execute({
36608
+ sql: `
36609
+ UPDATE service_connections SET
36610
+ operational_status = ?, last_checked_at = ?,
36611
+ last_successful_call_at = CASE WHEN ? = 'available' THEN ? ELSE last_successful_call_at END,
36612
+ last_failure_at = CASE WHEN ? IN ('degraded', 'unavailable') THEN ? ELSE NULL END,
36613
+ last_failure_code = ?, last_failure_retryable = ?,
36614
+ consecutive_failures = CASE WHEN ? = 'available' THEN 0 ELSE consecutive_failures + 1 END,
36615
+ updated_at = datetime('now')
36616
+ WHERE id = ?
36617
+ `,
36618
+ args: [
36619
+ args.operationalStatus,
36620
+ now,
36621
+ args.operationalStatus,
36622
+ now,
36623
+ args.operationalStatus,
36624
+ now,
36625
+ args.failureCode ?? null,
36626
+ args.retryable == null ? null : args.retryable ? 1 : 0,
36627
+ args.operationalStatus,
36628
+ args.connectionId
36629
+ ]
36630
+ });
36631
+ await getDb().execute({
36632
+ sql: `INSERT INTO service_connection_health_events (id, connection_id, operational_status, failure_code, retryable, evidence_source) VALUES (?, ?, ?, ?, ?, ?)`,
36633
+ args: [(0, import_node_crypto21.randomUUID)(), args.connectionId, args.operationalStatus, args.failureCode ?? null, args.retryable == null ? null : args.retryable ? 1 : 0, args.evidenceSource]
36634
+ });
36635
+ }
36636
+ async function setServiceConnectionActions(identity, connectionId, enabled) {
36637
+ const connection = await getOwnedServiceConnection(identity, connectionId);
36638
+ if (!connection || connection.lifecycleStatus === "disconnected") throw new Error("service_connection_not_found");
36639
+ await getDb().execute({
36640
+ sql: `UPDATE service_connections SET actions_enabled = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
36641
+ args: [enabled ? 1 : 0, connectionId, connection.userId]
36642
+ });
36643
+ return enabled;
36644
+ }
36645
+ async function markServiceConnectionDisconnected(identity, connectionId) {
36646
+ const connection = await getOwnedServiceConnection(identity, connectionId);
36647
+ if (!connection) throw new Error("service_connection_not_found");
36648
+ await getDb().execute({
36649
+ sql: `UPDATE service_connections SET lifecycle_status = 'disconnected', operational_status = 'unknown', reconnect_required = 0, updated_at = datetime('now') WHERE id = ? AND user_id = ?`,
36650
+ args: [connectionId, connection.userId]
36651
+ });
36652
+ const remaining = await getDb().execute({
36653
+ sql: `SELECT COUNT(*) AS count FROM service_connections WHERE user_id = ? AND transport = 'nango' AND lifecycle_status = 'connected'`,
36654
+ args: [connection.userId]
36655
+ });
36656
+ return Number(remaining.rows[0]?.count ?? 0);
36657
+ }
36658
+ async function recordServiceConnectionAction(args) {
36659
+ await ensureServiceConnectionsSchema();
36660
+ const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
36661
+ if (!connection) throw new Error("service_connection_not_found");
36662
+ await getDb().execute({
36663
+ sql: `UPDATE service_connection_action_audit SET status = ?, error_code = ?, result_json = ? WHERE user_id = ? AND request_id = ? AND connection_id = ?`,
36664
+ args: [args.status, args.errorCode ?? null, args.result === void 0 ? null : JSON.stringify(args.result), connection.userId, args.requestId, connection.id]
36665
+ });
36666
+ }
36667
+ async function claimServiceConnectionAction(args) {
36668
+ await ensureServiceConnectionsSchema();
36669
+ const connection = await getOwnedServiceConnection(args.identity, args.connectionId);
36670
+ if (!connection) throw new Error("service_connection_not_found");
36671
+ const inserted = await getDb().execute({
36672
+ sql: `INSERT OR IGNORE INTO service_connection_action_audit (id, connection_id, user_id, tool, request_id, status, request_digest) VALUES (?, ?, ?, ?, ?, 'started', ?)`,
36673
+ args: [(0, import_node_crypto21.randomUUID)(), connection.id, connection.userId, args.tool, args.requestId, args.requestDigest]
36674
+ });
36675
+ if (Number(inserted.rowsAffected ?? 0) === 1) return { claimed: true };
36676
+ const existing = await getDb().execute({
36677
+ 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`,
36678
+ args: [connection.userId, args.requestId]
36679
+ });
36680
+ const row = existing.rows[0];
36681
+ const conflict = String(row?.connection_id ?? "") !== connection.id || String(row?.tool ?? "") !== args.tool || String(row?.request_digest ?? "") !== args.requestDigest;
36682
+ let result = void 0;
36683
+ if (!conflict && typeof row?.result_json === "string") {
36684
+ try {
36685
+ result = JSON.parse(row.result_json);
36686
+ } catch {
36687
+ result = void 0;
36688
+ }
36689
+ }
36690
+ return {
36691
+ claimed: false,
36692
+ status: String(row?.status ?? "unknown"),
36693
+ errorCode: typeof row?.error_code === "string" ? row.error_code : null,
36694
+ conflict,
36695
+ ...result !== void 0 ? { result } : {}
36696
+ };
36697
+ }
36698
+ var import_node_crypto21, schemaReady, schemaDb5;
36699
+ var init_service_connections = __esm({
36700
+ "src/api/service-connections.ts"() {
36701
+ "use strict";
36702
+ import_node_crypto21 = require("crypto");
36703
+ init_db();
36704
+ schemaReady = null;
36705
+ schemaDb5 = null;
36706
+ }
36707
+ });
36708
+
36291
36709
  // src/api/connected-account-billing.ts
36292
36710
  function connectedBillingPeriodStart(now = /* @__PURE__ */ new Date()) {
36293
36711
  return `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}`;
@@ -36302,6 +36720,87 @@ async function chargedConnectionIds(userId, periodStart) {
36302
36720
  });
36303
36721
  return new Set(result.rows.map((row) => String(row.connection_id)));
36304
36722
  }
36723
+ async function inheritDisconnectedConnectionCharge(userId, connectionId, periodStart) {
36724
+ await ensureServiceConnectionsSchema();
36725
+ const normalizedUserId = Number(userId);
36726
+ const db = getDb();
36727
+ await db.batch([
36728
+ {
36729
+ sql: `
36730
+ INSERT OR IGNORE INTO connected_account_charge_transfers (
36731
+ user_id, period_start, from_connection_id, to_connection_id, provider_config_key
36732
+ )
36733
+ SELECT ?, ?, previous.id, replacement.id, replacement.provider_config_key
36734
+ FROM service_connections AS replacement
36735
+ JOIN service_connections AS previous
36736
+ ON previous.user_id = replacement.user_id
36737
+ AND previous.transport = 'nango'
36738
+ AND previous.provider_config_key = replacement.provider_config_key
36739
+ AND previous.lifecycle_status = 'disconnected'
36740
+ JOIN connected_account_charges AS charge
36741
+ ON charge.user_id = previous.user_id
36742
+ AND charge.connection_id = previous.id
36743
+ AND charge.period_start = ?
36744
+ WHERE replacement.id = ?
36745
+ AND replacement.user_id = ?
36746
+ AND replacement.transport = 'nango'
36747
+ AND replacement.lifecycle_status IN ('connected', 'needs_reauth')
36748
+ AND NOT EXISTS (
36749
+ SELECT 1 FROM connected_account_charges AS existing
36750
+ WHERE existing.user_id = replacement.user_id
36751
+ AND existing.connection_id = replacement.id
36752
+ AND existing.period_start = ?
36753
+ )
36754
+ ORDER BY previous.updated_at DESC, previous.id ASC
36755
+ LIMIT 1
36756
+ `,
36757
+ args: [normalizedUserId, periodStart, periodStart, connectionId, normalizedUserId, periodStart]
36758
+ },
36759
+ {
36760
+ sql: `
36761
+ UPDATE connected_account_charges
36762
+ SET connection_id = ?
36763
+ WHERE user_id = ?
36764
+ AND period_start = ?
36765
+ AND connection_id = (
36766
+ SELECT transfer.from_connection_id
36767
+ FROM connected_account_charge_transfers AS transfer
36768
+ JOIN connected_account_charges AS current_charge
36769
+ ON current_charge.user_id = transfer.user_id
36770
+ AND current_charge.period_start = transfer.period_start
36771
+ AND current_charge.connection_id = transfer.from_connection_id
36772
+ WHERE transfer.user_id = ?
36773
+ AND transfer.period_start = ?
36774
+ AND transfer.to_connection_id = ?
36775
+ ORDER BY transfer.transferred_at DESC, transfer.id DESC
36776
+ LIMIT 1
36777
+ )
36778
+ AND NOT EXISTS (
36779
+ SELECT 1 FROM connected_account_charges AS existing
36780
+ WHERE existing.user_id = ?
36781
+ AND existing.connection_id = ?
36782
+ AND existing.period_start = ?
36783
+ )
36784
+ `,
36785
+ args: [
36786
+ connectionId,
36787
+ normalizedUserId,
36788
+ periodStart,
36789
+ normalizedUserId,
36790
+ periodStart,
36791
+ connectionId,
36792
+ normalizedUserId,
36793
+ connectionId,
36794
+ periodStart
36795
+ ]
36796
+ }
36797
+ ], "write");
36798
+ const inherited = await db.execute({
36799
+ sql: "SELECT 1 FROM connected_account_charges WHERE user_id = ? AND connection_id = ? AND period_start = ? LIMIT 1",
36800
+ args: [normalizedUserId, connectionId, periodStart]
36801
+ });
36802
+ return inherited.rows.length > 0;
36803
+ }
36305
36804
  async function getConnectedAccountBillingState2(userId) {
36306
36805
  const result = await getDb().execute({
36307
36806
  sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
@@ -36430,6 +36929,7 @@ async function reconcileConnectedAccountBilling(user, connections) {
36430
36929
  const alreadyCharged = await chargedConnectionIds(user.id, periodStart);
36431
36930
  const unpaid = billableConnectionIds.filter((id) => !alreadyCharged.has(id));
36432
36931
  for (const connectionId of unpaid) {
36932
+ if (await inheritDisconnectedConnectionCharge(user.id, connectionId, periodStart)) continue;
36433
36933
  const feeMc = CONNECTED_ACTIVE_CONNECTION_MONTHLY_MC;
36434
36934
  const debit = await debitMcIdempotent(
36435
36935
  user.id,
@@ -36491,6 +36991,7 @@ var init_connected_account_billing = __esm({
36491
36991
  init_db();
36492
36992
  init_rates();
36493
36993
  init_connected_cost_telemetry();
36994
+ init_service_connections();
36494
36995
  DEFAULT_CONNECTED_ACCOUNT_PRICE_ID = "price_1TtYPAS8aAcsk3TGHOgrDZiQ";
36495
36996
  BILLABLE_LIFECYCLE = /* @__PURE__ */ new Set(["connected", "needs_reauth"]);
36496
36997
  ConnectedAccountBillingError = class extends Error {
@@ -40059,7 +40560,7 @@ async function applyAdjustment(input) {
40059
40560
  const credits = validateCredits(input.credits, input.confirmLarge === true);
40060
40561
  const reason = validateReason(input.reason);
40061
40562
  const actor = validateActor(input.actor);
40062
- const reference = input.reference?.trim() || `adj_${(0, import_node_crypto21.randomUUID)()}`;
40563
+ const reference = input.reference?.trim() || `adj_${(0, import_node_crypto22.randomUUID)()}`;
40063
40564
  if (reference.length > 120) throw new AdminCreditError(400, "reference must be at most 120 characters");
40064
40565
  const db = getDb();
40065
40566
  const existing = await db.execute({
@@ -40204,11 +40705,11 @@ async function lookupAccount(target, limit = 20) {
40204
40705
  }))
40205
40706
  };
40206
40707
  }
40207
- var import_node_crypto21, MIN_ADJUSTMENT_CREDITS, LARGE_ADJUSTMENT_CREDITS, MAX_ADJUSTMENT_CREDITS, MIN_REASON_LENGTH, MAX_REASON_LENGTH, MAX_ACTOR_LENGTH, AdminCreditError;
40708
+ var import_node_crypto22, MIN_ADJUSTMENT_CREDITS, LARGE_ADJUSTMENT_CREDITS, MAX_ADJUSTMENT_CREDITS, MIN_REASON_LENGTH, MAX_REASON_LENGTH, MAX_ACTOR_LENGTH, AdminCreditError;
40208
40709
  var init_admin_credits = __esm({
40209
40710
  "src/api/admin-credits.ts"() {
40210
40711
  "use strict";
40211
- import_node_crypto21 = require("crypto");
40712
+ import_node_crypto22 = require("crypto");
40212
40713
  init_db();
40213
40714
  init_rates();
40214
40715
  MIN_ADJUSTMENT_CREDITS = 1;
@@ -42173,7 +42674,7 @@ async function packageMapsMedia(args) {
42173
42674
  files.push({ path: "summary.json", content: Buffer.from(JSON.stringify(summary, null, 2)) });
42174
42675
  files.push({ path: "images.jsonl", content: Buffer.from(cleanImages.map((image) => JSON.stringify(image)).join("\n") + "\n") });
42175
42676
  const archive = await zipBuffer(files);
42176
- const id = (0, import_node_crypto22.randomBytes)(6).toString("hex");
42677
+ const id = (0, import_node_crypto23.randomBytes)(6).toString("hex");
42177
42678
  const pointer = await createPrivateArtifact({
42178
42679
  policy: policy3(),
42179
42680
  ownerId: args.ownerId,
@@ -42186,13 +42687,13 @@ async function packageMapsMedia(args) {
42186
42687
  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
42688
  return { media: args.media, artifact: { ...pointer, localPath } };
42188
42689
  }
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;
42690
+ 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
42691
  var init_maps_media_artifacts = __esm({
42191
42692
  "src/api/maps-media-artifacts.ts"() {
42192
42693
  "use strict";
42193
42694
  import_node_os9 = require("os");
42194
42695
  import_node_path13 = require("path");
42195
- import_node_crypto22 = require("crypto");
42696
+ import_node_crypto23 = require("crypto");
42196
42697
  import_yazl2 = require("yazl");
42197
42698
  import_p_limit5 = __toESM(require("p-limit"), 1);
42198
42699
  init_private_artifacts();
@@ -42816,7 +43317,7 @@ function retryDelaySeconds(attempts) {
42816
43317
  }
42817
43318
  async function dispatchPendingDirectoryWorkflows(limit = 25) {
42818
43319
  const rows = await claimDirectoryWorkflowOutbox({
42819
- workerId: `directory-dispatch-${process.pid}-${(0, import_node_crypto23.randomUUID)().slice(0, 8)}`,
43320
+ workerId: `directory-dispatch-${process.pid}-${(0, import_node_crypto24.randomUUID)().slice(0, 8)}`,
42820
43321
  limit
42821
43322
  });
42822
43323
  const result = { claimed: rows.length, dispatched: 0, failed: 0 };
@@ -42838,11 +43339,11 @@ async function dispatchPendingDirectoryWorkflows(limit = 25) {
42838
43339
  }
42839
43340
  return result;
42840
43341
  }
42841
- var import_node_crypto23;
43342
+ var import_node_crypto24;
42842
43343
  var init_directory_workflow_dispatch = __esm({
42843
43344
  "src/api/directory-workflow-dispatch.ts"() {
42844
43345
  "use strict";
42845
- import_node_crypto23 = require("crypto");
43346
+ import_node_crypto24 = require("crypto");
42846
43347
  init_client();
42847
43348
  init_directory_workflow_repository();
42848
43349
  }
@@ -42854,7 +43355,7 @@ function safeOptions(options) {
42854
43355
  return safe2;
42855
43356
  }
42856
43357
  function requestFingerprint(options) {
42857
- return (0, import_node_crypto24.createHash)("sha256").update(JSON.stringify(safeOptions(options))).digest("hex");
43358
+ return (0, import_node_crypto25.createHash)("sha256").update(JSON.stringify(safeOptions(options))).digest("hex");
42858
43359
  }
42859
43360
  function idempotencyKey(raw) {
42860
43361
  if (raw !== void 0) {
@@ -42863,14 +43364,14 @@ function idempotencyKey(raw) {
42863
43364
  if (trimmed.length > 500) return { ok: false, message: "Idempotency-Key must be 500 characters or fewer." };
42864
43365
  return { ok: true, key: trimmed };
42865
43366
  }
42866
- return { ok: true, key: `directory-${(0, import_node_crypto24.randomUUID)()}` };
43367
+ return { ok: true, key: `directory-${(0, import_node_crypto25.randomUUID)()}` };
42867
43368
  }
42868
43369
  function debitKeyFor(userId, responseKey) {
42869
- const digest2 = (0, import_node_crypto24.createHash)("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
43370
+ const digest2 = (0, import_node_crypto25.createHash)("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
42870
43371
  return `directory-workflow:${userId}:${digest2}`;
42871
43372
  }
42872
43373
  function jobId() {
42873
- return `dir_${(0, import_node_crypto24.randomUUID)().replace(/-/g, "")}`;
43374
+ return `dir_${(0, import_node_crypto25.randomUUID)().replace(/-/g, "")}`;
42874
43375
  }
42875
43376
  function publicStatus(job, result) {
42876
43377
  if (job.status === "failed") return "failed";
@@ -42979,7 +43480,7 @@ async function runSynchronously(c, user, options, plan, responseKey) {
42979
43480
  const csv = renderDirectoryWorkflowCsv(result);
42980
43481
  const artifact = await createDirectoryCsvArtifact({
42981
43482
  ownerId: String(user.id),
42982
- jobId: (0, import_node_crypto24.createHash)("sha256").update(debitKey2).digest("hex").slice(0, 32),
43483
+ jobId: (0, import_node_crypto25.createHash)("sha256").update(debitKey2).digest("hex").slice(0, 32),
42983
43484
  createdAt: result.extractedAt,
42984
43485
  filename: `${options.state}-${options.query}-directory.csv`,
42985
43486
  csv,
@@ -43035,11 +43536,11 @@ async function runSynchronously(c, user, options, plan, responseKey) {
43035
43536
  await releaseConcurrencyGate(gate.lockId);
43036
43537
  }
43037
43538
  }
43038
- var import_node_crypto24, import_hono16, directoryApp;
43539
+ var import_node_crypto25, import_hono16, directoryApp;
43039
43540
  var init_directory_routes = __esm({
43040
43541
  "src/api/directory-routes.ts"() {
43041
43542
  "use strict";
43042
- import_node_crypto24 = require("crypto");
43543
+ import_node_crypto25 = require("crypto");
43043
43544
  import_hono16 = require("hono");
43044
43545
  init_api_auth();
43045
43546
  init_db();
@@ -43257,15 +43758,15 @@ function bounded(value, field, min, max) {
43257
43758
  return normalized;
43258
43759
  }
43259
43760
  function newLeadListUploadId() {
43260
- return `upl_${(0, import_node_crypto25.randomUUID)().replace(/-/g, "")}`;
43761
+ return `upl_${(0, import_node_crypto26.randomUUID)().replace(/-/g, "")}`;
43261
43762
  }
43262
43763
  function newImportedLeadListId() {
43263
- return `lst_${(0, import_node_crypto25.randomUUID)().replace(/-/g, "")}`;
43764
+ return `lst_${(0, import_node_crypto26.randomUUID)().replace(/-/g, "")}`;
43264
43765
  }
43265
43766
  async function ensureLeadListImportRepositorySchema() {
43266
43767
  const db = getDb();
43267
- if (schemaDb5 !== db) {
43268
- schemaDb5 = db;
43768
+ if (schemaDb6 !== db) {
43769
+ schemaDb6 = db;
43269
43770
  schemaPromise5 = null;
43270
43771
  }
43271
43772
  if (!schemaPromise5) {
@@ -43460,13 +43961,13 @@ async function deleteExpiredLeadListInputRecords(now = /* @__PURE__ */ new Date(
43460
43961
  ], "write");
43461
43962
  return { uploads: Number(uploads.rowsAffected ?? 0), lists: Number(lists.rowsAffected ?? 0) };
43462
43963
  }
43463
- var import_node_crypto25, schemaDb5, schemaPromise5;
43964
+ var import_node_crypto26, schemaDb6, schemaPromise5;
43464
43965
  var init_lead_list_import_repository = __esm({
43465
43966
  "src/api/lead-list-import-repository.ts"() {
43466
43967
  "use strict";
43467
- import_node_crypto25 = require("crypto");
43968
+ import_node_crypto26 = require("crypto");
43468
43969
  init_db();
43469
- schemaDb5 = null;
43970
+ schemaDb6 = null;
43470
43971
  schemaPromise5 = null;
43471
43972
  }
43472
43973
  });
@@ -43506,7 +44007,7 @@ function safeFilenameHint(value) {
43506
44007
  return cleaned || null;
43507
44008
  }
43508
44009
  function requestFingerprint2(filenameHint) {
43509
- return (0, import_node_crypto26.createHash)("sha256").update(JSON.stringify({ filenameHint })).digest("hex");
44010
+ return (0, import_node_crypto27.createHash)("sha256").update(JSON.stringify({ filenameHint })).digest("hex");
43510
44011
  }
43511
44012
  function publicBaseUrl() {
43512
44013
  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 +44163,7 @@ async function inspectLeadListUpload(uploadId, ownerId2) {
43662
44163
  maxBytes: LEAD_LIST_UPLOAD_MAX_BYTES
43663
44164
  });
43664
44165
  if (!buffer) return null;
43665
- const sha2565 = (0, import_node_crypto26.createHash)("sha256").update(buffer).digest("hex");
44166
+ const sha2565 = (0, import_node_crypto27.createHash)("sha256").update(buffer).digest("hex");
43666
44167
  const completed = await completeLeadListUpload({
43667
44168
  id: record.id,
43668
44169
  ownerId: record.ownerId,
@@ -43786,11 +44287,11 @@ async function cleanupExpiredLeadListInputs(args = {}) {
43786
44287
  const records = await deleteExpiredLeadListInputRecords(now);
43787
44288
  return { deletedBlobs, deletedUploadRecords: records.uploads, deletedListRecords: records.lists };
43788
44289
  }
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;
44290
+ 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
44291
  var init_lead_list_input_artifacts = __esm({
43791
44292
  "src/api/lead-list-input-artifacts.ts"() {
43792
44293
  "use strict";
43793
- import_node_crypto26 = require("crypto");
44294
+ import_node_crypto27 = require("crypto");
43794
44295
  import_promises11 = require("fs/promises");
43795
44296
  import_node_os10 = require("os");
43796
44297
  import_node_path14 = require("path");
@@ -44173,7 +44674,7 @@ function canonicalize(value) {
44173
44674
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalize(object[key])}`).join(",")}}`;
44174
44675
  }
44175
44676
  function fingerprint(value) {
44176
- return (0, import_node_crypto27.createHash)("sha256").update(canonicalize(value)).digest("hex");
44677
+ return (0, import_node_crypto28.createHash)("sha256").update(canonicalize(value)).digest("hex");
44177
44678
  }
44178
44679
  async function safeJson(c) {
44179
44680
  try {
@@ -44250,11 +44751,11 @@ function textDelimiter(raw, text2, observedMime) {
44250
44751
  if (observedMime === "text/tab-separated-values") return " ";
44251
44752
  return detectLeadListTextDelimiter(text2);
44252
44753
  }
44253
- var import_node_crypto27, import_hono17, leadListInputApp;
44754
+ var import_node_crypto28, import_hono17, leadListInputApp;
44254
44755
  var init_lead_list_input_routes = __esm({
44255
44756
  "src/api/lead-list-input-routes.ts"() {
44256
44757
  "use strict";
44257
- import_node_crypto27 = require("crypto");
44758
+ import_node_crypto28 = require("crypto");
44258
44759
  import_hono17 = require("hono");
44259
44760
  init_api_auth();
44260
44761
  init_lead_list_input_artifacts();
@@ -44446,7 +44947,7 @@ function retryDelaySeconds2(attempts) {
44446
44947
  return Math.min(3600, Math.max(15, 15 * 2 ** Math.min(8, Math.max(0, attempts - 1))));
44447
44948
  }
44448
44949
  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 });
44950
+ const rows = await claimLeadListEnrichmentOutbox({ workerId: `lead-list-dispatch-${process.pid}-${(0, import_node_crypto29.randomUUID)().slice(0, 8)}`, limit });
44450
44951
  const result = { claimed: rows.length, dispatched: 0, failed: 0 };
44451
44952
  for (const row of rows) {
44452
44953
  if (!row.claimToken) continue;
@@ -44465,11 +44966,11 @@ async function dispatchPendingLeadListEnrichments(limit = 25) {
44465
44966
  }
44466
44967
  return result;
44467
44968
  }
44468
- var import_node_crypto28;
44969
+ var import_node_crypto29;
44469
44970
  var init_lead_list_enrichment_dispatch = __esm({
44470
44971
  "src/api/lead-list-enrichment-dispatch.ts"() {
44471
44972
  "use strict";
44472
- import_node_crypto28 = require("crypto");
44973
+ import_node_crypto29 = require("crypto");
44473
44974
  init_client();
44474
44975
  init_lead_list_enrichment_repository();
44475
44976
  }
@@ -44485,15 +44986,15 @@ function idempotencyKey2(raw) {
44485
44986
  return { ok: true, value };
44486
44987
  }
44487
44988
  function newJobId() {
44488
- return `lle_${(0, import_node_crypto29.randomUUID)().replace(/-/g, "")}`;
44989
+ return `lle_${(0, import_node_crypto30.randomUUID)().replace(/-/g, "")}`;
44489
44990
  }
44490
44991
  function debitKeyFor2(userId, key) {
44491
- const digest2 = (0, import_node_crypto29.createHash)("sha256").update(String(userId)).update("\0").update(key).digest("hex");
44992
+ const digest2 = (0, import_node_crypto30.createHash)("sha256").update(String(userId)).update("\0").update(key).digest("hex");
44492
44993
  return `lead-list-enrichment:${userId}:${digest2}`;
44493
44994
  }
44494
44995
  function inlineSourceDigest(headers, rows) {
44495
44996
  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");
44997
+ return (0, import_node_crypto30.createHash)("sha256").update(stableCanonicalJson({ headers, values })).digest("hex");
44497
44998
  }
44498
44999
  function issueMessage(error) {
44499
45000
  const issue = error.issues[0];
@@ -44691,11 +45192,11 @@ async function resolveInput(ownerId2, parsed) {
44691
45192
  sourceDigest: sourceDigest ?? inlineSourceDigest(normalized.headers, rows)
44692
45193
  };
44693
45194
  }
44694
- var import_node_crypto29, import_hono18, import_zod31, SourceSchema, StartSchema, TERMINAL, leadListEnrichmentApp;
45195
+ var import_node_crypto30, import_hono18, import_zod31, SourceSchema, StartSchema, TERMINAL, leadListEnrichmentApp;
44695
45196
  var init_lead_list_enrichment_routes = __esm({
44696
45197
  "src/api/lead-list-enrichment-routes.ts"() {
44697
45198
  "use strict";
44698
- import_node_crypto29 = require("crypto");
45199
+ import_node_crypto30 = require("crypto");
44699
45200
  import_hono18 = require("hono");
44700
45201
  import_zod31 = require("zod");
44701
45202
  init_api_auth();
@@ -48162,7 +48663,7 @@ async function readManifestFromSummary(summary) {
48162
48663
  function webhookSignature(body, timestamp2) {
48163
48664
  const secret2 = process.env.MCP_SCRAPER_WEBHOOK_SECRET?.trim();
48164
48665
  if (!secret2) return null;
48165
- return (0, import_node_crypto30.createHmac)("sha256", secret2).update(`${timestamp2}.${body}`).digest("hex");
48666
+ return (0, import_node_crypto31.createHmac)("sha256", secret2).update(`${timestamp2}.${body}`).digest("hex");
48166
48667
  }
48167
48668
  async function deliverWorkflowWebhook(input) {
48168
48669
  if (!input.webhookUrl) return;
@@ -48369,11 +48870,11 @@ async function dispatchDueWorkflowSchedules(apiUrl, limit = 3) {
48369
48870
  }
48370
48871
  return { dispatched: results.length, results };
48371
48872
  }
48372
- var import_node_crypto30, import_promises14, import_hono21, import_zod41, workflowApp, WorkflowInputSchema, WorkflowIdSchema, CadenceSchema, ScheduleStatusSchema, RunBodySchema, ScheduleCreateSchema, SchedulePatchSchema, TERMINAL_RUN_STATUSES;
48873
+ var import_node_crypto31, import_promises14, import_hono21, import_zod41, workflowApp, WorkflowInputSchema, WorkflowIdSchema, CadenceSchema, ScheduleStatusSchema, RunBodySchema, ScheduleCreateSchema, SchedulePatchSchema, TERMINAL_RUN_STATUSES;
48373
48874
  var init_workflow_routes = __esm({
48374
48875
  "src/api/workflow-routes.ts"() {
48375
48876
  "use strict";
48376
- import_node_crypto30 = require("crypto");
48877
+ import_node_crypto31 = require("crypto");
48377
48878
  import_promises14 = require("fs/promises");
48378
48879
  import_hono21 = require("hono");
48379
48880
  import_zod41 = require("zod");
@@ -48654,7 +49155,7 @@ var init_workflow_routes = __esm({
48654
49155
  // src/serp-intelligence/page-snapshot-extractor.ts
48655
49156
  function sha2562(value) {
48656
49157
  if (!value) return null;
48657
- return (0, import_node_crypto31.createHash)("sha256").update(value).digest("hex");
49158
+ return (0, import_node_crypto32.createHash)("sha256").update(value).digest("hex");
48658
49159
  }
48659
49160
  function countWords(markdown) {
48660
49161
  const matches = markdown.trim().match(/\b[\p{L}\p{N}][\p{L}\p{N}'-]*\b/gu);
@@ -48954,11 +49455,11 @@ async function capturePageSnapshots(targets, options = {}) {
48954
49455
  }
48955
49456
  };
48956
49457
  }
48957
- var import_node_crypto31, import_p_limit6, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_CONCURRENCY, DEFAULT_MAX_CONTENT_CHARS;
49458
+ var import_node_crypto32, import_p_limit6, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_CONCURRENCY, DEFAULT_MAX_CONTENT_CHARS;
48958
49459
  var init_page_snapshot_extractor = __esm({
48959
49460
  "src/serp-intelligence/page-snapshot-extractor.ts"() {
48960
49461
  "use strict";
48961
- import_node_crypto31 = require("crypto");
49462
+ import_node_crypto32 = require("crypto");
48962
49463
  import_p_limit6 = __toESM(require("p-limit"), 1);
48963
49464
  init_kpo_extractor();
48964
49465
  init_url_utils();
@@ -49455,21 +49956,21 @@ async function logRequestEventBestEffort(input) {
49455
49956
  }
49456
49957
  }
49457
49958
  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");
49959
+ const responseKey = suppliedKey?.trim() || (0, import_node_crypto33.randomUUID)();
49960
+ const requestFingerprint3 = (0, import_node_crypto33.createHash)("sha256").update(JSON.stringify(body)).digest("hex");
49961
+ const keyDigest = (0, import_node_crypto33.createHash)("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
49461
49962
  return {
49462
49963
  responseKey,
49463
49964
  debitKey: `serp-capture:${userId}:${keyDigest}`,
49464
49965
  debitDescription: `${body.query} [request:${requestFingerprint3}]`
49465
49966
  };
49466
49967
  }
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;
49968
+ 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
49969
  var init_serp_intelligence_routes = __esm({
49469
49970
  "src/api/serp-intelligence-routes.ts"() {
49470
49971
  "use strict";
49471
49972
  import_hono22 = require("hono");
49472
- import_node_crypto32 = require("crypto");
49973
+ import_node_crypto33 = require("crypto");
49473
49974
  init_browser_service_env();
49474
49975
  init_page_snapshot_extractor();
49475
49976
  init_serp_capture_service();
@@ -49706,7 +50207,7 @@ var PACKAGE_VERSION;
49706
50207
  var init_version = __esm({
49707
50208
  "src/version.ts"() {
49708
50209
  "use strict";
49709
- PACKAGE_VERSION = "0.66.6";
50210
+ PACKAGE_VERSION = "0.66.7";
49710
50211
  }
49711
50212
  });
49712
50213
 
@@ -49775,11 +50276,20 @@ seam is noted so you can chain them.
49775
50276
  - Plan a new or edited entity -> **commons_prepare_entity**. This is the Commons equivalent of
49776
50277
  prepare-memory-write: it returns the live entity profile contract, duplicate candidates, tag reuse/review/create
49777
50278
  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.
50279
+ - Host a featured image -> **commons_host_image**. The normal write sequence is **commons_prepare_entity ->
50280
+ compose the payload -> commons_host_image -> commons_validate_entity -> commons_submit_entity**. Put the
50281
+ permanent returned URL in featuredImage.url. Use imageBase64 for available attachment bytes within the request
50282
+ bound, or a stable direct public HTTPS image URL; never use a caller-local path, opaque chat attachment reference,
50283
+ HTML page, private URL, or temporary/signed URL.
50284
+ - Validate a composed entity without writing -> **commons_validate_entity**. Use it after image hosting and before
50285
+ submit. Validation does not fetch or store image bytes; an external/unregistered featured image is reported in
50286
+ imageDiagnostics as not publishable until commons_host_image returns its permanent Commons URL. Validation also
50287
+ catches missing source/body evidence, duplicate candidates, and unsupported placeholder sections.
49781
50288
  - 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
50289
+ not direct rendered HTML editing. Submit retains defensive auto-hosting for a stable direct public HTTPS image,
50290
+ but image ingress failure returns an actionable tool error: host different bytes or a different stable source,
50291
+ replace featuredImage.url, validate again, and retry the same intended write with the same idempotency key.
50292
+ Pick the closest entity type
49783
50293
  first: SoftwareApplication, Organization, Person, Event, Place, Taxon, ScienceConcept, MathConcept, TechArticle,
49784
50294
  Trail, or PublicArticle.
49785
50295
  Creating a new entity costs 10 Credits; editing an existing entity costs 2 Credits. Preparation, validation,
@@ -51521,7 +52031,7 @@ var init_contracts = __esm({
51521
52031
  });
51522
52032
 
51523
52033
  // 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;
52034
+ 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, 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
52035
  var init_mcp_tool_schemas = __esm({
51526
52036
  "src/mcp/mcp-tool-schemas.ts"() {
51527
52037
  "use strict";
@@ -51934,7 +52444,7 @@ var init_mcp_tool_schemas = __esm({
51934
52444
  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
52445
  };
51936
52446
  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."),
52447
+ url: import_zod45.z.string().url().describe("For a publish-ready entity, use the permanent URL returned by commons_host_image. Submit can auto-host a stable direct public HTTPS image URL as a compatibility fallback, but validation reports any unregistered external URL as not ready. Do not use a chat attachment reference, caller-local path, temporary or signed URL, private URL, or HTML page URL."),
51938
52448
  alt: import_zod45.z.string().trim().max(500).optional(),
51939
52449
  caption: import_zod45.z.string().trim().max(1e3).optional(),
51940
52450
  sourceUrl: import_zod45.z.string().url().optional(),
@@ -52026,14 +52536,14 @@ var init_mcp_tool_schemas = __esm({
52026
52536
  maxCandidates: import_zod45.z.number().int().min(1).max(20).default(8).describe("Maximum duplicate candidates to return.")
52027
52537
  };
52028
52538
  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."),
52539
+ 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
52540
  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
52541
  description: import_zod45.z.string().trim().min(8).max(600).describe("Short article summary and schema.org description."),
52032
52542
  slug: import_zod45.z.string().trim().max(180).optional().describe("Optional URL slug. Omit to derive one from title."),
52033
52543
  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
52544
  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
52545
  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."),
52546
+ featuredImage: CommonsFeaturedImageInputSchema.optional().describe("Required for auto-published public entities. First call commons_host_image and place its returned permanent URL here; validation does not fetch external images and reports unregistered URLs as not publishable. Submit retains a compatibility fallback that auto-hosts a stable direct public HTTPS image URL. The image is also added to the media manifest if absent."),
52037
52547
  source: CommonsSourceInputSchema.optional().describe("Source provenance. Store original URL, source byline, and origin canonical here; canonical does not replace rights review."),
52038
52548
  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
52549
  keywords: import_zod45.z.array(import_zod45.z.string().trim().min(1).max(120)).max(50).optional().describe("SEO and retrieval keywords."),
@@ -52095,8 +52605,8 @@ var init_mcp_tool_schemas = __esm({
52095
52605
  idOrSlug: import_zod45.z.string().trim().min(1).max(180).describe("Published entity id or /wiki/ slug whose contribution ledger should be read.")
52096
52606
  };
52097
52607
  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."),
52608
+ sourceUrl: import_zod45.z.string().url().optional().describe("Stable direct public HTTPS URL whose response is the image bytes to host. Do not pass an HTML webpage, chat attachment reference, caller-local path, temporary or signed URL, or private/authenticated URL."),
52609
+ imageBase64: import_zod45.z.string().min(16).optional().describe("Base64 image bytes for an image you already hold, including forwarded chat attachment bytes. Prefer this over a temporary attachment URL when the serialized request remains under about 3 MB; larger files require a stable direct public HTTPS source URL."),
52100
52610
  alt: import_zod45.z.string().trim().max(500).optional().describe("Alt text describing the image for readers who cannot see it."),
52101
52611
  license: import_zod45.z.string().trim().max(240).optional().describe("License or usage terms of the original image, preserved with the hosted copy."),
52102
52612
  attribution: import_zod45.z.string().trim().max(500).optional().describe("Credit line for the original photographer, publication, or archive.")
@@ -52128,6 +52638,30 @@ var init_mcp_tool_schemas = __esm({
52128
52638
  error: import_zod45.z.string().optional(),
52129
52639
  message: import_zod45.z.string().optional()
52130
52640
  };
52641
+ CommonsFeaturedImageDiagnosticsOutputSchema = import_zod45.z.object({
52642
+ state: import_zod45.z.enum(["missing", "external_unhosted", "commons_hosted"]).describe("Storage-readiness state for the candidate featured image."),
52643
+ url: import_zod45.z.string().url().nullable().describe("Normalized candidate featured-image URL, or null when none was supplied."),
52644
+ commonsImageId: import_zod45.z.string().nullable().describe("Registered Commons image ID when metadata proves the image is hosted, otherwise null."),
52645
+ readyForPublish: import_zod45.z.boolean().describe("Whether Commons storage metadata proves this featured image is ready to publish."),
52646
+ submitWillAttemptAutoHost: import_zod45.z.boolean().describe("Whether submit will use its compatibility fallback to try hosting this external image."),
52647
+ requiredAction: import_zod45.z.string().nullable().describe("Action the caller must take before submit, or null when the image is ready.")
52648
+ }).describe("Featured-image storage readiness determined without fetching or storing external bytes.");
52649
+ CommonsValidateEntityOutputSchema = {
52650
+ ok: import_zod45.z.boolean(),
52651
+ data: import_zod45.z.object({
52652
+ ok: import_zod45.z.literal(true),
52653
+ valid: import_zod45.z.boolean(),
52654
+ publishable: import_zod45.z.boolean(),
52655
+ autoPublishEligible: import_zod45.z.boolean(),
52656
+ errors: import_zod45.z.array(import_zod45.z.string()),
52657
+ warnings: import_zod45.z.array(import_zod45.z.string()),
52658
+ imageDiagnostics: import_zod45.z.object({
52659
+ featuredImage: CommonsFeaturedImageDiagnosticsOutputSchema
52660
+ }).describe("Read-only image readiness and recovery guidance for this payload.")
52661
+ }).passthrough().optional(),
52662
+ error: import_zod45.z.string().optional(),
52663
+ message: import_zod45.z.string().optional()
52664
+ };
52131
52665
  DirectoryWorkflowStatusInputSchema = {
52132
52666
  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
52667
  };
@@ -54839,11 +55373,11 @@ function requireTasksCapability(capabilityValue) {
54839
55373
  );
54840
55374
  }
54841
55375
  function taskKey(secret2) {
54842
- return (0, import_node_crypto33.createHash)("sha256").update("mcp-scraper-task-handle\0", "utf8").update(secret2, "utf8").digest();
55376
+ return (0, import_node_crypto34.createHash)("sha256").update("mcp-scraper-task-handle\0", "utf8").update(secret2, "utf8").digest();
54843
55377
  }
54844
55378
  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);
55379
+ const nonce = (0, import_node_crypto34.randomBytes)(12);
55380
+ const cipher = (0, import_node_crypto34.createCipheriv)("aes-256-gcm", taskKey(secret2), nonce);
54847
55381
  const ciphertext = Buffer.concat([
54848
55382
  cipher.update(JSON.stringify(payload), "utf8"),
54849
55383
  cipher.final()
@@ -54860,7 +55394,7 @@ function decodeTaskHandle(taskId, secret2, ownerId2) {
54860
55394
  const nonce = bytes.subarray(0, 12);
54861
55395
  const tag = bytes.subarray(bytes.length - 16);
54862
55396
  const ciphertext = bytes.subarray(12, bytes.length - 16);
54863
- const decipher = (0, import_node_crypto33.createDecipheriv)("aes-256-gcm", taskKey(secret2), nonce);
55397
+ const decipher = (0, import_node_crypto34.createDecipheriv)("aes-256-gcm", taskKey(secret2), nonce);
54864
55398
  decipher.setAuthTag(tag);
54865
55399
  const parsed = JSON.parse(Buffer.concat([
54866
55400
  decipher.update(ciphertext),
@@ -55224,12 +55758,12 @@ async function handleMcpTasksHttpRequest(request, executor, options) {
55224
55758
  return taskHttpError(id, error instanceof import_server.ProtocolError ? error : new import_server.ProtocolError(-32603, error instanceof Error ? error.message : "Internal error"));
55225
55759
  }
55226
55760
  }
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;
55761
+ 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
55762
  var init_mcp_tasks_extension = __esm({
55229
55763
  "src/mcp/mcp-tasks-extension.ts"() {
55230
55764
  "use strict";
55231
55765
  import_server = require("@modelcontextprotocol/server");
55232
- import_node_crypto33 = require("crypto");
55766
+ import_node_crypto34 = require("crypto");
55233
55767
  import_zod46 = require("zod");
55234
55768
  init_mcp_tool_schemas();
55235
55769
  MCP_TASKS_EXTENSION_ID = "io.modelcontextprotocol/tasks";
@@ -55537,7 +56071,7 @@ var init_analytics_mcp_tools = __esm({
55537
56071
 
55538
56072
  // src/mcp/paa-mcp-server.ts
55539
56073
  function hashOwnerId(callerKey) {
55540
- return (0, import_node_crypto34.createHash)("sha256").update(callerKey).digest("hex").slice(0, 24);
56074
+ return (0, import_node_crypto35.createHash)("sha256").update(callerKey).digest("hex").slice(0, 24);
55541
56075
  }
55542
56076
  function liveWebToolAnnotations(title) {
55543
56077
  return {
@@ -56024,9 +56558,9 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56024
56558
  }, async (input) => executor.commonsPrepareEntity(input));
56025
56559
  server.registerTool("commons_validate_entity", {
56026
56560
  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.",
56561
+ 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
56562
  inputSchema: CommonsValidateEntityInputSchema,
56029
- outputSchema: recordOutputSchema("commons_validate_entity", CommonsGenericOutputSchema),
56563
+ outputSchema: recordOutputSchema("commons_validate_entity", CommonsValidateEntityOutputSchema),
56030
56564
  annotations: {
56031
56565
  title: "Transparent Commons Validate Entity",
56032
56566
  readOnlyHint: true,
@@ -56037,7 +56571,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56037
56571
  }, async (input) => executor.commonsValidateEntity(input));
56038
56572
  server.registerTool("commons_submit_entity", {
56039
56573
  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.",
56574
+ 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
56575
  inputSchema: CommonsSubmitEntityInputSchema,
56042
56576
  outputSchema: recordOutputSchema("commons_submit_entity", CommonsGenericOutputSchema),
56043
56577
  annotations: {
@@ -56063,7 +56597,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56063
56597
  }, async (input) => executor.commonsGetEntityLedger(input));
56064
56598
  server.registerTool("commons_host_image", {
56065
56599
  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.",
56600
+ description: "Normal prerequisite for any featured image whose URL was not already returned by commons_host_image. Host image bytes from imageBase64 when you already have an attachment and the serialized request stays under about 3 MB, or from a stable direct public HTTPS image URL; do not pass a webpage, caller-local path, private URL, or temporary/signed attachment URL. Returns the permanent Commons URL to place in featuredImage.url before commons_validate_entity and commons_submit_entity. Submit can still auto-host a stable public source as a compatibility fallback. JPEG, PNG, GIF, and WebP are accepted up to 10 MB; SVG is rejected. Identical bytes are stored once.",
56067
56601
  inputSchema: CommonsHostImageInputSchema,
56068
56602
  outputSchema: recordOutputSchema("commons_host_image", CommonsGenericOutputSchema),
56069
56603
  annotations: {
@@ -56525,7 +57059,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56525
57059
  annotations: { title: "Set Scheduled Action Connections", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
56526
57060
  }, async (input) => executor.setScheduledActionConnections(input));
56527
57061
  }
56528
- var import_server2, import_zod48, import_node_fs11, import_node_path17, import_node_crypto34, ACTION_CONFIRMATION_SCHEMA;
57062
+ var import_server2, import_zod48, import_node_fs11, import_node_path17, import_node_crypto35, ACTION_CONFIRMATION_SCHEMA;
56529
57063
  var init_paa_mcp_server = __esm({
56530
57064
  "src/mcp/paa-mcp-server.ts"() {
56531
57065
  "use strict";
@@ -56533,7 +57067,7 @@ var init_paa_mcp_server = __esm({
56533
57067
  import_zod48 = require("zod");
56534
57068
  import_node_fs11 = require("fs");
56535
57069
  import_node_path17 = require("path");
56536
- import_node_crypto34 = require("crypto");
57070
+ import_node_crypto35 = require("crypto");
56537
57071
  init_version();
56538
57072
  init_rates();
56539
57073
  init_mcp_response_formatter();
@@ -56655,11 +57189,11 @@ function analyticsReportPath(input, report) {
56655
57189
  const suffix2 = query.size ? `?${query.toString()}` : "";
56656
57190
  return `/analytics/sites/${encodeURIComponent(input.siteId)}/${report}${suffix2}`;
56657
57191
  }
56658
- var import_node_crypto35, HttpMcpToolExecutor;
57192
+ var import_node_crypto36, HttpMcpToolExecutor;
56659
57193
  var init_http_mcp_tool_executor = __esm({
56660
57194
  "src/mcp/http-mcp-tool-executor.ts"() {
56661
57195
  "use strict";
56662
- import_node_crypto35 = require("crypto");
57196
+ import_node_crypto36 = require("crypto");
56663
57197
  init_harvest_timeout();
56664
57198
  init_browser_service_env();
56665
57199
  init_errors();
@@ -56728,13 +57262,13 @@ var init_http_mcp_tool_executor = __esm({
56728
57262
  });
56729
57263
  }
56730
57264
  async callDirectoryWorkflowStart(body, explicitIdempotencyKey) {
56731
- const idempotencyKey4 = `mcp-directory-${(0, import_node_crypto35.createHash)("sha256").update(explicitIdempotencyKey).digest("hex")}`;
57265
+ const idempotencyKey4 = `mcp-directory-${(0, import_node_crypto36.createHash)("sha256").update(explicitIdempotencyKey).digest("hex")}`;
56732
57266
  return this.call("/directory/run", body, this.timeoutMs, "POST", {
56733
57267
  "Idempotency-Key": idempotencyKey4
56734
57268
  });
56735
57269
  }
56736
57270
  async callSiteExtractStart(toolName, body, explicitIdempotencyKey) {
56737
- const idempotencyKey4 = `mcp-site-${(0, import_node_crypto35.createHash)("sha256").update(toolName).update("\0").update(explicitIdempotencyKey).digest("hex")}`;
57271
+ const idempotencyKey4 = `mcp-site-${(0, import_node_crypto36.createHash)("sha256").update(toolName).update("\0").update(explicitIdempotencyKey).digest("hex")}`;
56738
57272
  return this.call("/extract-site", body, this.timeoutMs, "POST", {
56739
57273
  "Idempotency-Key": idempotencyKey4
56740
57274
  });
@@ -57178,7 +57712,7 @@ var init_http_mcp_tool_executor = __esm({
57178
57712
  report: input.report,
57179
57713
  format: input.format
57180
57714
  }, this.timeoutMs, "POST", {
57181
- "Idempotency-Key": `analytics-export-${(0, import_node_crypto35.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57715
+ "Idempotency-Key": `analytics-export-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57182
57716
  });
57183
57717
  }
57184
57718
  commonsSearchEntities(input) {
@@ -57217,7 +57751,7 @@ var init_http_mcp_tool_executor = __esm({
57217
57751
  commonsSubmitEntity(input) {
57218
57752
  const { idempotencyKey: idempotencyKey4, ...body } = input;
57219
57753
  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")}`
57754
+ "Idempotency-Key": `commons-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57221
57755
  });
57222
57756
  }
57223
57757
  commonsGetEntityLedger(input) {
@@ -57232,7 +57766,7 @@ var init_http_mcp_tool_executor = __esm({
57232
57766
  commonsUpdateEditorialArticle(input) {
57233
57767
  const { idempotencyKey: idempotencyKey4, ...body } = input;
57234
57768
  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")}`
57769
+ "Idempotency-Key": `commons-article-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57236
57770
  });
57237
57771
  }
57238
57772
  commonsSaveFilter(input) {
@@ -57253,13 +57787,13 @@ var init_http_mcp_tool_executor = __esm({
57253
57787
  commonsClaimPublication(input) {
57254
57788
  const { idempotencyKey: idempotencyKey4, ...body } = input;
57255
57789
  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")}`
57790
+ "Idempotency-Key": `commons-publication-claim-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57257
57791
  });
57258
57792
  }
57259
57793
  commonsPublishEditorial(input) {
57260
57794
  const { idempotencyKey: idempotencyKey4, ...body } = input;
57261
57795
  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")}`
57796
+ "Idempotency-Key": `commons-publication-publish-${(0, import_node_crypto36.createHash)("sha256").update(idempotencyKey4).digest("hex")}`
57263
57797
  });
57264
57798
  }
57265
57799
  commonsGetPublication(input) {
@@ -57267,13 +57801,13 @@ var init_http_mcp_tool_executor = __esm({
57267
57801
  return this.getJson(input.subdomain ? `/commons/publications/${encodeURIComponent(input.subdomain)}?${query}` : `/commons/publications/me?${query}`);
57268
57802
  }
57269
57803
  async captureSerpSnapshot(input) {
57270
- const fingerprint2 = (0, import_node_crypto35.createHash)("sha256").update(JSON.stringify(input)).digest("hex");
57804
+ const fingerprint2 = (0, import_node_crypto36.createHash)("sha256").update(JSON.stringify(input)).digest("hex");
57271
57805
  const now = Date.now();
57272
57806
  for (const [pendingFingerprint, pendingEntry] of this.pendingSerpCaptureBillingKeys) {
57273
57807
  if (pendingEntry.expiresAt <= now) this.pendingSerpCaptureBillingKeys.delete(pendingFingerprint);
57274
57808
  }
57275
57809
  const pending = this.pendingSerpCaptureBillingKeys.get(fingerprint2);
57276
- const idempotencyKey4 = pending && pending.expiresAt > now ? pending.key : (0, import_node_crypto35.randomUUID)();
57810
+ const idempotencyKey4 = pending && pending.expiresAt > now ? pending.key : (0, import_node_crypto36.randomUUID)();
57277
57811
  this.pendingSerpCaptureBillingKeys.set(fingerprint2, {
57278
57812
  key: idempotencyKey4,
57279
57813
  expiresAt: now + 15 * 6e4
@@ -63283,7 +63817,7 @@ async function createImageSourceArtifact(args) {
63283
63817
  if (args.content.length === 0 || args.content.length > IMAGE_SOURCE_MAX_BYTES) {
63284
63818
  throw new Error("image_source_size_invalid");
63285
63819
  }
63286
- const id = (0, import_node_crypto36.randomUUID)().replaceAll("-", "");
63820
+ const id = (0, import_node_crypto37.randomUUID)().replaceAll("-", "");
63287
63821
  return createPrivateArtifact({
63288
63822
  policy: policy4(),
63289
63823
  ownerId: args.ownerId,
@@ -63302,11 +63836,11 @@ async function readOwnedImageSourceArtifact(args) {
63302
63836
  maxBytes: IMAGE_SOURCE_MAX_BYTES
63303
63837
  });
63304
63838
  }
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;
63839
+ 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
63840
  var init_image_source_artifacts = __esm({
63307
63841
  "src/api/image-source-artifacts.ts"() {
63308
63842
  "use strict";
63309
- import_node_crypto36 = require("crypto");
63843
+ import_node_crypto37 = require("crypto");
63310
63844
  init_private_artifacts();
63311
63845
  IMAGE_SOURCE_ARTIFACT_PREFIX = "image-sources/";
63312
63846
  IMAGE_SOURCE_ARTIFACT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
@@ -64920,7 +65454,7 @@ async function runBrowserAgentMigration() {
64920
65454
  }
64921
65455
  async function createExtensionRow(input) {
64922
65456
  const db = getDb();
64923
- const id = `bext_${(0, import_node_crypto37.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
65457
+ const id = `bext_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
64924
65458
  await db.execute({
64925
65459
  sql: `INSERT INTO browser_agent_extensions (id, user_id, name, backend_id, backend_name, source, source_url, size_bytes)
64926
65460
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -64955,7 +65489,7 @@ async function deleteExtensionRow(userId, name) {
64955
65489
  }
64956
65490
  async function createAuthConnectionRow(input) {
64957
65491
  const db = getDb();
64958
- const connectionId = `authc_${(0, import_node_crypto37.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
65492
+ const connectionId = `authc_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
64959
65493
  await db.execute({
64960
65494
  sql: `INSERT INTO browser_auth_connections (connection_id, domain, profile, account_email, note, status, browser_agent_session_id)
64961
65495
  VALUES (?, ?, ?, ?, ?, 'NEEDS_AUTH', ?)`,
@@ -65040,7 +65574,7 @@ async function deleteProfileLabel(userId, profile) {
65040
65574
  }
65041
65575
  async function createSessionRow(input) {
65042
65576
  const db = getDb();
65043
- const id = `bas_${(0, import_node_crypto37.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
65577
+ const id = `bas_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
65044
65578
  await db.execute({
65045
65579
  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
65580
  VALUES (?, ?, ?, ?, 'open', ?, ?, ?, datetime('now'))`,
@@ -65117,7 +65651,7 @@ async function recordAction(input) {
65117
65651
  sql: `INSERT INTO browser_agent_actions (id, session_id, type, params_json, ok, error)
65118
65652
  VALUES (?, ?, ?, ?, ?, ?)`,
65119
65653
  args: [
65120
- `baa_${(0, import_node_crypto37.randomUUID)().replace(/-/g, "").slice(0, 20)}`,
65654
+ `baa_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`,
65121
65655
  input.sessionId,
65122
65656
  input.type,
65123
65657
  input.params == null ? null : JSON.stringify(input.params),
@@ -65157,11 +65691,11 @@ async function listReplayRows(sessionId) {
65157
65691
  });
65158
65692
  return res.rows;
65159
65693
  }
65160
- var import_node_crypto37, _ready2, _migrationPromise2, ORPHANED_SESSION_STATUS;
65694
+ var import_node_crypto38, _ready2, _migrationPromise2, ORPHANED_SESSION_STATUS;
65161
65695
  var init_browser_agent_db = __esm({
65162
65696
  "src/api/browser-agent-db.ts"() {
65163
65697
  "use strict";
65164
- import_node_crypto37 = require("crypto");
65698
+ import_node_crypto38 = require("crypto");
65165
65699
  init_db();
65166
65700
  _ready2 = false;
65167
65701
  _migrationPromise2 = null;
@@ -66303,7 +66837,7 @@ var init_browser_agent_service = __esm({
66303
66837
 
66304
66838
  // src/api/serp-identity-db.ts
66305
66839
  async function createSerpIdentityRow(input) {
66306
- const id = `serpi_${(0, import_node_crypto38.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
66840
+ const id = `serpi_${(0, import_node_crypto39.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
66307
66841
  await getDb().execute({
66308
66842
  sql: `INSERT INTO serp_identities
66309
66843
  (id, user_id, name, kernel_profile_name, kernel_proxy_id, proxy_type, country, status)
@@ -66348,11 +66882,11 @@ async function deleteSerpIdentityRow(userId, name) {
66348
66882
  args: [userId, name]
66349
66883
  });
66350
66884
  }
66351
- var import_node_crypto38;
66885
+ var import_node_crypto39;
66352
66886
  var init_serp_identity_db = __esm({
66353
66887
  "src/api/serp-identity-db.ts"() {
66354
66888
  "use strict";
66355
- import_node_crypto38 = require("crypto");
66889
+ import_node_crypto39 = require("crypto");
66356
66890
  init_db();
66357
66891
  }
66358
66892
  });
@@ -66364,8 +66898,8 @@ function kernelClient() {
66364
66898
  return new import_sdk10.default({ apiKey });
66365
66899
  }
66366
66900
  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);
66901
+ const digest2 = (0, import_node_crypto40.createHash)("sha256").update(`${userId}:${name}`).digest("hex").slice(0, 16);
66902
+ const nonce = (0, import_node_crypto40.randomUUID)().replace(/-/g, "").slice(0, 8);
66369
66903
  return `mcp-serp-${resource}-${digest2}-${nonce}`;
66370
66904
  }
66371
66905
  function isNotFound2(error) {
@@ -66443,11 +66977,11 @@ async function deleteSerpIdentity(userId, name) {
66443
66977
  throw error;
66444
66978
  }
66445
66979
  }
66446
- var import_node_crypto39, import_sdk10, MAX_SERP_IDENTITIES_PER_USER;
66980
+ var import_node_crypto40, import_sdk10, MAX_SERP_IDENTITIES_PER_USER;
66447
66981
  var init_serp_identity_service = __esm({
66448
66982
  "src/api/serp-identity-service.ts"() {
66449
66983
  "use strict";
66450
- import_node_crypto39 = require("crypto");
66984
+ import_node_crypto40 = require("crypto");
66451
66985
  import_sdk10 = __toESM(require("@onkernel/sdk"), 1);
66452
66986
  init_browser_service_env();
66453
66987
  init_serp_identity_db();
@@ -67158,7 +67692,7 @@ function buildBrowserAgentRoutes() {
67158
67692
  }
67159
67693
  const existing = await getExtensionRow(user.id, name);
67160
67694
  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, "")}`;
67695
+ const backendName2 = `u${user.id}_${(0, import_node_crypto41.randomUUID)().replace(/-/g, "")}`;
67162
67696
  try {
67163
67697
  const imported = await importExtensionFromStore(storeUrl, backendName2);
67164
67698
  const row = await createExtensionRow({
@@ -67217,11 +67751,11 @@ function buildBrowserAgentRoutes() {
67217
67751
  });
67218
67752
  return app2;
67219
67753
  }
67220
- var import_node_crypto40, import_hono24, auth, DEFAULT_BROWSER_SESSION_LOCK_TTL_SECONDS, EXTENSION_NAME_RE, SERP_IDENTITY_NAME_RE;
67754
+ var import_node_crypto41, import_hono24, auth, DEFAULT_BROWSER_SESSION_LOCK_TTL_SECONDS, EXTENSION_NAME_RE, SERP_IDENTITY_NAME_RE;
67221
67755
  var init_browser_agent_routes = __esm({
67222
67756
  "src/api/browser-agent-routes.ts"() {
67223
67757
  "use strict";
67224
- import_node_crypto40 = require("crypto");
67758
+ import_node_crypto41 = require("crypto");
67225
67759
  import_hono24 = require("hono");
67226
67760
  init_api_auth();
67227
67761
  init_errors();
@@ -67779,7 +68313,7 @@ async function getKeys() {
67779
68313
  const privateKey = await (0, import_jose2.importPKCS8)(pem, "RS256", { extractable: true });
67780
68314
  const full = await (0, import_jose2.exportJWK)(privateKey);
67781
68315
  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);
68316
+ 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
68317
  publicJwk.kid = kid;
67784
68318
  publicJwk.alg = "RS256";
67785
68319
  publicJwk.use = "sig";
@@ -67958,23 +68492,23 @@ async function validateAuthRequest(p) {
67958
68492
  }
67959
68493
  function pkceMatches(verifier, challenge) {
67960
68494
  if (!verifier) return false;
67961
- const computed = (0, import_node_crypto41.createHash)("sha256").update(verifier).digest("base64url");
68495
+ const computed = (0, import_node_crypto42.createHash)("sha256").update(verifier).digest("base64url");
67962
68496
  return computed === challenge;
67963
68497
  }
67964
68498
  async function mintAccessToken(identity, scope, plan, audience) {
67965
68499
  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);
68500
+ 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
68501
  }
67968
68502
  function tokenErrorResponse(c, error, description, status) {
67969
68503
  return c.json({ error, error_description: description }, status);
67970
68504
  }
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;
68505
+ 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
68506
  var init_oauth_routes = __esm({
67973
68507
  "src/api/oauth-routes.ts"() {
67974
68508
  "use strict";
67975
68509
  import_hono26 = require("hono");
67976
68510
  import_cookie = require("hono/cookie");
67977
- import_node_crypto41 = require("crypto");
68511
+ import_node_crypto42 = require("crypto");
67978
68512
  import_jose2 = require("jose");
67979
68513
  init_session();
67980
68514
  init_db();
@@ -68081,7 +68615,7 @@ var init_oauth_routes = __esm({
68081
68615
  }
68082
68616
  }
68083
68617
  const clientName = typeof body.client_name === "string" ? body.client_name : null;
68084
- const clientId = `client_${(0, import_node_crypto41.randomBytes)(16).toString("hex")}`;
68618
+ const clientId = `client_${(0, import_node_crypto42.randomBytes)(16).toString("hex")}`;
68085
68619
  await withOAuthDatabaseDeadline("register-client", registerClient(clientId, redirectUris, clientName));
68086
68620
  console.log("[oauth-dcr] register OK client_id=%s redirect_uris=%s", clientId, JSON.stringify(redirectUris));
68087
68621
  return c.json({
@@ -68134,7 +68668,7 @@ var init_oauth_routes = __esm({
68134
68668
  if (action3 === "deny") return redirectWithError(p.redirect_uri, p.state, "access_denied");
68135
68669
  if (action3 !== "approve") return c.text("unsupported action", 400);
68136
68670
  const scope = negotiateScope(p.scope, user, p.resource);
68137
- const code = `code_${(0, import_node_crypto41.randomBytes)(32).toString("base64url")}`;
68671
+ const code = `code_${(0, import_node_crypto42.randomBytes)(32).toString("base64url")}`;
68138
68672
  const expiresAt = new Date(Date.now() + CODE_TTL_SECONDS * 1e3).toISOString();
68139
68673
  await withOAuthDatabaseDeadline("put-authorization-code", putCode({
68140
68674
  code,
@@ -68173,7 +68707,7 @@ var init_oauth_routes = __esm({
68173
68707
  const plan = user ? resolvePlan(user) : "free";
68174
68708
  const audience = record.resource ?? RESOURCE();
68175
68709
  const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
68176
- const refreshToken = `rt_${(0, import_node_crypto41.randomBytes)(40).toString("base64url")}`;
68710
+ const refreshToken = `rt_${(0, import_node_crypto42.randomBytes)(40).toString("base64url")}`;
68177
68711
  await withOAuthDatabaseDeadline("put-refresh-token", putRefresh({
68178
68712
  refresh_token: refreshToken,
68179
68713
  client_id: clientId,
@@ -68203,7 +68737,7 @@ var init_oauth_routes = __esm({
68203
68737
  const plan = user ? resolvePlan(user) : "free";
68204
68738
  const audience = record.resource ?? RESOURCE();
68205
68739
  const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
68206
- const nextRefresh = `rt_${(0, import_node_crypto41.randomBytes)(40).toString("base64url")}`;
68740
+ const nextRefresh = `rt_${(0, import_node_crypto42.randomBytes)(40).toString("base64url")}`;
68207
68741
  await withOAuthDatabaseDeadline("rotate-refresh-token", rotateRefresh(refreshToken, {
68208
68742
  refresh_token: nextRefresh,
68209
68743
  client_id: record.client_id,
@@ -69623,7 +70157,7 @@ function quoteUntrusted(value) {
69623
70157
  return clean3.split("\n").map((line) => `> ${line}`).join("\n");
69624
70158
  }
69625
70159
  function shortHash(value, length = 24) {
69626
- return (0, import_node_crypto42.createHash)("sha256").update(value).digest("hex").slice(0, length);
70160
+ return (0, import_node_crypto43.createHash)("sha256").update(value).digest("hex").slice(0, length);
69627
70161
  }
69628
70162
  function safePathPart(value) {
69629
70163
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "contact";
@@ -70111,11 +70645,11 @@ async function captureSupportMessage(memoryKey, email, options) {
70111
70645
  direction: options.direction
70112
70646
  };
70113
70647
  }
70114
- var import_node_crypto42, SUPPORT_TAGS, ISSUE_TAGS;
70648
+ var import_node_crypto43, SUPPORT_TAGS, ISSUE_TAGS;
70115
70649
  var init_resend_support_thread = __esm({
70116
70650
  "src/api/resend-support-thread.ts"() {
70117
70651
  "use strict";
70118
- import_node_crypto42 = require("crypto");
70652
+ import_node_crypto43 = require("crypto");
70119
70653
  init_memory();
70120
70654
  SUPPORT_TAGS = [
70121
70655
  {
@@ -70700,7 +71234,7 @@ async function claimWorkshopRegistration(input) {
70700
71234
  ) < ?
70701
71235
  `,
70702
71236
  args: [
70703
- (0, import_node_crypto43.randomUUID)(),
71237
+ (0, import_node_crypto44.randomUUID)(),
70704
71238
  input.eventSlug,
70705
71239
  input.email,
70706
71240
  input.firstName,
@@ -70717,7 +71251,7 @@ async function claimWorkshopRegistration(input) {
70717
71251
  id, event_slug, email, first_name, last_name, status
70718
71252
  ) VALUES (?, ?, ?, ?, ?, 'waitlisted')
70719
71253
  `,
70720
- args: [(0, import_node_crypto43.randomUUID)(), input.eventSlug, input.email, input.firstName, input.lastName || null]
71254
+ args: [(0, import_node_crypto44.randomUUID)(), input.eventSlug, input.email, input.firstName, input.lastName || null]
70721
71255
  });
70722
71256
  const waitlisted = await getWorkshopRegistration(input.eventSlug, input.email);
70723
71257
  if (!waitlisted) throw new Error("workshop registration could not be claimed");
@@ -70732,7 +71266,7 @@ async function registerWorkshopInterest(input) {
70732
71266
  id, event_slug, email, first_name, last_name, status
70733
71267
  ) VALUES (?, ?, ?, ?, ?, 'interested')
70734
71268
  `,
70735
- args: [(0, import_node_crypto43.randomUUID)(), input.eventSlug, input.email, input.firstName, input.lastName || null]
71269
+ args: [(0, import_node_crypto44.randomUUID)(), input.eventSlug, input.email, input.firstName, input.lastName || null]
70736
71270
  });
70737
71271
  const registration = await getWorkshopRegistration(input.eventSlug, input.email);
70738
71272
  if (!registration) throw new Error("workshop interest could not be recorded");
@@ -70762,11 +71296,11 @@ async function updateWorkshopRegistration(id, patch) {
70762
71296
  args: [...entries.map(([, value]) => value), id]
70763
71297
  });
70764
71298
  }
70765
- var import_node_crypto43;
71299
+ var import_node_crypto44;
70766
71300
  var init_workshop_registration_repository = __esm({
70767
71301
  "src/api/workshop-registration-repository.ts"() {
70768
71302
  "use strict";
70769
- import_node_crypto43 = require("crypto");
71303
+ import_node_crypto44 = require("crypto");
70770
71304
  init_db();
70771
71305
  }
70772
71306
  });
@@ -71339,7 +71873,7 @@ function renderEditorialReadingRoom(input, now = /* @__PURE__ */ new Date()) {
71339
71873
  articleCount: articles.length,
71340
71874
  wordCount: totalWordCount,
71341
71875
  bytes,
71342
- sha256: (0, import_node_crypto44.createHash)("sha256").update(html).digest("hex"),
71876
+ sha256: (0, import_node_crypto45.createHash)("sha256").update(html).digest("hex"),
71343
71877
  warnings
71344
71878
  };
71345
71879
  }
@@ -71422,14 +71956,14 @@ ${provenance}`;
71422
71956
  ...rendered,
71423
71957
  html,
71424
71958
  bytes: Buffer.byteLength(html),
71425
- sha256: (0, import_node_crypto44.createHash)("sha256").update(html).digest("hex")
71959
+ sha256: (0, import_node_crypto45.createHash)("sha256").update(html).digest("hex")
71426
71960
  };
71427
71961
  }
71428
- var import_node_crypto44, import_node_fs14, import_node_path21, import_marked, runtimeEntryDir, assetCache;
71962
+ var import_node_crypto45, import_node_fs14, import_node_path21, import_marked, runtimeEntryDir, assetCache;
71429
71963
  var init_render = __esm({
71430
71964
  "src/editorial-reading-room/render.ts"() {
71431
71965
  "use strict";
71432
- import_node_crypto44 = require("crypto");
71966
+ import_node_crypto45 = require("crypto");
71433
71967
  import_node_fs14 = require("fs");
71434
71968
  import_node_path21 = require("path");
71435
71969
  import_marked = require("marked");
@@ -71573,8 +72107,9 @@ var init_editorial_reading_room_routes = __esm({
71573
72107
 
71574
72108
  // src/api/commons-image-store.ts
71575
72109
  async function ensureCommonsImageSchema() {
71576
- if (imageSchemaReady) return;
71577
- await getDb().execute(`
72110
+ const db = getDb();
72111
+ if (imageSchemaReady && imageSchemaDb === db) return;
72112
+ await db.execute(`
71578
72113
  CREATE TABLE IF NOT EXISTS commons_images (
71579
72114
  id TEXT PRIMARY KEY,
71580
72115
  user_id INTEGER NOT NULL,
@@ -71590,8 +72125,9 @@ async function ensureCommonsImageSchema() {
71590
72125
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
71591
72126
  )
71592
72127
  `);
71593
- await getDb().execute(`CREATE UNIQUE INDEX IF NOT EXISTS commons_images_digest ON commons_images(digest)`);
72128
+ await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS commons_images_digest ON commons_images(digest)`);
71594
72129
  imageSchemaReady = true;
72130
+ imageSchemaDb = db;
71595
72131
  }
71596
72132
  function sniffImageFormat(bytes) {
71597
72133
  if (bytes.length < 12) return null;
@@ -71621,7 +72157,7 @@ async function assertPublicHttpsUrl(rawUrl) {
71621
72157
  try {
71622
72158
  url = new URL(rawUrl);
71623
72159
  } catch {
71624
- throw new CommonsImageError("image_url_invalid", `"${rawUrl}" is not a valid URL.`);
72160
+ throw new CommonsImageError("image_url_invalid", "The image source is not a valid URL.");
71625
72161
  }
71626
72162
  if (url.protocol !== "https:") {
71627
72163
  throw new CommonsImageError("image_url_not_https", "Commons image sources must be https URLs.");
@@ -71629,7 +72165,7 @@ async function assertPublicHttpsUrl(rawUrl) {
71629
72165
  const hostname = url.hostname.replace(/^\[|\]$/g, "");
71630
72166
  const addresses = (0, import_node_net2.isIP)(hostname) ? [{ address: hostname }] : await (0, import_promises16.lookup)(hostname, { all: true }).catch(() => []);
71631
72167
  if (!addresses.length) {
71632
- throw new CommonsImageError("image_host_unresolvable", `Could not resolve ${url.hostname}.`);
72168
+ throw new CommonsImageError("image_host_unresolvable", "The image source host could not be resolved.");
71633
72169
  }
71634
72170
  if (addresses.some((entry) => isBlockedAddress(entry.address))) {
71635
72171
  throw new CommonsImageError("image_host_blocked", "Commons image sources cannot resolve to private or loopback addresses.");
@@ -71640,15 +72176,36 @@ async function downloadImage(sourceUrl) {
71640
72176
  let current = sourceUrl;
71641
72177
  for (let redirect = 0; redirect <= COMMONS_IMAGE_MAX_REDIRECTS; redirect += 1) {
71642
72178
  const url = await assertPublicHttpsUrl(current);
71643
- const response = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(COMMONS_IMAGE_FETCH_TIMEOUT_MS) });
72179
+ let response;
72180
+ try {
72181
+ response = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(COMMONS_IMAGE_FETCH_TIMEOUT_MS) });
72182
+ } catch (error) {
72183
+ if (error instanceof CommonsImageError) throw error;
72184
+ throw new CommonsImageError(
72185
+ "image_source_unavailable",
72186
+ "The image source could not be reached by Commons.",
72187
+ 422,
72188
+ true
72189
+ );
72190
+ }
71644
72191
  if (response.status >= 300 && response.status < 400) {
71645
72192
  const location2 = response.headers.get("location");
71646
72193
  if (!location2) throw new CommonsImageError("image_redirect_invalid", "Image source redirected without a destination.");
71647
- current = new URL(location2, url).toString();
72194
+ try {
72195
+ current = new URL(location2, url).toString();
72196
+ } catch {
72197
+ throw new CommonsImageError("image_redirect_invalid", "Image source redirected to an invalid destination.");
72198
+ }
71648
72199
  continue;
71649
72200
  }
71650
72201
  if (!response.ok) {
71651
- throw new CommonsImageError("image_source_unavailable", `Image source returned HTTP ${response.status}.`, 422);
72202
+ const retryable = response.status === 408 || response.status === 425 || response.status === 429 || response.status >= 500;
72203
+ throw new CommonsImageError(
72204
+ "image_source_unavailable",
72205
+ `The image source returned HTTP ${response.status} and could not be hosted.`,
72206
+ 422,
72207
+ retryable
72208
+ );
71652
72209
  }
71653
72210
  const declaredLength = Number(response.headers.get("content-length") ?? "0");
71654
72211
  if (declaredLength > COMMONS_IMAGE_MAX_BYTES) {
@@ -71675,8 +72232,35 @@ function commonsImageBlobStore() {
71675
72232
  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
72233
  return token6 ? new VercelBlobStore(token6) : getBlobStore();
71677
72234
  }
71678
- function isCommonsHostedImageUrl(url) {
71679
- return /^https:\/\/[^/]*(\.public\.blob\.vercel-storage\.com|blob\.vercel-storage\.com)\//i.test(url);
72235
+ function normalizeCommonsImageUrl(rawUrl) {
72236
+ try {
72237
+ const url = new URL(rawUrl.trim());
72238
+ if (url.protocol !== "https:") return null;
72239
+ url.hash = "";
72240
+ return url.toString();
72241
+ } catch {
72242
+ return null;
72243
+ }
72244
+ }
72245
+ async function findCommonsHostedImageByUrl(rawUrl) {
72246
+ const url = normalizeCommonsImageUrl(rawUrl);
72247
+ if (!url) return null;
72248
+ await ensureCommonsImageSchema();
72249
+ const result = await getDb().execute({
72250
+ sql: "SELECT id, url, content_type, bytes, digest, source_url FROM commons_images WHERE url = ? LIMIT 1",
72251
+ args: [url]
72252
+ });
72253
+ const row = result.rows[0];
72254
+ if (!row) return null;
72255
+ return {
72256
+ id: String(row.id),
72257
+ url: String(row.url),
72258
+ contentType: String(row.content_type),
72259
+ bytes: Number(row.bytes),
72260
+ digest: String(row.digest),
72261
+ sourceUrl: row.source_url ? String(row.source_url) : null,
72262
+ reused: true
72263
+ };
71680
72264
  }
71681
72265
  async function hostCommonsImage(input) {
71682
72266
  if (hostOverride) return hostOverride(input);
@@ -71693,7 +72277,7 @@ async function hostCommonsImage(input) {
71693
72277
  415
71694
72278
  );
71695
72279
  }
71696
- const digest2 = (0, import_node_crypto45.createHash)("sha256").update(bytes).digest("hex");
72280
+ const digest2 = (0, import_node_crypto46.createHash)("sha256").update(bytes).digest("hex");
71697
72281
  const existing = await getDb().execute({
71698
72282
  sql: "SELECT id, url, content_type, bytes, source_url FROM commons_images WHERE digest = ? LIMIT 1",
71699
72283
  args: [digest2]
@@ -71747,7 +72331,8 @@ async function hostCommonsImage(input) {
71747
72331
  async function hostEntityImages(input) {
71748
72332
  const rewrite = { mediaUrls: /* @__PURE__ */ new Map(), dropped: [] };
71749
72333
  const featuredUrl = input.featuredImage?.url;
71750
- if (featuredUrl && !isCommonsHostedImageUrl(featuredUrl)) {
72334
+ const registeredFeatured = featuredUrl ? await findCommonsHostedImageByUrl(featuredUrl) : null;
72335
+ if (featuredUrl && !registeredFeatured) {
71751
72336
  const hosted = await hostCommonsImage({
71752
72337
  userId: input.userId,
71753
72338
  sourceUrl: featuredUrl,
@@ -71755,17 +72340,19 @@ async function hostEntityImages(input) {
71755
72340
  license: input.featuredImage?.license,
71756
72341
  attribution: input.featuredImage?.sourceUrl
71757
72342
  }).catch((error) => {
71758
- const message = error instanceof Error ? error.message : String(error);
72343
+ if (error instanceof CommonsImageError) throw error;
71759
72344
  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}`,
72345
+ "featured_image_unhostable",
72346
+ "The featured image could not be hosted, so the entity was not persisted with a link that could break.",
71762
72347
  422
71763
72348
  );
71764
72349
  });
71765
72350
  rewrite.featuredImageUrl = hosted.url;
72351
+ rewrite.mediaUrls.set(featuredUrl, hosted.url);
71766
72352
  }
71767
72353
  for (const item of input.media ?? []) {
71768
- if (!item.url || isCommonsHostedImageUrl(item.url)) continue;
72354
+ if (item.url && rewrite.mediaUrls.has(item.url)) continue;
72355
+ if (!item.url || await findCommonsHostedImageByUrl(item.url)) continue;
71769
72356
  try {
71770
72357
  const hosted = await hostCommonsImage({
71771
72358
  userId: input.userId,
@@ -71781,11 +72368,11 @@ async function hostEntityImages(input) {
71781
72368
  }
71782
72369
  return rewrite;
71783
72370
  }
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;
72371
+ var import_node_crypto46, import_promises16, import_node_net2, COMMONS_IMAGE_MAX_BYTES, COMMONS_IMAGE_MAX_REDIRECTS, COMMONS_IMAGE_FETCH_TIMEOUT_MS, COMMONS_IMAGE_RETRY_GUIDANCE, CommonsImageError, imageSchemaReady, imageSchemaDb, hostOverride;
71785
72372
  var init_commons_image_store = __esm({
71786
72373
  "src/api/commons-image-store.ts"() {
71787
72374
  "use strict";
71788
- import_node_crypto45 = require("crypto");
72375
+ import_node_crypto46 = require("crypto");
71789
72376
  import_promises16 = require("dns/promises");
71790
72377
  import_node_net2 = require("net");
71791
72378
  init_blob_store();
@@ -71793,17 +72380,23 @@ var init_commons_image_store = __esm({
71793
72380
  COMMONS_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
71794
72381
  COMMONS_IMAGE_MAX_REDIRECTS = 3;
71795
72382
  COMMONS_IMAGE_FETCH_TIMEOUT_MS = 2e4;
72383
+ COMMONS_IMAGE_RETRY_GUIDANCE = "Call commons_host_image with a different stable public HTTPS image URL or imageBase64, replace featuredImage.url with the returned URL, validate again, then retry commons_submit_entity with the same idempotency key.";
71796
72384
  CommonsImageError = class extends Error {
71797
- constructor(code, message, httpStatus = 400) {
72385
+ constructor(code, message, httpStatus = 400, retryable = false, retryGuidance = COMMONS_IMAGE_RETRY_GUIDANCE) {
71798
72386
  super(message);
71799
72387
  this.code = code;
71800
72388
  this.httpStatus = httpStatus;
72389
+ this.retryable = retryable;
72390
+ this.retryGuidance = retryGuidance;
71801
72391
  this.name = "CommonsImageError";
71802
72392
  }
71803
72393
  code;
71804
72394
  httpStatus;
72395
+ retryable;
72396
+ retryGuidance;
71805
72397
  };
71806
72398
  imageSchemaReady = false;
72399
+ imageSchemaDb = null;
71807
72400
  hostOverride = null;
71808
72401
  }
71809
72402
  });
@@ -71910,7 +72503,7 @@ function buildCommonsLinkset(entity, claims) {
71910
72503
  context[claim.predicate] = targets;
71911
72504
  }
71912
72505
  const document2 = { linkset: [context] };
71913
- const etag = `"${(0, import_node_crypto46.createHash)("sha256").update(JSON.stringify(document2)).digest("hex")}"`;
72506
+ const etag = `"${(0, import_node_crypto47.createHash)("sha256").update(JSON.stringify(document2)).digest("hex")}"`;
71914
72507
  return { document: document2, etag, publicUrl, linksetUrl, profile: COMMONS_RELATIONSHIP_PROFILE };
71915
72508
  }
71916
72509
  function commonsLinksetDiscoveryHeader(idOrSlug) {
@@ -71978,11 +72571,11 @@ function normalizeHreflang(value) {
71978
72571
  const languages = [...new Set(value.map((item) => optionalText(item, 80)).filter((item) => Boolean(item)))];
71979
72572
  return languages.length ? languages : void 0;
71980
72573
  }
71981
- var import_node_crypto46, COMMONS_LINKSET_MEDIA_TYPE, COMMONS_RELATIONSHIP_PROFILE, REGISTERED_RELATIONS;
72574
+ var import_node_crypto47, COMMONS_LINKSET_MEDIA_TYPE, COMMONS_RELATIONSHIP_PROFILE, REGISTERED_RELATIONS;
71982
72575
  var init_commons_linksets = __esm({
71983
72576
  "src/api/commons-linksets.ts"() {
71984
72577
  "use strict";
71985
- import_node_crypto46 = require("crypto");
72578
+ import_node_crypto47 = require("crypto");
71986
72579
  COMMONS_LINKSET_MEDIA_TYPE = "application/linkset+json";
71987
72580
  COMMONS_RELATIONSHIP_PROFILE = "https://mcpscraper.dev/commons/profiles/relationships/v1";
71988
72581
  REGISTERED_RELATIONS = /* @__PURE__ */ new Set([
@@ -72025,9 +72618,9 @@ var init_commons_linksets = __esm({
72025
72618
 
72026
72619
  // src/api/commons-repository.ts
72027
72620
  async function ensureCommonsSchema() {
72028
- if (schemaReady) return;
72621
+ if (schemaReady2) return;
72029
72622
  if (await schemaObjectsPresent(COMMONS_SCHEMA_OBJECTS)) {
72030
- schemaReady = true;
72623
+ schemaReady2 = true;
72031
72624
  return;
72032
72625
  }
72033
72626
  const db = getDb();
@@ -72250,7 +72843,7 @@ async function ensureCommonsSchema() {
72250
72843
  args: [COMMONS_SCHEMA_VERSION]
72251
72844
  }
72252
72845
  ], "write");
72253
- schemaReady = true;
72846
+ schemaReady2 = true;
72254
72847
  }
72255
72848
  async function searchCommonsEntities(filters = {}, userId) {
72256
72849
  await ensureCommonsSchema();
@@ -72458,7 +73051,7 @@ async function prepareCommonsEntity(input, user) {
72458
73051
  normalizedEntityType: entityType,
72459
73052
  proposedSlug: slug4 || null,
72460
73053
  publicUrl: slug4 ? publicUrlForSlug(slug4) : null,
72461
- writePath: "commons_prepare_entity -> commons_validate_entity -> commons_submit_entity"
73054
+ writePath: "commons_prepare_entity -> compose the entity -> commons_host_image -> commons_validate_entity -> commons_submit_entity"
72462
73055
  },
72463
73056
  contract,
72464
73057
  billing: {
@@ -72480,8 +73073,8 @@ async function prepareCommonsEntity(input, user) {
72480
73073
  "Search likely duplicates before submitting a new entity. If one matches, edit it with entityId and baseRevision instead of creating another page.",
72481
73074
  "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
73075
  "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.",
73076
+ "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.",
73077
+ "Call commons_validate_entity with the composed, image-ready payload before commons_submit_entity.",
72485
73078
  "Validation is free. Submitting a new entity costs 10 Credits; submitting an edit to an existing entity costs 2 Credits."
72486
73079
  ]
72487
73080
  };
@@ -72496,6 +73089,8 @@ async function validateCommonsEntity(input, user) {
72496
73089
  const contract = commonsEntityProfileFor(input.entityType, input.seo?.schemaOrgType);
72497
73090
  const source = normalizeSource(input.source);
72498
73091
  const featuredImage = normalizeFeaturedImage(input.featuredImage);
73092
+ const normalizedFeaturedImageUrl = featuredImage?.url ? normalizeCommonsImageUrl(featuredImage.url) : null;
73093
+ const registeredFeaturedImage = normalizedFeaturedImageUrl ? await findCommonsHostedImageByUrl(normalizedFeaturedImageUrl) : null;
72499
73094
  const bodyMarkdown = safeCandidateBody(input.bodyMarkdown, input.contentSections);
72500
73095
  const headings = extractMarkdownHeadings(bodyMarkdown);
72501
73096
  const placeholderSections = unsupportedPlaceholderSections(bodyMarkdown);
@@ -72533,15 +73128,44 @@ async function validateCommonsEntity(input, user) {
72533
73128
  if (input.reviewPolicy === "always_review") {
72534
73129
  warnings.push("reviewPolicy is always_review, so the proposal will not auto-publish even if the payload is otherwise valid.");
72535
73130
  }
73131
+ if (featuredImage?.url && !registeredFeaturedImage) {
73132
+ 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.");
73133
+ }
72536
73134
  if (user?.id == null) warnings.push("No authenticated user was supplied to validation; submit still requires API-key authenticated user context.");
72537
73135
  const valid = errors.length === 0;
72538
- const autoPublishEligible = valid && input.reviewPolicy !== "always_review";
73136
+ const imageReadyForPublish = Boolean(registeredFeaturedImage);
73137
+ const publishable = valid && imageReadyForPublish;
73138
+ const autoPublishEligible = publishable && input.reviewPolicy !== "always_review";
72539
73139
  const tagVocabulary = await commonsTagVocabulary();
72540
73140
  return {
72541
73141
  ok: true,
72542
73142
  valid,
72543
- publishable: valid,
73143
+ publishable,
72544
73144
  autoPublishEligible,
73145
+ imageDiagnostics: {
73146
+ featuredImage: registeredFeaturedImage ? {
73147
+ state: "commons_hosted",
73148
+ url: registeredFeaturedImage.url,
73149
+ commonsImageId: registeredFeaturedImage.id,
73150
+ readyForPublish: true,
73151
+ submitWillAttemptAutoHost: false,
73152
+ requiredAction: null
73153
+ } : featuredImage?.url ? {
73154
+ state: "external_unhosted",
73155
+ url: normalizedFeaturedImageUrl,
73156
+ commonsImageId: null,
73157
+ readyForPublish: false,
73158
+ submitWillAttemptAutoHost: true,
73159
+ requiredAction: "Call commons_host_image, replace featuredImage.url with its returned permanent URL, and validate again."
73160
+ } : {
73161
+ state: "missing",
73162
+ url: null,
73163
+ commonsImageId: null,
73164
+ readyForPublish: false,
73165
+ submitWillAttemptAutoHost: false,
73166
+ requiredAction: "Add a featured image by calling commons_host_image, then set featuredImage.url to its returned permanent URL."
73167
+ }
73168
+ },
72545
73169
  errors,
72546
73170
  warnings,
72547
73171
  contract,
@@ -72622,7 +73246,7 @@ async function submitCommonsEntity(input, user) {
72622
73246
  const existingClaims = existing && normalized.claims !== void 0 ? await getCommonsClaimsByEntityId(existing.id) : [];
72623
73247
  const safety = evaluatePublishSafety(normalized, existing);
72624
73248
  const shouldApply = safety.safe && normalized.reviewPolicy !== "always_review";
72625
- const proposalId = `commons-proposal-${(0, import_node_crypto47.randomUUID)()}`;
73249
+ const proposalId = `commons-proposal-${(0, import_node_crypto48.randomUUID)()}`;
72626
73250
  const now = (/* @__PURE__ */ new Date()).toISOString();
72627
73251
  const entityId = existing?.id ?? normalized.entityId ?? allocateEntityId();
72628
73252
  const proposalStatus = shouldApply ? "accepted" : "pending_review";
@@ -72677,7 +73301,7 @@ async function submitCommonsEntity(input, user) {
72677
73301
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
72678
73302
  `,
72679
73303
  args: [
72680
- `commons-ledger-${(0, import_node_crypto47.randomUUID)()}`,
73304
+ `commons-ledger-${(0, import_node_crypto48.randomUUID)()}`,
72681
73305
  nextEntity.id,
72682
73306
  proposalId,
72683
73307
  user.id,
@@ -72783,7 +73407,7 @@ async function saveCommonsFilter(input, user) {
72783
73407
  sql: "SELECT id FROM commons_saved_filters WHERE user_id = ? AND name = ? LIMIT 1",
72784
73408
  args: [user.id, name]
72785
73409
  });
72786
- const id = existing.rows[0]?.id != null ? String(existing.rows[0].id) : `commons-filter-${(0, import_node_crypto47.randomUUID)()}`;
73410
+ const id = existing.rows[0]?.id != null ? String(existing.rows[0].id) : `commons-filter-${(0, import_node_crypto48.randomUUID)()}`;
72787
73411
  await getDb().execute({
72788
73412
  sql: `
72789
73413
  INSERT INTO commons_saved_filters (id, user_id, name, description, filter_json, created_at, updated_at)
@@ -73561,7 +74185,7 @@ function commonsIndexDocuments(entity, now) {
73561
74185
  return documents;
73562
74186
  }
73563
74187
  function commonsIndexDocumentId(entityId, documentType, documentKey) {
73564
- return `commons-index-${(0, import_node_crypto47.createHash)("sha256").update(`${entityId}
74188
+ return `commons-index-${(0, import_node_crypto48.createHash)("sha256").update(`${entityId}
73565
74189
  ${documentType}
73566
74190
  ${documentKey}`).digest("hex").slice(0, 32)}`;
73567
74191
  }
@@ -74127,11 +74751,11 @@ function jsonLikeValue(value) {
74127
74751
  function escapeLike(value) {
74128
74752
  return value.replace(/[%_]/g, "");
74129
74753
  }
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;
74754
+ 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
74755
  var init_commons_repository = __esm({
74132
74756
  "src/api/commons-repository.ts"() {
74133
74757
  "use strict";
74134
- import_node_crypto47 = require("crypto");
74758
+ import_node_crypto48 = require("crypto");
74135
74759
  init_db();
74136
74760
  init_commons_image_store();
74137
74761
  init_commons_embeddings();
@@ -74286,7 +74910,7 @@ var init_commons_repository = __esm({
74286
74910
  ]
74287
74911
  }
74288
74912
  };
74289
- schemaReady = false;
74913
+ schemaReady2 = false;
74290
74914
  COMMONS_SCHEMA_OBJECTS = [
74291
74915
  "schema_migrations",
74292
74916
  "commons_entities",
@@ -74510,7 +75134,7 @@ async function claimCommonsPublication(input, user) {
74510
75134
  }
74511
75135
  const existingName = await getCommonsPublicationBySubdomain(subdomain);
74512
75136
  if (existingName) throw new CommonsPublicationError("publication_name_unavailable", "That publication name is already claimed.", 409);
74513
- const id = `tcpub_${(0, import_node_crypto48.randomUUID)()}`;
75137
+ const id = `tcpub_${(0, import_node_crypto49.randomUUID)()}`;
74514
75138
  const now = (/* @__PURE__ */ new Date()).toISOString();
74515
75139
  try {
74516
75140
  await getDb().execute({
@@ -74562,7 +75186,7 @@ async function publishCommonsEditorial(input, user) {
74562
75186
  const canonicalUrl = editionPublicUrl(subdomain, editionSlug);
74563
75187
  const rendered = renderEditorialReadingRoom(editionInput);
74564
75188
  const html = addPublicMetadata(rendered.html, canonicalUrl, publication.title);
74565
- const editionId = `tced_${(0, import_node_crypto48.randomUUID)()}`;
75189
+ const editionId = `tced_${(0, import_node_crypto49.randomUUID)()}`;
74566
75190
  const revision = (latest?.revision ?? 0) + 1;
74567
75191
  const now = (/* @__PURE__ */ new Date()).toISOString();
74568
75192
  await getDb().batch([
@@ -74586,7 +75210,7 @@ async function publishCommonsEditorial(input, user) {
74586
75210
  JSON.stringify(editionInput.articles),
74587
75211
  html,
74588
75212
  rendered.filename,
74589
- (0, import_node_crypto48.createHash)("sha256").update(html).digest("hex"),
75213
+ (0, import_node_crypto49.createHash)("sha256").update(html).digest("hex"),
74590
75214
  rendered.articleCount,
74591
75215
  rendered.wordCount,
74592
75216
  Buffer.byteLength(html),
@@ -74644,7 +75268,7 @@ async function updateCommonsEditorialArticle(input, user) {
74644
75268
  const canonicalUrl = editionPublicUrl(subdomain, editionSlug);
74645
75269
  const rendered = renderEditorialReadingRoom(editionInput);
74646
75270
  const html = addPublicMetadata(rendered.html, canonicalUrl, publication.title);
74647
- const editionId = `tced_${(0, import_node_crypto48.randomUUID)()}`;
75271
+ const editionId = `tced_${(0, import_node_crypto49.randomUUID)()}`;
74648
75272
  const revision = latest.revision + 1;
74649
75273
  const now = (/* @__PURE__ */ new Date()).toISOString();
74650
75274
  await getDb().batch([
@@ -74668,7 +75292,7 @@ async function updateCommonsEditorialArticle(input, user) {
74668
75292
  JSON.stringify(nextArticles),
74669
75293
  html,
74670
75294
  rendered.filename,
74671
- (0, import_node_crypto48.createHash)("sha256").update(html).digest("hex"),
75295
+ (0, import_node_crypto49.createHash)("sha256").update(html).digest("hex"),
74672
75296
  rendered.articleCount,
74673
75297
  rendered.wordCount,
74674
75298
  Buffer.byteLength(html),
@@ -74873,11 +75497,11 @@ function addPublicMetadata(html, canonicalUrl, publicationTitle) {
74873
75497
  "</head>"
74874
75498
  ].join("\n"));
74875
75499
  }
74876
- var import_node_crypto48, PUBLICATION_ROOT_DOMAIN, RESERVED_SUBDOMAINS, CommonsPublicationError;
75500
+ var import_node_crypto49, PUBLICATION_ROOT_DOMAIN, RESERVED_SUBDOMAINS, CommonsPublicationError;
74877
75501
  var init_commons_publication_repository = __esm({
74878
75502
  "src/api/commons-publication-repository.ts"() {
74879
75503
  "use strict";
74880
- import_node_crypto48 = require("crypto");
75504
+ import_node_crypto49 = require("crypto");
74881
75505
  init_db();
74882
75506
  init_commons_repository();
74883
75507
  init_render();
@@ -74964,6 +75588,18 @@ function repositoryStatus(error) {
74964
75588
  if (error.httpStatus === 409) return 409;
74965
75589
  return 400;
74966
75590
  }
75591
+ function imageErrorResponse(c, error, chargeStatus) {
75592
+ return c.json({
75593
+ ok: false,
75594
+ error: error.code,
75595
+ error_code: error.code,
75596
+ error_type: "invalid_request",
75597
+ retryable: error.retryable,
75598
+ ...chargeStatus ? { charge_status: chargeStatus } : {},
75599
+ message: `${error.message} ${error.retryGuidance}`,
75600
+ details: { retry_guidance: error.retryGuidance }
75601
+ }, error.httpStatus);
75602
+ }
74967
75603
  async function publicationOperation(c, operation, successStatus = 200) {
74968
75604
  try {
74969
75605
  return c.json({ ok: true, data: await operation() }, successStatus);
@@ -75304,7 +75940,7 @@ var init_commons_routes = __esm({
75304
75940
  return c.json({ ok: true, data: hosted }, hosted.reused ? 200 : 201);
75305
75941
  } catch (error) {
75306
75942
  if (error instanceof CommonsImageError) {
75307
- return c.json({ ok: false, error: error.code, message: error.message }, error.httpStatus);
75943
+ return imageErrorResponse(c, error);
75308
75944
  }
75309
75945
  throw error;
75310
75946
  }
@@ -75368,7 +76004,14 @@ var init_commons_routes = __esm({
75368
76004
  }
75369
76005
  return c.json({ ok: true, data: result, billing: publicListingWriteBilling(billing) }, 201);
75370
76006
  } catch (error) {
75371
- if (billing) await refundListingWrite(billing, "Transparent Commons write failed before persistence.").catch(() => null);
76007
+ let chargeStatus = billing ? billing.duplicate ? "not_charged" : "refund_pending" : "not_charged";
76008
+ if (billing && !billing.duplicate) {
76009
+ const settlement = await refundListingWrite(billing, "Transparent Commons write failed before persistence.").catch(() => null);
76010
+ if (settlement) chargeStatus = "refunded";
76011
+ }
76012
+ if (error instanceof CommonsImageError) {
76013
+ return imageErrorResponse(c, error, chargeStatus);
76014
+ }
75372
76015
  if (error instanceof CommonsRepositoryError) {
75373
76016
  return c.json({ ok: false, error: error.code, message: error.message }, repositoryStatus(error));
75374
76017
  }
@@ -76110,7 +76753,7 @@ async function migrateAnalytics() {
76110
76753
  }
76111
76754
  function normalizeSlug2(value) {
76112
76755
  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")}`;
76756
+ return slug4 || `site-${(0, import_node_crypto50.randomBytes)(4).toString("hex")}`;
76114
76757
  }
76115
76758
  function normalizeObservedHostname(origin) {
76116
76759
  if (!origin || origin === "null") return null;
@@ -76123,7 +76766,7 @@ function normalizeObservedHostname(origin) {
76123
76766
  }
76124
76767
  }
76125
76768
  function publicPixelId() {
76126
- return `px_${(0, import_node_crypto49.randomBytes)(18).toString("base64url")}`;
76769
+ return `px_${(0, import_node_crypto50.randomBytes)(18).toString("base64url")}`;
76127
76770
  }
76128
76771
  function mapRows(rows) {
76129
76772
  return rows;
@@ -76155,7 +76798,7 @@ async function requireEditor(client2, siteId, userId) {
76155
76798
  async function createAnalyticsSite(input) {
76156
76799
  const db = getAnalyticsPool();
76157
76800
  const client2 = await db.connect();
76158
- const id = (0, import_node_crypto49.randomUUID)();
76801
+ const id = (0, import_node_crypto50.randomUUID)();
76159
76802
  const baseSlug = normalizeSlug2(input.slug || input.name);
76160
76803
  try {
76161
76804
  await client2.query("BEGIN");
@@ -76171,7 +76814,7 @@ async function createAnalyticsSite(input) {
76171
76814
  } catch (error) {
76172
76815
  const code = error.code;
76173
76816
  if (code !== "23505" || attempt === 2) throw error;
76174
- slug4 = `${baseSlug}-${(0, import_node_crypto49.randomBytes)(2).toString("hex")}`;
76817
+ slug4 = `${baseSlug}-${(0, import_node_crypto50.randomBytes)(2).toString("hex")}`;
76175
76818
  }
76176
76819
  }
76177
76820
  await client2.query(
@@ -76386,7 +77029,7 @@ async function createAnalyticsPixel(input) {
76386
77029
  VALUES ($1, $2, $3, $4, $5)
76387
77030
  RETURNING id, site_id, public_id, name, environment, status, created_at::text, NULL::text AS last_event_at`,
76388
77031
  [
76389
- (0, import_node_crypto49.randomUUID)(),
77032
+ (0, import_node_crypto50.randomUUID)(),
76390
77033
  input.siteId,
76391
77034
  publicPixelId(),
76392
77035
  input.name.trim(),
@@ -76463,7 +77106,7 @@ async function setAnalyticsPixelDomainState(input) {
76463
77106
  SELECT $1, p.id, $4, $5 FROM analytics_pixels p WHERE p.id = $2 AND p.site_id = $3
76464
77107
  ON CONFLICT(pixel_id, hostname) DO UPDATE SET state = EXCLUDED.state, updated_at = now()
76465
77108
  RETURNING id`,
76466
- [(0, import_node_crypto49.randomUUID)(), input.pixelId, input.siteId, hostname, input.state]
77109
+ [(0, import_node_crypto50.randomUUID)(), input.pixelId, input.siteId, hostname, input.state]
76467
77110
  );
76468
77111
  if (!result.rowCount)
76469
77112
  throw new AnalyticsRepositoryError(
@@ -76513,7 +77156,7 @@ function sanitizeAnalyticsProperties(value) {
76513
77156
  }
76514
77157
  async function ingestAnalyticsEvents(input) {
76515
77158
  const db = getAnalyticsPool();
76516
- const requestId = `air_${(0, import_node_crypto49.randomUUID)()}`;
77159
+ const requestId = `air_${(0, import_node_crypto50.randomUUID)()}`;
76517
77160
  const hostname = normalizeObservedHostname(input.origin);
76518
77161
  const pixelResult = await db.query(
76519
77162
  `SELECT p.id, p.site_id, p.status,
@@ -76534,7 +77177,7 @@ async function ingestAnalyticsEvents(input) {
76534
77177
  `INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, hostname, rejected_count, reason_codes)
76535
77178
  VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,
76536
77179
  [
76537
- (0, import_node_crypto49.randomUUID)(),
77180
+ (0, import_node_crypto50.randomUUID)(),
76538
77181
  requestId,
76539
77182
  pixel?.site_id ?? null,
76540
77183
  pixel?.id ?? null,
@@ -76559,7 +77202,7 @@ async function ingestAnalyticsEvents(input) {
76559
77202
  `INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, hostname, rejected_count, reason_codes)
76560
77203
  VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,
76561
77204
  [
76562
- (0, import_node_crypto49.randomUUID)(),
77205
+ (0, import_node_crypto50.randomUUID)(),
76563
77206
  requestId,
76564
77207
  pixel.site_id,
76565
77208
  pixel.id,
@@ -76584,7 +77227,7 @@ async function ingestAnalyticsEvents(input) {
76584
77227
  `INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, rejected_count, reason_codes)
76585
77228
  VALUES ($1, $2, $3, $4, $5, $6::jsonb)`,
76586
77229
  [
76587
- (0, import_node_crypto49.randomUUID)(),
77230
+ (0, import_node_crypto50.randomUUID)(),
76588
77231
  requestId,
76589
77232
  pixel.site_id,
76590
77233
  pixel.id,
@@ -76608,7 +77251,7 @@ async function ingestAnalyticsEvents(input) {
76608
77251
  ON CONFLICT(pixel_id, hostname) DO UPDATE
76609
77252
  SET last_seen_at = now(), updated_at = now()
76610
77253
  RETURNING state`,
76611
- [(0, import_node_crypto49.randomUUID)(), pixel.id, hostname]
77254
+ [(0, import_node_crypto50.randomUUID)(), pixel.id, hostname]
76612
77255
  );
76613
77256
  if (domainResult.rows[0]?.state !== "approved") {
76614
77257
  await db.query(
@@ -76622,7 +77265,7 @@ async function ingestAnalyticsEvents(input) {
76622
77265
  `INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, hostname, rejected_count, reason_codes)
76623
77266
  VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,
76624
77267
  [
76625
- (0, import_node_crypto49.randomUUID)(),
77268
+ (0, import_node_crypto50.randomUUID)(),
76626
77269
  requestId,
76627
77270
  pixel.site_id,
76628
77271
  pixel.id,
@@ -76672,7 +77315,7 @@ async function ingestAnalyticsEvents(input) {
76672
77315
  $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33
76673
77316
  ) ON CONFLICT(site_id, event_id) DO NOTHING`,
76674
77317
  [
76675
- (0, import_node_crypto49.randomUUID)(),
77318
+ (0, import_node_crypto50.randomUUID)(),
76676
77319
  event2.eventId,
76677
77320
  pixel.site_id,
76678
77321
  pixel.id,
@@ -76730,7 +77373,7 @@ async function ingestAnalyticsEvents(input) {
76730
77373
  id, request_id, site_id, pixel_id, hostname, accepted_count, rejected_count, reason_codes
76731
77374
  ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb)`,
76732
77375
  [
76733
- (0, import_node_crypto49.randomUUID)(),
77376
+ (0, import_node_crypto50.randomUUID)(),
76734
77377
  requestId,
76735
77378
  pixel.site_id,
76736
77379
  pixel.id,
@@ -76762,7 +77405,7 @@ function normalizeGeographyCode(value, max) {
76762
77405
  return normalized && /^[A-Z0-9-]+$/.test(normalized) ? normalized.slice(0, max) : null;
76763
77406
  }
76764
77407
  function pageFingerprint(scope, siteId, filters) {
76765
- return (0, import_node_crypto49.createHash)("sha256").update(JSON.stringify({ scope, siteId, filters })).digest("base64url").slice(0, 18);
77408
+ return (0, import_node_crypto50.createHash)("sha256").update(JSON.stringify({ scope, siteId, filters })).digest("base64url").slice(0, 18);
76766
77409
  }
76767
77410
  function decodePageOffset(cursor, fingerprint2) {
76768
77411
  if (!cursor) return 0;
@@ -76812,7 +77455,7 @@ async function createAnalyticsConversion(input) {
76812
77455
  404
76813
77456
  );
76814
77457
  }
76815
- const id = (0, import_node_crypto49.randomUUID)();
77458
+ const id = (0, import_node_crypto50.randomUUID)();
76816
77459
  const resolvedPerson = input.sessionId ? await db.query(
76817
77460
  `SELECT e.person_id FROM analytics_identity_nodes n JOIN analytics_identity_edges e ON e.identity_node_id = n.id
76818
77461
  WHERE n.site_id = $1 AND n.kind = 'session_id' AND n.value_hmac = $2 ORDER BY e.confidence DESC LIMIT 1`,
@@ -77561,7 +78204,7 @@ async function createAnalyticsCampaignLink(input) {
77561
78204
  404
77562
78205
  );
77563
78206
  }
77564
- const shortCode = (input.shortCode?.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-") || (0, import_node_crypto49.randomBytes)(5).toString("base64url").toLowerCase()).slice(0, 48);
78207
+ const shortCode = (input.shortCode?.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-") || (0, import_node_crypto50.randomBytes)(5).toString("base64url").toLowerCase()).slice(0, 48);
77565
78208
  try {
77566
78209
  const result = await db.query(
77567
78210
  `INSERT INTO analytics_campaign_links(
@@ -77570,7 +78213,7 @@ async function createAnalyticsCampaignLink(input) {
77570
78213
  ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)
77571
78214
  RETURNING *, 0::int AS click_count`,
77572
78215
  [
77573
- (0, import_node_crypto49.randomUUID)(),
78216
+ (0, import_node_crypto50.randomUUID)(),
77574
78217
  input.siteId,
77575
78218
  input.pixelId ?? null,
77576
78219
  input.name.trim(),
@@ -77641,7 +78284,7 @@ async function resolveAnalyticsCampaignLink(shortCode, referrer) {
77641
78284
  if (!row) return null;
77642
78285
  await db.query(
77643
78286
  `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)]
78287
+ [(0, import_node_crypto50.randomUUID)(), row.id, normalizeAnalyticsUrl(referrer || void 0)]
77645
78288
  );
77646
78289
  return buildTaggedCampaignUrl(row);
77647
78290
  }
@@ -77659,14 +78302,14 @@ async function createAnalyticsForm(input) {
77659
78302
  404
77660
78303
  );
77661
78304
  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")}`;
78305
+ const slug4 = `${baseSlug}-${(0, import_node_crypto50.randomBytes)(3).toString("hex")}`;
78306
+ const publicId = `form_${(0, import_node_crypto50.randomBytes)(18).toString("base64url")}`;
77664
78307
  const result = await db.query(
77665
78308
  `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
78309
  VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8::jsonb,$9,$10,$11,$12,$13,$14)
77667
78310
  RETURNING *, 0::int AS submission_count`,
77668
78311
  [
77669
- (0, import_node_crypto49.randomUUID)(),
78312
+ (0, import_node_crypto50.randomUUID)(),
77670
78313
  publicId,
77671
78314
  input.siteId,
77672
78315
  input.pixelId,
@@ -77711,7 +78354,7 @@ async function getPublicAnalyticsForm(publicId) {
77711
78354
  return result.rows[0] ?? null;
77712
78355
  }
77713
78356
  async function recordAnalyticsFormSubmission(input) {
77714
- const id = (0, import_node_crypto49.randomUUID)();
78357
+ const id = (0, import_node_crypto50.randomUUID)();
77715
78358
  await getAnalyticsPool().query(
77716
78359
  `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
78360
  VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12::jsonb)`,
@@ -77742,7 +78385,7 @@ function sanitizeClickIds(value) {
77742
78385
  return output;
77743
78386
  }
77744
78387
  function identityHmac(kind, value) {
77745
- return (0, import_node_crypto49.createHmac)("sha256", getSessionSecret()).update(`${kind}:${value.trim().toLowerCase()}`).digest("hex");
78388
+ return (0, import_node_crypto50.createHmac)("sha256", getSessionSecret()).update(`${kind}:${value.trim().toLowerCase()}`).digest("hex");
77746
78389
  }
77747
78390
  async function linkAnalyticsFormIdentity(input) {
77748
78391
  const client2 = await getAnalyticsPool().connect();
@@ -77751,7 +78394,7 @@ async function linkAnalyticsFormIdentity(input) {
77751
78394
  const person = await client2.query(
77752
78395
  `INSERT INTO analytics_people(id, site_id, crm_person_ref) VALUES ($1,$2,$3)
77753
78396
  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]
78397
+ [(0, import_node_crypto50.randomUUID)(), input.siteId, input.crmPersonRef]
77755
78398
  );
77756
78399
  const personId = person.rows[0].id;
77757
78400
  const signals = [];
@@ -77798,7 +78441,7 @@ async function linkAnalyticsFormIdentity(input) {
77798
78441
  `INSERT INTO analytics_identity_nodes(id, site_id, kind, value_hmac) VALUES ($1,$2,$3,$4)
77799
78442
  ON CONFLICT(site_id, kind, value_hmac) DO UPDATE SET last_seen_at = now() RETURNING id`,
77800
78443
  [
77801
- (0, import_node_crypto49.randomUUID)(),
78444
+ (0, import_node_crypto50.randomUUID)(),
77802
78445
  input.siteId,
77803
78446
  signal.kind,
77804
78447
  identityHmac(signal.kind, signal.value)
@@ -77809,7 +78452,7 @@ async function linkAnalyticsFormIdentity(input) {
77809
78452
  VALUES ($1,$2,$3,$4,$5,$6)
77810
78453
  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
78454
  [
77812
- (0, import_node_crypto49.randomUUID)(),
78455
+ (0, import_node_crypto50.randomUUID)(),
77813
78456
  input.siteId,
77814
78457
  personId,
77815
78458
  node.rows[0].id,
@@ -77907,7 +78550,7 @@ async function createAnalyticsCrmImport(input) {
77907
78550
  const db = getAnalyticsPool();
77908
78551
  await requireEditor(db, input.siteId, input.userId);
77909
78552
  const client2 = await db.connect();
77910
- const id = (0, import_node_crypto49.randomUUID)();
78553
+ const id = (0, import_node_crypto50.randomUUID)();
77911
78554
  try {
77912
78555
  await client2.query("BEGIN");
77913
78556
  await client2.query(
@@ -77927,7 +78570,7 @@ async function createAnalyticsCrmImport(input) {
77927
78570
  await client2.query(
77928
78571
  `INSERT INTO analytics_crm_import_rows(id, import_id, crm_person_ref, payload_ciphertext)
77929
78572
  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]
78573
+ [(0, import_node_crypto50.randomUUID)(), id, row.crmPersonRef, row.payloadCiphertext]
77931
78574
  );
77932
78575
  }
77933
78576
  await client2.query("COMMIT");
@@ -77966,7 +78609,7 @@ async function createAnalyticsActivationDestination(input) {
77966
78609
  `INSERT INTO analytics_activation_destinations(id, site_id, platform, name, connection_ref, external_dataset_id, event_mapping, created_by_user_id)
77967
78610
  VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8) RETURNING *`,
77968
78611
  [
77969
- (0, import_node_crypto49.randomUUID)(),
78612
+ (0, import_node_crypto50.randomUUID)(),
77970
78613
  input.siteId,
77971
78614
  input.platform,
77972
78615
  input.name.trim(),
@@ -78018,7 +78661,7 @@ async function queueAnalyticsActivation(input) {
78018
78661
  `INSERT INTO analytics_activation_jobs(id, destination_id, conversion_id, person_id, payload_ciphertext)
78019
78662
  VALUES ($1,$2,$3,$4,$5) ON CONFLICT(destination_id, conversion_id) DO NOTHING`,
78020
78663
  [
78021
- (0, import_node_crypto49.randomUUID)(),
78664
+ (0, import_node_crypto50.randomUUID)(),
78022
78665
  destination.id,
78023
78666
  input.conversionId,
78024
78667
  input.personId ?? null,
@@ -78030,7 +78673,7 @@ async function queueAnalyticsActivation(input) {
78030
78673
  return queued;
78031
78674
  }
78032
78675
  async function queueAnalyticsFormDelivery(input) {
78033
- const id = (0, import_node_crypto49.randomUUID)();
78676
+ const id = (0, import_node_crypto50.randomUUID)();
78034
78677
  await getAnalyticsPool().query(
78035
78678
  `INSERT INTO analytics_form_delivery_jobs(id, submission_id, owner_user_id, payload_ciphertext, last_error_code)
78036
78679
  VALUES ($1,$2,$3,$4,$5)`,
@@ -78157,7 +78800,7 @@ async function analyticsHealth(siteId, userId) {
78157
78800
  }
78158
78801
  async function refreshAnalyticsDailyRollups(input) {
78159
78802
  const db = getAnalyticsPool();
78160
- const runId = (0, import_node_crypto49.randomUUID)();
78803
+ const runId = (0, import_node_crypto50.randomUUID)();
78161
78804
  await db.query(
78162
78805
  `INSERT INTO analytics_rollup_runs(id, window_start, window_end, status) VALUES ($1, $2, $3, 'running')`,
78163
78806
  [runId, input.start, input.end]
@@ -78354,7 +78997,7 @@ async function createAnalyticsExport(input) {
78354
78997
  `Generated ${(/* @__PURE__ */ new Date()).toISOString()} from the governed ${input.report} report contract.`
78355
78998
  ].join("\n");
78356
78999
  }
78357
- const id = (0, import_node_crypto49.randomUUID)();
79000
+ const id = (0, import_node_crypto50.randomUUID)();
78358
79001
  const inserted = await getAnalyticsPool().query(
78359
79002
  `INSERT INTO analytics_exports(id, site_id, requested_by_user_id, idempotency_key, report, format, filters, content)
78360
79003
  VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8)
@@ -78387,11 +79030,11 @@ async function createAnalyticsExport(input) {
78387
79030
  }
78388
79031
  return describeArtifact(replay.rows[0]);
78389
79032
  }
78390
- var import_node_crypto49, import_pg, AnalyticsRepositoryError, pool2, MAX_ENGAGED_MS, ENGAGED_SESSION_MS, blockedPropertyName, inferredFamilySql, ANALYTICS_CONTENT_SORTS, clickIdKeys;
79033
+ var import_node_crypto50, import_pg, AnalyticsRepositoryError, pool2, MAX_ENGAGED_MS, ENGAGED_SESSION_MS, blockedPropertyName, inferredFamilySql, ANALYTICS_CONTENT_SORTS, clickIdKeys;
78391
79034
  var init_analytics_repository = __esm({
78392
79035
  "src/api/analytics-repository.ts"() {
78393
79036
  "use strict";
78394
- import_node_crypto49 = require("crypto");
79037
+ import_node_crypto50 = require("crypto");
78395
79038
  import_pg = require("pg");
78396
79039
  init_session();
78397
79040
  init_analytics_attribution();
@@ -78630,8 +79273,8 @@ function dashboardCallbackUrl() {
78630
79273
  }
78631
79274
  async function createThorbitConnectUrl(userId) {
78632
79275
  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");
79276
+ const state = (0, import_node_crypto51.randomBytes)(32).toString("base64url");
79277
+ const stateHash = (0, import_node_crypto51.createHash)("sha256").update(state).digest("hex");
78635
79278
  await getAnalyticsPool().query(
78636
79279
  `INSERT INTO analytics_thorbit_connect_states(state_hash,user_id,expires_at)
78637
79280
  VALUES ($1,$2,now()+interval '10 minutes')
@@ -78646,9 +79289,9 @@ async function createThorbitConnectUrl(userId) {
78646
79289
  function verifyThorbitAssertion(token6) {
78647
79290
  const [encoded, signature] = token6.split(".");
78648
79291
  if (!encoded || !signature || !bridgeConfigured()) throw new Error("Invalid Thorbit assertion");
78649
- const expected = (0, import_node_crypto50.createHmac)("sha256", bridgeSecret()).update(encoded).digest();
79292
+ const expected = (0, import_node_crypto51.createHmac)("sha256", bridgeSecret()).update(encoded).digest();
78650
79293
  const actual = Buffer.from(signature, "base64url");
78651
- if (actual.length !== expected.length || !(0, import_node_crypto50.timingSafeEqual)(actual, expected)) {
79294
+ if (actual.length !== expected.length || !(0, import_node_crypto51.timingSafeEqual)(actual, expected)) {
78652
79295
  throw new Error("Invalid Thorbit assertion signature");
78653
79296
  }
78654
79297
  const parsed = AssertionSchema.safeParse(
@@ -78660,7 +79303,7 @@ function verifyThorbitAssertion(token6) {
78660
79303
  return parsed.data.entitlement;
78661
79304
  }
78662
79305
  async function consumeThorbitConnectCallback(input) {
78663
- const stateHash = (0, import_node_crypto50.createHash)("sha256").update(input.state).digest("hex");
79306
+ const stateHash = (0, import_node_crypto51.createHash)("sha256").update(input.state).digest("hex");
78664
79307
  const client2 = await getAnalyticsPool().connect();
78665
79308
  try {
78666
79309
  await client2.query("BEGIN");
@@ -78700,11 +79343,11 @@ async function disconnectAnalyticsEntitlement(userId) {
78700
79343
  [userId]
78701
79344
  );
78702
79345
  }
78703
- var import_node_crypto50, import_zod55, THORBIT_PRODUCT_URL, REFRESH_INTERVAL_MS, ELIGIBLE_GRACE_MS, TRIAL_LENGTH_MS, ThorbitEntitlementSchema, AssertionSchema;
79346
+ var import_node_crypto51, import_zod55, THORBIT_PRODUCT_URL, REFRESH_INTERVAL_MS, ELIGIBLE_GRACE_MS, TRIAL_LENGTH_MS, ThorbitEntitlementSchema, AssertionSchema;
78704
79347
  var init_analytics_entitlement = __esm({
78705
79348
  "src/api/analytics-entitlement.ts"() {
78706
79349
  "use strict";
78707
- import_node_crypto50 = require("crypto");
79350
+ import_node_crypto51 = require("crypto");
78708
79351
  import_zod55 = require("zod");
78709
79352
  init_analytics_repository();
78710
79353
  THORBIT_PRODUCT_URL = "https://thorbit.ai";
@@ -78844,14 +79487,14 @@ function renderPublicForm(form, placementUrl) {
78844
79487
  const brand = form.brand;
78845
79488
  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
79489
  }
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;
79490
+ 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
79491
  var init_analytics_routes = __esm({
78849
79492
  "src/api/analytics-routes.ts"() {
78850
79493
  "use strict";
78851
79494
  import_hono34 = require("hono");
78852
79495
  import_factory6 = require("hono/factory");
78853
79496
  import_zod56 = require("zod");
78854
- import_node_crypto51 = require("crypto");
79497
+ import_node_crypto52 = require("crypto");
78855
79498
  import_papaparse6 = __toESM(require("papaparse"), 1);
78856
79499
  init_api_auth();
78857
79500
  init_db();
@@ -79182,7 +79825,7 @@ var init_analytics_routes = __esm({
79182
79825
  const lastName = typeof data.last_name === "string" ? data.last_name.trim() : "";
79183
79826
  const fullName = [firstName, lastName].filter(Boolean).join(" ") || (typeof data.name === "string" ? data.name.trim() : "") || email || "Website lead";
79184
79827
  const identitySeed = email || `${fullName}:${Date.now()}`;
79185
- const suffix2 = (0, import_node_crypto51.createHash)("sha256").update(identitySeed).digest("hex").slice(0, 12);
79828
+ const suffix2 = (0, import_node_crypto52.createHash)("sha256").update(identitySeed).digest("hex").slice(0, 12);
79186
79829
  const path6 = `Leads/person-${suffix2}`;
79187
79830
  const { key: memoryKey, error: memoryKeyError } = await getOrCreateUserMemoryKey(user);
79188
79831
  const existing = memoryKey ? await memoryCall("getTool", { vault: "People", path: path6 }, memoryKey) : { ok: false, error: memoryKeyError || "memory_credential_unavailable" };
@@ -79281,9 +79924,9 @@ Submitted the published form.
79281
79924
  occurredAt: (/* @__PURE__ */ new Date()).toISOString()
79282
79925
  });
79283
79926
  const match = {
79284
- ...email ? { emailSha256: (0, import_node_crypto51.createHash)("sha256").update(email).digest("hex") } : {},
79927
+ ...email ? { emailSha256: (0, import_node_crypto52.createHash)("sha256").update(email).digest("hex") } : {},
79285
79928
  ...phone ? {
79286
- phoneSha256: (0, import_node_crypto51.createHash)("sha256").update(phone.replace(/\D/g, "")).digest("hex")
79929
+ phoneSha256: (0, import_node_crypto52.createHash)("sha256").update(phone.replace(/\D/g, "")).digest("hex")
79287
79930
  } : {}
79288
79931
  };
79289
79932
  const activationQueued = await queueAnalyticsActivation({
@@ -79711,7 +80354,7 @@ Submitted the published form.
79711
80354
  rejectedCount += 1;
79712
80355
  continue;
79713
80356
  }
79714
- const digest2 = (0, import_node_crypto51.createHash)("sha256").update(
80357
+ const digest2 = (0, import_node_crypto52.createHash)("sha256").update(
79715
80358
  `${parsed.data.sourceSystem}:${externalId || email || phone || `${fullName}:${index}`}`
79716
80359
  ).digest("hex").slice(0, 16);
79717
80360
  const path6 = `Leads/person-${digest2}`;
@@ -80169,19 +80812,19 @@ var init_analytics_delivery = __esm({
80169
80812
 
80170
80813
  // src/api/scheduled-artifact-owner.ts
80171
80814
  function scheduledArtifactOwnerIdForApiKey(apiKey) {
80172
- return (0, import_node_crypto52.createHash)("sha256").update(apiKey).digest("hex").slice(0, 24);
80815
+ return (0, import_node_crypto53.createHash)("sha256").update(apiKey).digest("hex").slice(0, 24);
80173
80816
  }
80174
- var import_node_crypto52;
80817
+ var import_node_crypto53;
80175
80818
  var init_scheduled_artifact_owner = __esm({
80176
80819
  "src/api/scheduled-artifact-owner.ts"() {
80177
80820
  "use strict";
80178
- import_node_crypto52 = require("crypto");
80821
+ import_node_crypto53 = require("crypto");
80179
80822
  }
80180
80823
  });
80181
80824
 
80182
80825
  // src/api/scheduled-run-view-links.ts
80183
80826
  async function ensureScheduledRunViewLinksSchema() {
80184
- if (schemaReady2) return;
80827
+ if (schemaReady3) return;
80185
80828
  const db = getDb();
80186
80829
  await db.execute(`
80187
80830
  CREATE TABLE IF NOT EXISTS scheduled_run_view_links (
@@ -80229,10 +80872,10 @@ async function ensureScheduledRunViewLinksSchema() {
80229
80872
  CREATE INDEX IF NOT EXISTS scheduled_run_view_links_expiry
80230
80873
  ON scheduled_run_view_links(expires_at, revoked_at)
80231
80874
  `);
80232
- schemaReady2 = true;
80875
+ schemaReady3 = true;
80233
80876
  }
80234
80877
  function tokenHash2(token6) {
80235
- return (0, import_node_crypto53.createHash)("sha256").update(token6).digest("hex");
80878
+ return (0, import_node_crypto54.createHash)("sha256").update(token6).digest("hex");
80236
80879
  }
80237
80880
  function mapRow(row) {
80238
80881
  return {
@@ -80252,9 +80895,9 @@ function mapRow(row) {
80252
80895
  async function createScheduledRunViewLink(input) {
80253
80896
  await ensureScheduledRunViewLinksSchema();
80254
80897
  const now = input.now ?? /* @__PURE__ */ new Date();
80255
- const token6 = (0, import_node_crypto53.randomBytes)(32).toString("base64url");
80898
+ const token6 = (0, import_node_crypto54.randomBytes)(32).toString("base64url");
80256
80899
  const record = {
80257
- shareId: (0, import_node_crypto53.randomUUID)(),
80900
+ shareId: (0, import_node_crypto54.randomUUID)(),
80258
80901
  ownerId: input.ownerId,
80259
80902
  runId: input.runId,
80260
80903
  artifactId: input.artifactId,
@@ -80317,13 +80960,13 @@ async function revokeScheduledRunViewLink(ownerId2, runId, shareId, now = /* @__
80317
80960
  });
80318
80961
  return result.rowsAffected > 0;
80319
80962
  }
80320
- var import_node_crypto53, schemaReady2;
80963
+ var import_node_crypto54, schemaReady3;
80321
80964
  var init_scheduled_run_view_links = __esm({
80322
80965
  "src/api/scheduled-run-view-links.ts"() {
80323
80966
  "use strict";
80324
- import_node_crypto53 = require("crypto");
80967
+ import_node_crypto54 = require("crypto");
80325
80968
  init_db();
80326
- schemaReady2 = false;
80969
+ schemaReady3 = false;
80327
80970
  }
80328
80971
  });
80329
80972
 
@@ -80387,15 +81030,15 @@ ${section(flags.finalCta && Boolean(finalCta), `<section class="section cta" id=
80387
81030
  html,
80388
81031
  filename: `${input.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "personal-authority"}.html`,
80389
81032
  bytes,
80390
- sha256: (0, import_node_crypto54.createHash)("sha256").update(html).digest("hex"),
81033
+ sha256: (0, import_node_crypto55.createHash)("sha256").update(html).digest("hex"),
80391
81034
  generatedAt: generatedAtIso
80392
81035
  };
80393
81036
  }
80394
- var import_node_crypto54;
81037
+ var import_node_crypto55;
80395
81038
  var init_render2 = __esm({
80396
81039
  "src/personal-authority/render.ts"() {
80397
81040
  "use strict";
80398
- import_node_crypto54 = require("crypto");
81041
+ import_node_crypto55 = require("crypto");
80399
81042
  }
80400
81043
  });
80401
81044
 
@@ -80498,15 +81141,15 @@ ${section2(flags.finalCta && Boolean(finalCta), `<section class="section cta" id
80498
81141
  html,
80499
81142
  filename: `${input.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "personal-authority"}.html`,
80500
81143
  bytes,
80501
- sha256: (0, import_node_crypto55.createHash)("sha256").update(html).digest("hex"),
81144
+ sha256: (0, import_node_crypto56.createHash)("sha256").update(html).digest("hex"),
80502
81145
  generatedAt: generatedAtIso
80503
81146
  };
80504
81147
  }
80505
- var import_node_crypto55, FONT_STACKS;
81148
+ var import_node_crypto56, FONT_STACKS;
80506
81149
  var init_render_v2 = __esm({
80507
81150
  "src/personal-authority/render-v2.ts"() {
80508
81151
  "use strict";
80509
- import_node_crypto55 = require("crypto");
81152
+ import_node_crypto56 = require("crypto");
80510
81153
  init_contracts();
80511
81154
  FONT_STACKS = Object.freeze({
80512
81155
  "editorial-serif": "Iowan Old Style, Baskerville, Times New Roman, serif",
@@ -80622,15 +81265,15 @@ body{overflow-x:hidden}.lead-story__body,.lead-support__item,.story-card__body{m
80622
81265
  @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
81266
  </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
81267
  const bytes = Buffer.byteLength(html);
80625
- const sha2565 = (0, import_node_crypto56.createHash)("sha256").update(html).digest("hex");
81268
+ const sha2565 = (0, import_node_crypto57.createHash)("sha256").update(html).digest("hex");
80626
81269
  const filename2 = `${brand.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "newsroom"}-news-site.html`;
80627
81270
  return { html, filename: filename2, bytes, sha256: sha2565, generatedAt: generatedAtIso };
80628
81271
  }
80629
- var import_node_crypto56;
81272
+ var import_node_crypto57;
80630
81273
  var init_render3 = __esm({
80631
81274
  "src/newsroom-publisher/render.ts"() {
80632
81275
  "use strict";
80633
- import_node_crypto56 = require("crypto");
81276
+ import_node_crypto57 = require("crypto");
80634
81277
  }
80635
81278
  });
80636
81279
 
@@ -80722,7 +81365,7 @@ function renderBlogArticleV1(input, config, generatedAt) {
80722
81365
  :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
81366
  </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
81367
  const bytes = Buffer.byteLength(html);
80725
- const sha2565 = (0, import_node_crypto57.createHash)("sha256").update(html).digest("hex");
81368
+ const sha2565 = (0, import_node_crypto58.createHash)("sha256").update(html).digest("hex");
80726
81369
  return {
80727
81370
  html,
80728
81371
  filename: "blog-article.html",
@@ -80732,11 +81375,11 @@ function renderBlogArticleV1(input, config, generatedAt) {
80732
81375
  generatedAt: generatedAtIso
80733
81376
  };
80734
81377
  }
80735
- var import_node_crypto57;
81378
+ var import_node_crypto58;
80736
81379
  var init_render4 = __esm({
80737
81380
  "src/blog-article/render.ts"() {
80738
81381
  "use strict";
80739
- import_node_crypto57 = require("crypto");
81382
+ import_node_crypto58 = require("crypto");
80740
81383
  }
80741
81384
  });
80742
81385
 
@@ -81067,7 +81710,7 @@ function policy6() {
81067
81710
  };
81068
81711
  }
81069
81712
  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)}`;
81713
+ 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
81714
  }
81072
81715
  async function createScheduledRunArtifact(args) {
81073
81716
  if (args.rendered.bytes > SCHEDULED_RUN_ARTIFACT_MAX_BYTES) {
@@ -81108,12 +81751,12 @@ async function readScheduledRunArtifact(args) {
81108
81751
  if (!window2 || window2.nextOffset !== null || window2.totalBytes > SCHEDULED_RUN_ARTIFACT_MAX_BYTES) return null;
81109
81752
  return window2.text;
81110
81753
  }
81111
- var import_node_crypto58, SCHEDULED_RUN_ARTIFACT_PREFIX, SCHEDULED_RUN_ARTIFACT_DOWNLOAD_TTL_MS, SCHEDULED_RUN_ARTIFACT_MAX_BYTES;
81754
+ var import_node_crypto59, SCHEDULED_RUN_ARTIFACT_PREFIX, SCHEDULED_RUN_ARTIFACT_DOWNLOAD_TTL_MS, SCHEDULED_RUN_ARTIFACT_MAX_BYTES;
81112
81755
  var init_scheduled_run_artifact_store = __esm({
81113
81756
  "src/scheduled-artifacts/scheduled-run-artifact-store.ts"() {
81114
81757
  "use strict";
81115
81758
  init_private_artifacts();
81116
- import_node_crypto58 = require("crypto");
81759
+ import_node_crypto59 = require("crypto");
81117
81760
  SCHEDULED_RUN_ARTIFACT_PREFIX = "scheduled-run-artifacts/";
81118
81761
  SCHEDULED_RUN_ARTIFACT_DOWNLOAD_TTL_MS = 15 * 60 * 1e3;
81119
81762
  SCHEDULED_RUN_ARTIFACT_MAX_BYTES = 2e6;
@@ -81191,357 +81834,6 @@ var init_public_scheduled_result_routes = __esm({
81191
81834
  }
81192
81835
  });
81193
81836
 
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
81837
  // src/api/scheduler-integration-auth.ts
81546
81838
  function signingSecret() {
81547
81839
  const secret2 = process.env.SCHEDULER_INTEGRATION_SIGNING_SECRET?.trim();