mcp-scraper 0.32.2 → 0.32.3

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.
@@ -37,7 +37,7 @@ import {
37
37
  sanitizeAttempts,
38
38
  sanitizeHarvestResult,
39
39
  transcribeMediaUrl
40
- } from "./chunk-XEED325T.js";
40
+ } from "./chunk-GNRHBIYZ.js";
41
41
  import {
42
42
  auditImages,
43
43
  buildLinkReport,
@@ -78,7 +78,7 @@ import {
78
78
  RawMapsOverviewSchema,
79
79
  RawMapsReviewStatsSchema
80
80
  } from "./chunk-V73MPRU6.js";
81
- import "./chunk-XLO5CZ6T.js";
81
+ import "./chunk-XF46AB5C.js";
82
82
  import {
83
83
  completeExtractJob,
84
84
  countSuccessfulPages,
@@ -13097,6 +13097,22 @@ function buildGoogleOrganicSearchUrl(input) {
13097
13097
  if (input.location) params.set("uule", encodeUule(normalizeLocation(input.location)));
13098
13098
  return `https://www.google.com/search?${params.toString()}`;
13099
13099
  }
13100
+ function mapsIdentifiersFromUrl(url) {
13101
+ const fid = url.match(/!1s(0x[0-9a-f]+):(0x[0-9a-f]+)/i) ?? url.match(/(0x[0-9a-f]+):(0x[0-9a-f]+)/i);
13102
+ if (fid) {
13103
+ try {
13104
+ return { cid: `${fid[1]}:${fid[2]}`, cidDecimal: BigInt(fid[2]).toString() };
13105
+ } catch {
13106
+ return { cid: `${fid[1]}:${fid[2]}`, cidDecimal: null };
13107
+ }
13108
+ }
13109
+ try {
13110
+ const decimal = new URL(url).searchParams.get("cid");
13111
+ return { cid: null, cidDecimal: decimal && /^\d+$/.test(decimal) ? decimal : null };
13112
+ } catch {
13113
+ return { cid: null, cidDecimal: null };
13114
+ }
13115
+ }
13100
13116
  var MapsSearchExtractor = class {
13101
13117
  constructor(driver) {
13102
13118
  this.driver = driver;
@@ -13172,13 +13188,15 @@ var MapsSearchExtractor = class {
13172
13188
  for (const card of cards) {
13173
13189
  if (results.length >= options.maxResults || seen.has(card.cardKey)) continue;
13174
13190
  seen.add(card.cardKey);
13191
+ const cardIds = mapsIdentifiersFromUrl(card.directionsUrl ?? "");
13192
+ const cardPlaceUrl = cardIds.cidDecimal ? `https://www.google.com/maps?cid=${cardIds.cidDecimal}` : card.placeUrl;
13175
13193
  const details = await this.openCardAndExtractDialog(page, card, options.includeServices);
13176
13194
  results.push({
13177
13195
  position: results.length + 1,
13178
13196
  name: card.name,
13179
- placeUrl: details?.placeUrl ?? card.placeUrl,
13180
- cid: details?.cid ?? card.cid,
13181
- cidDecimal: details?.cidDecimal ?? card.cidDecimal,
13197
+ placeUrl: details?.placeUrl ?? cardPlaceUrl,
13198
+ cid: details?.cid ?? cardIds.cid,
13199
+ cidDecimal: details?.cidDecimal ?? cardIds.cidDecimal,
13182
13200
  rating: details?.rating ?? card.rating,
13183
13201
  reviewCount: details?.reviewCount ?? card.reviewCount,
13184
13202
  category: details?.category ?? card.category,
@@ -13235,8 +13253,10 @@ var MapsSearchExtractor = class {
13235
13253
  const phone = lines.map((line) => line.match(phonePattern)?.[0]).find((value) => Boolean(value)) ?? null;
13236
13254
  const hoursStatus = normalize((lines.find((line) => /\b(?:open|closed|opens|closes)\b/i.test(line)) ?? "").split("\xB7")[0] ?? "");
13237
13255
  const address = lines.find((line) => addressPattern.test(line)) ?? null;
13238
- const websiteUrl = Array.from(card.querySelectorAll("a[href]")).map((anchor) => anchor.href).find((href) => /^https?:/i.test(href) && !/google\.|gstatic|googleusercontent/.test(href)) ?? null;
13256
+ const anchorHrefs = Array.from(card.querySelectorAll("a[href]")).map((anchor) => anchor.href);
13257
+ const websiteUrl = anchorHrefs.find((href) => /^https?:/i.test(href) && !/google\.|gstatic|googleusercontent/.test(href)) ?? null;
13239
13258
  if (!rating && !(phone && websiteUrl)) continue;
13259
+ const fidHref = anchorHrefs.find((href) => /(0x[0-9a-f]+):(0x[0-9a-f]+)/i.test(href)) ?? null;
13240
13260
  const key = `${name.toLowerCase()}|${lines.join(" ").toLowerCase()}`;
13241
13261
  if (seen.has(key)) continue;
13242
13262
  seen.add(key);
@@ -13256,7 +13276,7 @@ var MapsSearchExtractor = class {
13256
13276
  phone,
13257
13277
  hoursStatus,
13258
13278
  websiteUrl,
13259
- directionsUrl: `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent([name, address].filter(Boolean).join(", "))}`,
13279
+ directionsUrl: fidHref ?? `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent([name, address].filter(Boolean).join(", "))}`,
13260
13280
  metadata: lines.slice(0, 20)
13261
13281
  });
13262
13282
  }
@@ -19554,9 +19574,222 @@ if (state.key) { refreshSessions(); if (state.current) selectSession(state.curre
19554
19574
  }
19555
19575
 
19556
19576
  // src/api/stripe-routes.ts
19557
- import Stripe from "stripe";
19577
+ import Stripe2 from "stripe";
19558
19578
  import { Hono as Hono19 } from "hono";
19559
- var stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
19579
+
19580
+ // src/api/connected-account-billing.ts
19581
+ import Stripe from "stripe";
19582
+ var DEFAULT_CONNECTED_ACCOUNT_PRICE_ID = "price_1TtYPAS8aAcsk3TGHOgrDZiQ";
19583
+ var STRIPE_API_VERSION = "2026-02-25.clover";
19584
+ async function ensureConnectedAccountBillingSchema() {
19585
+ await getDb().execute(`CREATE TABLE IF NOT EXISTS connected_account_billing (
19586
+ user_id INTEGER PRIMARY KEY,
19587
+ stripe_subscription_id TEXT,
19588
+ stripe_subscription_item_id TEXT,
19589
+ price_id TEXT,
19590
+ quantity INTEGER NOT NULL DEFAULT 0,
19591
+ status TEXT NOT NULL DEFAULT 'pending',
19592
+ last_error_code TEXT,
19593
+ synced_at TEXT,
19594
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
19595
+ )`);
19596
+ }
19597
+ async function getConnectedAccountBillingState(userId) {
19598
+ await ensureConnectedAccountBillingSchema();
19599
+ const result = await getDb().execute({
19600
+ sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
19601
+ args: [Number(userId)]
19602
+ });
19603
+ return result.rows[0] ? result.rows[0] : null;
19604
+ }
19605
+ async function setConnectedAccountBillingState(input) {
19606
+ await ensureConnectedAccountBillingSchema();
19607
+ await getDb().execute({
19608
+ sql: `INSERT INTO connected_account_billing
19609
+ (user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, synced_at, updated_at)
19610
+ VALUES (?, ?, ?, ?, ?, ?, ?, CASE WHEN ? THEN datetime('now') ELSE NULL END, datetime('now'))
19611
+ ON CONFLICT(user_id) DO UPDATE SET
19612
+ stripe_subscription_id = excluded.stripe_subscription_id,
19613
+ stripe_subscription_item_id = excluded.stripe_subscription_item_id,
19614
+ price_id = excluded.price_id,
19615
+ quantity = excluded.quantity,
19616
+ status = excluded.status,
19617
+ last_error_code = excluded.last_error_code,
19618
+ synced_at = CASE WHEN ? THEN datetime('now') ELSE connected_account_billing.synced_at END,
19619
+ updated_at = datetime('now')`,
19620
+ args: [
19621
+ Number(input.userId),
19622
+ input.stripeSubscriptionId,
19623
+ input.stripeSubscriptionItemId,
19624
+ input.priceId,
19625
+ input.quantity,
19626
+ input.status,
19627
+ input.lastErrorCode,
19628
+ input.synced ? 1 : 0,
19629
+ input.synced ? 1 : 0
19630
+ ]
19631
+ });
19632
+ const state = await getConnectedAccountBillingState(input.userId);
19633
+ if (!state) throw new Error("connected account billing state was not persisted");
19634
+ return state;
19635
+ }
19636
+ var ConnectedAccountBillingError = class extends Error {
19637
+ constructor(message, code, status = 409) {
19638
+ super(message);
19639
+ this.code = code;
19640
+ this.status = status;
19641
+ this.name = "ConnectedAccountBillingError";
19642
+ }
19643
+ code;
19644
+ status;
19645
+ };
19646
+ function connectedAccountPriceId() {
19647
+ const configured = process.env.CONNECTED_ACCOUNT_PRICE_ID?.trim();
19648
+ if (configured) return configured;
19649
+ return DEFAULT_CONNECTED_ACCOUNT_PRICE_ID || null;
19650
+ }
19651
+ function connectedAccountBillingView(actualQuantity, state) {
19652
+ const quantity = Math.max(0, Math.round(actualQuantity));
19653
+ return {
19654
+ provider: "nango",
19655
+ billingMode: "flat_recurring_usd_plus_credits",
19656
+ priceId: connectedAccountPriceId(),
19657
+ unitAmountUsd: CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
19658
+ interval: "month",
19659
+ quantity,
19660
+ projectedMonthlyUsd: quantity * CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
19661
+ status: state?.status ?? "pending",
19662
+ lastErrorCode: state?.last_error_code ?? null,
19663
+ syncedAt: state?.synced_at ?? null,
19664
+ usage: CONNECTED_USAGE_RATE_POLICY.usage
19665
+ };
19666
+ }
19667
+ function findBasePlanItem(subscription) {
19668
+ return subscription.items.data.find((item) => !!item.price?.id && item.price.id in SUBSCRIPTION_TIERS);
19669
+ }
19670
+ function findConnectedAccountItem(subscription, priceId = connectedAccountPriceId()) {
19671
+ return priceId ? subscription.items.data.find((item) => item.price?.id === priceId) : void 0;
19672
+ }
19673
+ function liveSubscription(subscription) {
19674
+ return subscription.status === "active" || subscription.status === "trialing";
19675
+ }
19676
+ function safeStripeErrorCode(error) {
19677
+ if (error instanceof ConnectedAccountBillingError) return error.code;
19678
+ return "stripe_connection_billing_failed";
19679
+ }
19680
+ async function persistFailure(user, actualQuantity, error, subscriptionItemId = null) {
19681
+ await setConnectedAccountBillingState({
19682
+ userId: user.id,
19683
+ stripeSubscriptionId: user.subscription_id,
19684
+ stripeSubscriptionItemId: subscriptionItemId,
19685
+ priceId: connectedAccountPriceId(),
19686
+ quantity: actualQuantity,
19687
+ status: error instanceof ConnectedAccountBillingError && error.code !== "stripe_connection_billing_failed" ? "blocked" : "error",
19688
+ lastErrorCode: safeStripeErrorCode(error),
19689
+ synced: false
19690
+ });
19691
+ }
19692
+ async function reconcileConnectedAccountBilling(user, actualQuantity, stripeClient) {
19693
+ if (!Number.isSafeInteger(actualQuantity) || actualQuantity < 0) {
19694
+ throw new Error("actualQuantity must be a non-negative safe integer");
19695
+ }
19696
+ const priceId = connectedAccountPriceId();
19697
+ if (!priceId) {
19698
+ const error = new ConnectedAccountBillingError(
19699
+ "Connected-account billing is not configured.",
19700
+ "connected_account_price_not_configured",
19701
+ 503
19702
+ );
19703
+ await persistFailure(user, actualQuantity, error);
19704
+ throw error;
19705
+ }
19706
+ if (!user.subscription_id) {
19707
+ if (actualQuantity === 0) {
19708
+ const state = await setConnectedAccountBillingState({
19709
+ userId: user.id,
19710
+ stripeSubscriptionId: null,
19711
+ stripeSubscriptionItemId: null,
19712
+ priceId,
19713
+ quantity: 0,
19714
+ status: "synced",
19715
+ lastErrorCode: null,
19716
+ synced: true
19717
+ });
19718
+ return connectedAccountBillingView(0, state);
19719
+ }
19720
+ const error = new ConnectedAccountBillingError(
19721
+ "An active paid plan is required before connected accounts can be billed.",
19722
+ "paid_plan_required",
19723
+ 403
19724
+ );
19725
+ await persistFailure(user, actualQuantity, error);
19726
+ throw error;
19727
+ }
19728
+ const client2 = stripeClient ?? (() => {
19729
+ const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
19730
+ if (!secret2) {
19731
+ throw new ConnectedAccountBillingError(
19732
+ "Stripe is not configured.",
19733
+ "connected_account_price_not_configured",
19734
+ 503
19735
+ );
19736
+ }
19737
+ return new Stripe(secret2, { apiVersion: STRIPE_API_VERSION });
19738
+ })();
19739
+ let currentItem;
19740
+ try {
19741
+ const subscription = await client2.subscriptions.retrieve(user.subscription_id);
19742
+ currentItem = findConnectedAccountItem(subscription, priceId);
19743
+ if (!liveSubscription(subscription)) {
19744
+ throw new ConnectedAccountBillingError(
19745
+ "The plan subscription is not active.",
19746
+ "subscription_not_active",
19747
+ 402
19748
+ );
19749
+ }
19750
+ if (!findBasePlanItem(subscription)) {
19751
+ throw new ConnectedAccountBillingError(
19752
+ "The base plan item could not be found on this subscription.",
19753
+ "subscription_plan_item_missing",
19754
+ 409
19755
+ );
19756
+ }
19757
+ const currentQuantity = Math.max(0, currentItem?.quantity ?? 0);
19758
+ let resultingItem = currentItem;
19759
+ if (actualQuantity !== currentQuantity) {
19760
+ const updated = await client2.subscriptions.update(subscription.id, {
19761
+ items: actualQuantity === 0 ? currentItem ? [{ id: currentItem.id, deleted: true }] : [] : currentItem ? [{ id: currentItem.id, quantity: actualQuantity }] : [{ price: priceId, quantity: actualQuantity }],
19762
+ proration_behavior: "create_prorations"
19763
+ });
19764
+ resultingItem = findConnectedAccountItem(updated, priceId);
19765
+ }
19766
+ const state = await setConnectedAccountBillingState({
19767
+ userId: user.id,
19768
+ stripeSubscriptionId: subscription.id,
19769
+ stripeSubscriptionItemId: resultingItem?.id ?? null,
19770
+ priceId,
19771
+ quantity: actualQuantity,
19772
+ status: "synced",
19773
+ lastErrorCode: null,
19774
+ synced: true
19775
+ });
19776
+ return connectedAccountBillingView(actualQuantity, state);
19777
+ } catch (error) {
19778
+ await persistFailure(user, actualQuantity, error, currentItem?.id ?? null);
19779
+ if (error instanceof ConnectedAccountBillingError) throw error;
19780
+ throw new ConnectedAccountBillingError(
19781
+ "Unable to update connected-account billing in Stripe.",
19782
+ "stripe_connection_billing_failed",
19783
+ 503
19784
+ );
19785
+ }
19786
+ }
19787
+ async function currentConnectedAccountBillingView(userId, actualQuantity) {
19788
+ return connectedAccountBillingView(actualQuantity, await getConnectedAccountBillingState(userId));
19789
+ }
19790
+
19791
+ // src/api/stripe-routes.ts
19792
+ var stripe = new Stripe2(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
19560
19793
  var stripeApp = new Hono19();
19561
19794
  stripeApp.post("/webhooks", async (c) => {
19562
19795
  const sig = c.req.header("stripe-signature");
@@ -19573,15 +19806,23 @@ stripeApp.post("/webhooks", async (c) => {
19573
19806
  const invoice = event.data.object;
19574
19807
  const memLineId = invoice.lines.data.map(linePriceId).find((id) => id && id in MEMORY_PLANS);
19575
19808
  if (memLineId) return c.json({ received: true });
19576
- const lineTierId = invoice.lines.data.map(linePriceId).find((id) => id && id in SUBSCRIPTION_TIERS);
19577
- if (lineTierId) {
19578
- const tier = SUBSCRIPTION_TIERS[lineTierId];
19579
- const user = await resolveUser2(invoice.customer, invoice.customer_email ?? void 0);
19580
- if (user && invoice.id && !await ledgerExistsForStripePI(invoice.id)) {
19581
- await creditMc(user.id, tier.credits_mc, LedgerOperation.SUBSCRIPTION, `${tier.label} subscription credits`, invoice.id);
19582
- await setSubscriptionTier(user.id, tier.tier, tier.concurrency, invoice.subscription ?? user.subscription_id);
19583
- const credentials = await syncScheduledActionCredentials(user);
19584
- if (!credentials.ok) console.warn("[stripe] scheduled-action credential sync failed:", credentials.error ?? "unknown error");
19809
+ const invoiceHasTierLine = invoice.lines.data.some((l) => {
19810
+ const id = linePriceId(l);
19811
+ return id && id in SUBSCRIPTION_TIERS;
19812
+ });
19813
+ if (invoiceHasTierLine) {
19814
+ const subId = invoice.subscription;
19815
+ const liveBasePlanPriceId = subId ? findBasePlanItem(await stripe.subscriptions.retrieve(subId))?.price?.id : void 0;
19816
+ const tierPriceId = liveBasePlanPriceId ?? invoice.lines.data.map(linePriceId).find((id) => id && id in SUBSCRIPTION_TIERS);
19817
+ const tier = tierPriceId ? SUBSCRIPTION_TIERS[tierPriceId] : void 0;
19818
+ if (tier) {
19819
+ const user = await resolveUser2(invoice.customer, invoice.customer_email ?? void 0);
19820
+ if (user && invoice.id && !await ledgerExistsForStripePI(invoice.id)) {
19821
+ await creditMc(user.id, tier.credits_mc, LedgerOperation.SUBSCRIPTION, `${tier.label} subscription credits`, invoice.id);
19822
+ await setSubscriptionTier(user.id, tier.tier, tier.concurrency, subId ?? user.subscription_id);
19823
+ const credentials = await syncScheduledActionCredentials(user);
19824
+ if (!credentials.ok) console.warn("[stripe] scheduled-action credential sync failed:", credentials.error ?? "unknown error");
19825
+ }
19585
19826
  }
19586
19827
  }
19587
19828
  }
@@ -22832,8 +23073,8 @@ async function reconcileDiscoveredNangoConnections(identity, discovered) {
22832
23073
  sql: `
22833
23074
  INSERT INTO service_connections (
22834
23075
  id, user_id, provider_config_key, provider, transport, upstream_connection_id,
22835
- label, lifecycle_status, reconnect_required, legacy_owner_identity, created_at, updated_at
22836
- ) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
23076
+ label, lifecycle_status, reconnect_required, actions_enabled, legacy_owner_identity, created_at, updated_at
23077
+ ) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, 1, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
22837
23078
  ON CONFLICT(user_id, provider_config_key, upstream_connection_id) DO UPDATE SET
22838
23079
  provider = excluded.provider,
22839
23080
  label = COALESCE(excluded.label, service_connections.label),
@@ -24902,217 +25143,6 @@ async function verifySchedulerIntegrationRequest(request, rawBody) {
24902
25143
  return { requestId };
24903
25144
  }
24904
25145
 
24905
- // src/api/connected-account-billing.ts
24906
- import Stripe2 from "stripe";
24907
- var DEFAULT_CONNECTED_ACCOUNT_PRICE_ID = "price_1TtYPAS8aAcsk3TGHOgrDZiQ";
24908
- var STRIPE_API_VERSION = "2026-02-25.clover";
24909
- async function ensureConnectedAccountBillingSchema() {
24910
- await getDb().execute(`CREATE TABLE IF NOT EXISTS connected_account_billing (
24911
- user_id INTEGER PRIMARY KEY,
24912
- stripe_subscription_id TEXT,
24913
- stripe_subscription_item_id TEXT,
24914
- price_id TEXT,
24915
- quantity INTEGER NOT NULL DEFAULT 0,
24916
- status TEXT NOT NULL DEFAULT 'pending',
24917
- last_error_code TEXT,
24918
- synced_at TEXT,
24919
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
24920
- )`);
24921
- }
24922
- async function getConnectedAccountBillingState(userId) {
24923
- await ensureConnectedAccountBillingSchema();
24924
- const result = await getDb().execute({
24925
- sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
24926
- args: [Number(userId)]
24927
- });
24928
- return result.rows[0] ? result.rows[0] : null;
24929
- }
24930
- async function setConnectedAccountBillingState(input) {
24931
- await ensureConnectedAccountBillingSchema();
24932
- await getDb().execute({
24933
- sql: `INSERT INTO connected_account_billing
24934
- (user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, synced_at, updated_at)
24935
- VALUES (?, ?, ?, ?, ?, ?, ?, CASE WHEN ? THEN datetime('now') ELSE NULL END, datetime('now'))
24936
- ON CONFLICT(user_id) DO UPDATE SET
24937
- stripe_subscription_id = excluded.stripe_subscription_id,
24938
- stripe_subscription_item_id = excluded.stripe_subscription_item_id,
24939
- price_id = excluded.price_id,
24940
- quantity = excluded.quantity,
24941
- status = excluded.status,
24942
- last_error_code = excluded.last_error_code,
24943
- synced_at = CASE WHEN ? THEN datetime('now') ELSE connected_account_billing.synced_at END,
24944
- updated_at = datetime('now')`,
24945
- args: [
24946
- Number(input.userId),
24947
- input.stripeSubscriptionId,
24948
- input.stripeSubscriptionItemId,
24949
- input.priceId,
24950
- input.quantity,
24951
- input.status,
24952
- input.lastErrorCode,
24953
- input.synced ? 1 : 0,
24954
- input.synced ? 1 : 0
24955
- ]
24956
- });
24957
- const state = await getConnectedAccountBillingState(input.userId);
24958
- if (!state) throw new Error("connected account billing state was not persisted");
24959
- return state;
24960
- }
24961
- var ConnectedAccountBillingError = class extends Error {
24962
- constructor(message, code, status = 409) {
24963
- super(message);
24964
- this.code = code;
24965
- this.status = status;
24966
- this.name = "ConnectedAccountBillingError";
24967
- }
24968
- code;
24969
- status;
24970
- };
24971
- function connectedAccountPriceId() {
24972
- const configured = process.env.CONNECTED_ACCOUNT_PRICE_ID?.trim();
24973
- if (configured) return configured;
24974
- return DEFAULT_CONNECTED_ACCOUNT_PRICE_ID || null;
24975
- }
24976
- function connectedAccountBillingView(actualQuantity, state) {
24977
- const quantity = Math.max(0, Math.round(actualQuantity));
24978
- return {
24979
- provider: "nango",
24980
- billingMode: "flat_recurring_usd_plus_credits",
24981
- priceId: connectedAccountPriceId(),
24982
- unitAmountUsd: CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
24983
- interval: "month",
24984
- quantity,
24985
- projectedMonthlyUsd: quantity * CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
24986
- status: state?.status ?? "pending",
24987
- lastErrorCode: state?.last_error_code ?? null,
24988
- syncedAt: state?.synced_at ?? null,
24989
- usage: CONNECTED_USAGE_RATE_POLICY.usage
24990
- };
24991
- }
24992
- function findBasePlanItem(subscription) {
24993
- return subscription.items.data.find((item) => !!item.price?.id && item.price.id in SUBSCRIPTION_TIERS);
24994
- }
24995
- function findConnectedAccountItem(subscription, priceId = connectedAccountPriceId()) {
24996
- return priceId ? subscription.items.data.find((item) => item.price?.id === priceId) : void 0;
24997
- }
24998
- function liveSubscription(subscription) {
24999
- return subscription.status === "active" || subscription.status === "trialing";
25000
- }
25001
- function safeStripeErrorCode(error) {
25002
- if (error instanceof ConnectedAccountBillingError) return error.code;
25003
- return "stripe_connection_billing_failed";
25004
- }
25005
- async function persistFailure(user, actualQuantity, error, subscriptionItemId = null) {
25006
- await setConnectedAccountBillingState({
25007
- userId: user.id,
25008
- stripeSubscriptionId: user.subscription_id,
25009
- stripeSubscriptionItemId: subscriptionItemId,
25010
- priceId: connectedAccountPriceId(),
25011
- quantity: actualQuantity,
25012
- status: error instanceof ConnectedAccountBillingError && error.code !== "stripe_connection_billing_failed" ? "blocked" : "error",
25013
- lastErrorCode: safeStripeErrorCode(error),
25014
- synced: false
25015
- });
25016
- }
25017
- async function reconcileConnectedAccountBilling(user, actualQuantity, stripeClient) {
25018
- if (!Number.isSafeInteger(actualQuantity) || actualQuantity < 0) {
25019
- throw new Error("actualQuantity must be a non-negative safe integer");
25020
- }
25021
- const priceId = connectedAccountPriceId();
25022
- if (!priceId) {
25023
- const error = new ConnectedAccountBillingError(
25024
- "Connected-account billing is not configured.",
25025
- "connected_account_price_not_configured",
25026
- 503
25027
- );
25028
- await persistFailure(user, actualQuantity, error);
25029
- throw error;
25030
- }
25031
- if (!user.subscription_id) {
25032
- if (actualQuantity === 0) {
25033
- const state = await setConnectedAccountBillingState({
25034
- userId: user.id,
25035
- stripeSubscriptionId: null,
25036
- stripeSubscriptionItemId: null,
25037
- priceId,
25038
- quantity: 0,
25039
- status: "synced",
25040
- lastErrorCode: null,
25041
- synced: true
25042
- });
25043
- return connectedAccountBillingView(0, state);
25044
- }
25045
- const error = new ConnectedAccountBillingError(
25046
- "An active paid plan is required before connected accounts can be billed.",
25047
- "paid_plan_required",
25048
- 403
25049
- );
25050
- await persistFailure(user, actualQuantity, error);
25051
- throw error;
25052
- }
25053
- const client2 = stripeClient ?? (() => {
25054
- const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
25055
- if (!secret2) {
25056
- throw new ConnectedAccountBillingError(
25057
- "Stripe is not configured.",
25058
- "connected_account_price_not_configured",
25059
- 503
25060
- );
25061
- }
25062
- return new Stripe2(secret2, { apiVersion: STRIPE_API_VERSION });
25063
- })();
25064
- let currentItem;
25065
- try {
25066
- const subscription = await client2.subscriptions.retrieve(user.subscription_id);
25067
- currentItem = findConnectedAccountItem(subscription, priceId);
25068
- if (!liveSubscription(subscription)) {
25069
- throw new ConnectedAccountBillingError(
25070
- "The plan subscription is not active.",
25071
- "subscription_not_active",
25072
- 402
25073
- );
25074
- }
25075
- if (!findBasePlanItem(subscription)) {
25076
- throw new ConnectedAccountBillingError(
25077
- "The base plan item could not be found on this subscription.",
25078
- "subscription_plan_item_missing",
25079
- 409
25080
- );
25081
- }
25082
- const currentQuantity = Math.max(0, currentItem?.quantity ?? 0);
25083
- let resultingItem = currentItem;
25084
- if (actualQuantity !== currentQuantity) {
25085
- const updated = await client2.subscriptions.update(subscription.id, {
25086
- items: actualQuantity === 0 ? currentItem ? [{ id: currentItem.id, deleted: true }] : [] : currentItem ? [{ id: currentItem.id, quantity: actualQuantity }] : [{ price: priceId, quantity: actualQuantity }],
25087
- proration_behavior: "create_prorations"
25088
- });
25089
- resultingItem = findConnectedAccountItem(updated, priceId);
25090
- }
25091
- const state = await setConnectedAccountBillingState({
25092
- userId: user.id,
25093
- stripeSubscriptionId: subscription.id,
25094
- stripeSubscriptionItemId: resultingItem?.id ?? null,
25095
- priceId,
25096
- quantity: actualQuantity,
25097
- status: "synced",
25098
- lastErrorCode: null,
25099
- synced: true
25100
- });
25101
- return connectedAccountBillingView(actualQuantity, state);
25102
- } catch (error) {
25103
- await persistFailure(user, actualQuantity, error, currentItem?.id ?? null);
25104
- if (error instanceof ConnectedAccountBillingError) throw error;
25105
- throw new ConnectedAccountBillingError(
25106
- "Unable to update connected-account billing in Stripe.",
25107
- "stripe_connection_billing_failed",
25108
- 503
25109
- );
25110
- }
25111
- }
25112
- async function currentConnectedAccountBillingView(userId, actualQuantity) {
25113
- return connectedAccountBillingView(actualQuantity, await getConnectedAccountBillingState(userId));
25114
- }
25115
-
25116
25146
  // src/api/server.ts
25117
25147
  var secureCookies2 = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
25118
25148
  var isProduction2 = secureCookies2;
@@ -26097,7 +26127,7 @@ app.post("/schedule-connections/actions/slack/send-message", auth2, requireInteg
26097
26127
  user.email,
26098
26128
  connectionId,
26099
26129
  { channel: body.channel, text: body.text },
26100
- void 0,
26130
+ "send-message",
26101
26131
  connectedActionIdempotencyKey(c)
26102
26132
  );
26103
26133
  return c.json({ ok: true, result });
@@ -26117,7 +26147,7 @@ app.post("/schedule-connections/actions/gmail/send-message", auth2, requireInteg
26117
26147
  user.email,
26118
26148
  connectionId,
26119
26149
  { to: body.to, subject: body.subject, body: body.body },
26120
- void 0,
26150
+ "send-message",
26121
26151
  connectedActionIdempotencyKey(c)
26122
26152
  );
26123
26153
  return c.json({ ok: true, result });
@@ -26125,6 +26155,76 @@ app.post("/schedule-connections/actions/gmail/send-message", auth2, requireInteg
26125
26155
  return scheduleConnectionError(c, err, "Unable to send the email.");
26126
26156
  }
26127
26157
  });
26158
+ function toolResultText(result) {
26159
+ if (!result || typeof result !== "object") return null;
26160
+ const content = result.content;
26161
+ if (!Array.isArray(content)) return null;
26162
+ const textItem = content.find((item) => item && typeof item === "object" && item.type === "text");
26163
+ const text2 = textItem?.text;
26164
+ return typeof text2 === "string" ? text2 : null;
26165
+ }
26166
+ function parseEmailAddress(headerValue) {
26167
+ const value = headerValue?.trim();
26168
+ if (!value) return null;
26169
+ const angled = value.match(/^"?([^"<]*?)"?\s*<([^>]+)>$/);
26170
+ if (angled) return { email: angled[2].trim().toLowerCase(), name: angled[1].trim() || null };
26171
+ const bare = value.match(/^([^\s<>]+@[^\s<>]+)$/);
26172
+ return bare ? { email: bare[1].trim().toLowerCase(), name: null } : null;
26173
+ }
26174
+ app.post("/schedule-connections/actions/gmail/search-contacts", auth2, requireIntegrationsTier, async (c) => {
26175
+ const user = c.get("user");
26176
+ const body = await c.req.json().catch(() => ({}));
26177
+ const connectionId = providerConfigKeyFrom(body.connectionId);
26178
+ const query = typeof body.query === "string" ? body.query.trim() : "";
26179
+ const maxMessages = Math.min(150, Math.max(1, Number.isFinite(body.maxMessages) ? Number(body.maxMessages) : 50));
26180
+ const pageToken = typeof body.pageToken === "string" ? body.pageToken : void 0;
26181
+ if (!connectionId || !query) return c.json({ ok: false, error: "connectionId and query are required." }, 400);
26182
+ try {
26183
+ const listRaw = await callScheduleConnectionRead(user.email, connectionId, "list-messages", { q: query, maxResults: maxMessages, pageToken });
26184
+ const listText = toolResultText(listRaw);
26185
+ const list = listText ? JSON.parse(listText) : {};
26186
+ const ids = (list.messages ?? []).map((m) => m.id);
26187
+ const messages = [];
26188
+ const contacts = /* @__PURE__ */ new Map();
26189
+ for (const id of ids) {
26190
+ const msgRaw = await callScheduleConnectionRead(user.email, connectionId, "get-message", { id, format: "metadata" });
26191
+ const msgText = toolResultText(msgRaw);
26192
+ if (!msgText) continue;
26193
+ const msg = JSON.parse(msgText);
26194
+ const headers = new Map((msg.payload?.headers ?? []).map((h) => [h.name.toLowerCase(), h.value]));
26195
+ const date = headers.get("date") ?? null;
26196
+ const fromRaw = headers.get("from");
26197
+ const from = parseEmailAddress(fromRaw);
26198
+ const subject = headers.get("subject") ?? null;
26199
+ messages.push({ id, date, from: fromRaw ?? null, to: headers.get("to") ?? null, subject, snippet: msg.snippet ?? null });
26200
+ if (from) {
26201
+ const domain = from.email.split("@")[1] ?? null;
26202
+ const existing = contacts.get(from.email);
26203
+ if (existing) {
26204
+ existing.messageCount += 1;
26205
+ existing.name = existing.name ?? from.name;
26206
+ if (subject && existing.sampleSubjects.length < 5 && !existing.sampleSubjects.includes(subject)) existing.sampleSubjects.push(subject);
26207
+ if (date && (!existing.lastSeen || date > existing.lastSeen)) existing.lastSeen = date;
26208
+ if (date && (!existing.firstSeen || date < existing.firstSeen)) existing.firstSeen = date;
26209
+ } else {
26210
+ contacts.set(from.email, { email: from.email, name: from.name, domain, messageCount: 1, firstSeen: date, lastSeen: date, sampleSubjects: subject ? [subject] : [] });
26211
+ }
26212
+ }
26213
+ }
26214
+ const totalMatches = list.resultSizeEstimate;
26215
+ return c.json({
26216
+ ok: true,
26217
+ totalMatches,
26218
+ messagesFetched: messages.length,
26219
+ truncated: typeof totalMatches === "number" ? totalMatches > messages.length : Boolean(list.nextPageToken),
26220
+ nextPageToken: list.nextPageToken ?? null,
26221
+ messages,
26222
+ contacts: Array.from(contacts.values())
26223
+ });
26224
+ } catch (err) {
26225
+ return scheduleConnectionError(c, err, "Unable to search Gmail contacts.");
26226
+ }
26227
+ });
26128
26228
  app.post("/schedule-connections/actions/google-calendar/create-event", auth2, requireIntegrationsTier, async (c) => {
26129
26229
  const user = c.get("user");
26130
26230
  const body = await c.req.json().catch(() => ({}));
@@ -26143,7 +26243,7 @@ app.post("/schedule-connections/actions/google-calendar/create-event", auth2, re
26143
26243
  start: { dateTime: body.startDateTime, timeZone },
26144
26244
  end: { dateTime: body.endDateTime, timeZone },
26145
26245
  attendees
26146
- }, void 0, connectedActionIdempotencyKey(c));
26246
+ }, "create-event", connectedActionIdempotencyKey(c));
26147
26247
  return c.json({ ok: true, result });
26148
26248
  } catch (err) {
26149
26249
  return scheduleConnectionError(c, err, "Unable to create the calendar event.");
@@ -26163,7 +26263,7 @@ app.post("/schedule-connections/actions/zoom/create-meeting", auth2, requireInte
26163
26263
  durationMinutes: typeof body.durationMinutes === "number" ? body.durationMinutes : 30,
26164
26264
  timezone: typeof body.timezone === "string" ? body.timezone : void 0,
26165
26265
  agenda: body.agenda
26166
- }, void 0, connectedActionIdempotencyKey(c));
26266
+ }, "create-meeting", connectedActionIdempotencyKey(c));
26167
26267
  return c.json({ ok: true, result });
26168
26268
  } catch (err) {
26169
26269
  return scheduleConnectionError(c, err, "Unable to create the Zoom meeting.");
@@ -27191,6 +27291,22 @@ app.post("/billing/concurrency/checkout", requireAllowedOrigin, sessionAuth, asy
27191
27291
  return c.json({ error: message }, 500);
27192
27292
  }
27193
27293
  });
27294
+ async function chargeTierChangeNow(stripeClient, subscriptionId, customerId) {
27295
+ try {
27296
+ const draft = await stripeClient.invoices.create({
27297
+ customer: customerId,
27298
+ subscription: subscriptionId,
27299
+ auto_advance: true,
27300
+ collection_method: "charge_automatically"
27301
+ });
27302
+ const finalized = await stripeClient.invoices.finalizeInvoice(draft.id);
27303
+ if (finalized.amount_due <= 0 || finalized.status === "paid") return { ok: true, amountDue: finalized.amount_due };
27304
+ const paid = await stripeClient.invoices.pay(finalized.id);
27305
+ return { ok: paid.status === "paid", amountDue: paid.amount_due };
27306
+ } catch (err) {
27307
+ return { ok: false, amountDue: 0, error: err instanceof Error ? err.message : "Unable to charge the plan change immediately." };
27308
+ }
27309
+ }
27194
27310
  app.post("/billing/subscribe", requireAllowedOrigin, sessionAuth, async (c) => {
27195
27311
  try {
27196
27312
  const user = c.get("sessionUser");
@@ -27214,7 +27330,16 @@ app.post("/billing/subscribe", requireAllowedOrigin, sessionAuth, async (c) => {
27214
27330
  items: [{ id: itemId, price: tier.price_id }],
27215
27331
  proration_behavior: "create_prorations"
27216
27332
  });
27217
- return c.json({ updated: true, tier: tier.tier });
27333
+ const billed = await chargeTierChangeNow(stripeClient, user.subscription_id, customerId);
27334
+ if (!billed.ok) {
27335
+ return c.json({
27336
+ updated: true,
27337
+ tier: tier.tier,
27338
+ billed: false,
27339
+ error: billed.error ?? "Plan changed, but the prorated charge could not be collected. Update your payment method and it will retry automatically."
27340
+ });
27341
+ }
27342
+ return c.json({ updated: true, tier: tier.tier, billed: true });
27218
27343
  }
27219
27344
  }
27220
27345
  const session = await stripeClient.checkout.sessions.create({
@@ -27331,7 +27456,16 @@ app.post("/billing/subscribe/terminal-checkout", auth2, async (c) => {
27331
27456
  items: [{ id: itemId, price: tier.price_id }],
27332
27457
  proration_behavior: "create_prorations"
27333
27458
  });
27334
- return c.json({ updated: true, tier: tier.tier, message: `Switched to ${tier.label} (prorated). Credits and concurrency update on the next invoice.` });
27459
+ const billed = await chargeTierChangeNow(stripeClient, user.subscription_id, customerId);
27460
+ if (!billed.ok) {
27461
+ return c.json({
27462
+ updated: true,
27463
+ tier: tier.tier,
27464
+ billed: false,
27465
+ message: `Switched to ${tier.label} (prorated), but the prorated charge could not be collected: ${billed.error ?? "unknown error"}. Update your payment method and it will retry automatically.`
27466
+ });
27467
+ }
27468
+ return c.json({ updated: true, tier: tier.tier, billed: true, message: `Switched to ${tier.label} and charged the prorated difference immediately \u2014 credits and concurrency are live now.` });
27335
27469
  }
27336
27470
  }
27337
27471
  const session = await stripeClient.checkout.sessions.create({
@@ -27633,4 +27767,4 @@ app.get("/blog/:slug/", (c) => {
27633
27767
  export {
27634
27768
  app
27635
27769
  };
27636
- //# sourceMappingURL=server-LTZDJHFG.js.map
27770
+ //# sourceMappingURL=server-NHLEIKDZ.js.map