mcp-scraper 0.32.1 → 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.
- package/README.md +1 -1
- package/dist/bin/api-server.cjs +434 -252
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +3 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +3 -3
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +44 -1
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +3 -3
- package/dist/bin/paa-harvest.cjs +4 -2
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +2 -2
- package/dist/{chunk-RDCCU7CQ.js → chunk-AEMABLEL.js} +5 -4
- package/dist/chunk-AEMABLEL.js.map +1 -0
- package/dist/{chunk-2FY4VBAY.js → chunk-GNRHBIYZ.js} +44 -3
- package/dist/chunk-GNRHBIYZ.js.map +1 -0
- package/dist/{chunk-55M5CE5L.js → chunk-MKZIL4EA.js} +2 -2
- package/dist/{chunk-PTHY7NYD.js → chunk-V73MPRU6.js} +3 -1
- package/dist/chunk-V73MPRU6.js.map +1 -0
- package/dist/chunk-XF46AB5C.js +7 -0
- package/dist/chunk-XF46AB5C.js.map +1 -0
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{server-GXTYKIAX.js → server-NHLEIKDZ.js} +386 -246
- package/dist/server-NHLEIKDZ.js.map +1 -0
- package/dist/{worker-7DJYUACV.js → worker-ZZCZNY3R.js} +3 -3
- package/docs/mcp-tool-manifest.generated.json +165 -3
- package/package.json +1 -1
- package/dist/chunk-2FY4VBAY.js.map +0 -1
- package/dist/chunk-PTHY7NYD.js.map +0 -1
- package/dist/chunk-RDCCU7CQ.js.map +0 -1
- package/dist/chunk-VJBS6RPB.js +0 -7
- package/dist/chunk-VJBS6RPB.js.map +0 -1
- package/dist/server-GXTYKIAX.js.map +0 -1
- /package/dist/{chunk-55M5CE5L.js.map → chunk-MKZIL4EA.js.map} +0 -0
- /package/dist/{worker-7DJYUACV.js.map → worker-ZZCZNY3R.js.map} +0 -0
package/dist/bin/api-server.cjs
CHANGED
|
@@ -13834,8 +13834,8 @@ function positiveIntFromEnv(name, fallback) {
|
|
|
13834
13834
|
function proxyIdSuffix(proxyId) {
|
|
13835
13835
|
return proxyId ? proxyId.slice(-6) : null;
|
|
13836
13836
|
}
|
|
13837
|
-
function serpArrivalUrl(googleUrl) {
|
|
13838
|
-
const base = process.env.SERP_REDIRECT_BASE?.trim().replace(/\/+$/, "");
|
|
13837
|
+
function serpArrivalUrl(googleUrl, baseOverride) {
|
|
13838
|
+
const base = (baseOverride ?? process.env.SERP_REDIRECT_BASE)?.trim().replace(/\/+$/, "");
|
|
13839
13839
|
if (!base) return googleUrl;
|
|
13840
13840
|
return `${base}/g/${Buffer.from(googleUrl, "utf8").toString("base64url")}`;
|
|
13841
13841
|
}
|
|
@@ -17394,6 +17394,8 @@ var init_schemas3 = __esm({
|
|
|
17394
17394
|
includeServices: import_zod17.z.boolean().default(false),
|
|
17395
17395
|
proxyMode: import_zod17.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
|
|
17396
17396
|
proxyZip: import_zod17.z.string().regex(/^\d{5}$/).optional(),
|
|
17397
|
+
serpRedirect: import_zod17.z.boolean().optional(),
|
|
17398
|
+
forceDirectEgress: import_zod17.z.boolean().optional(),
|
|
17397
17399
|
debug: import_zod17.z.boolean().default(false),
|
|
17398
17400
|
kernelApiKey: import_zod17.z.string().optional(),
|
|
17399
17401
|
kernelProxyId: import_zod17.z.string().optional(),
|
|
@@ -19260,14 +19262,32 @@ function buildGoogleOrganicSearchUrl(input) {
|
|
|
19260
19262
|
if (input.location) params.set("uule", encodeUule(normalizeLocation(input.location)));
|
|
19261
19263
|
return `https://www.google.com/search?${params.toString()}`;
|
|
19262
19264
|
}
|
|
19263
|
-
|
|
19265
|
+
function mapsIdentifiersFromUrl(url) {
|
|
19266
|
+
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);
|
|
19267
|
+
if (fid) {
|
|
19268
|
+
try {
|
|
19269
|
+
return { cid: `${fid[1]}:${fid[2]}`, cidDecimal: BigInt(fid[2]).toString() };
|
|
19270
|
+
} catch {
|
|
19271
|
+
return { cid: `${fid[1]}:${fid[2]}`, cidDecimal: null };
|
|
19272
|
+
}
|
|
19273
|
+
}
|
|
19274
|
+
try {
|
|
19275
|
+
const decimal = new URL(url).searchParams.get("cid");
|
|
19276
|
+
return { cid: null, cidDecimal: decimal && /^\d+$/.test(decimal) ? decimal : null };
|
|
19277
|
+
} catch {
|
|
19278
|
+
return { cid: null, cidDecimal: null };
|
|
19279
|
+
}
|
|
19280
|
+
}
|
|
19281
|
+
var LOCAL_RESULTS_PAGE_LIMIT, LOCAL_RESULTS_WAIT_MS, MAPS_REDIRECT_BASE, MapsSearchExtractor;
|
|
19264
19282
|
var init_MapsSearchExtractor = __esm({
|
|
19265
19283
|
"src/extractor/MapsSearchExtractor.ts"() {
|
|
19266
19284
|
"use strict";
|
|
19267
19285
|
init_errors();
|
|
19286
|
+
init_BrowserDriver();
|
|
19268
19287
|
init_uule();
|
|
19269
19288
|
LOCAL_RESULTS_PAGE_LIMIT = 8;
|
|
19270
19289
|
LOCAL_RESULTS_WAIT_MS = 1e3;
|
|
19290
|
+
MAPS_REDIRECT_BASE = "https://serp-redirector.vercel.app";
|
|
19271
19291
|
MapsSearchExtractor = class {
|
|
19272
19292
|
constructor(driver) {
|
|
19273
19293
|
this.driver = driver;
|
|
@@ -19277,21 +19297,23 @@ var init_MapsSearchExtractor = __esm({
|
|
|
19277
19297
|
const startMs = Date.now();
|
|
19278
19298
|
const searchQuery = [options.query, options.location].filter(Boolean).join(" ");
|
|
19279
19299
|
const searchUrl = buildGoogleOrganicSearchUrl(options);
|
|
19300
|
+
const keepDefaultProxy = !options.forceDirectEgress && options.proxyMode !== "location" && options.proxyMode !== "configured" && !options.kernelProxyId;
|
|
19280
19301
|
const config = {
|
|
19281
19302
|
headless: options.headless,
|
|
19282
19303
|
kernelApiKey: options.kernelApiKey,
|
|
19283
19304
|
kernelProxyId: options.kernelProxyId,
|
|
19284
19305
|
kernelProxyResolution: options.kernelProxyResolution,
|
|
19285
19306
|
proxyMode: options.proxyMode,
|
|
19286
|
-
keepDefaultProxy
|
|
19307
|
+
keepDefaultProxy,
|
|
19287
19308
|
viewport: { width: 1280, height: 900 },
|
|
19288
19309
|
locale: `${options.hl}-${options.gl.toUpperCase()}`,
|
|
19289
19310
|
debug: options.debug
|
|
19290
19311
|
};
|
|
19312
|
+
const navUrl = options.serpRedirect ? serpArrivalUrl(searchUrl, process.env.SERP_REDIRECT_BASE?.trim() || MAPS_REDIRECT_BASE) : searchUrl;
|
|
19291
19313
|
try {
|
|
19292
19314
|
await this.driver.launch(config);
|
|
19293
19315
|
const page = this.driver.getPage();
|
|
19294
|
-
await page.goto(
|
|
19316
|
+
await page.goto(navUrl, { waitUntil: "domcontentloaded", timeout: 6e4 });
|
|
19295
19317
|
await page.waitForTimeout(LOCAL_RESULTS_WAIT_MS);
|
|
19296
19318
|
if (await this.detectBlock(page)) throw new CaptchaError(RECAPTCHA_INSTRUCTIONS);
|
|
19297
19319
|
const reachedFinder = await this.clickIntoLocalFinder(page);
|
|
@@ -19341,13 +19363,15 @@ var init_MapsSearchExtractor = __esm({
|
|
|
19341
19363
|
for (const card of cards) {
|
|
19342
19364
|
if (results.length >= options.maxResults || seen.has(card.cardKey)) continue;
|
|
19343
19365
|
seen.add(card.cardKey);
|
|
19366
|
+
const cardIds = mapsIdentifiersFromUrl(card.directionsUrl ?? "");
|
|
19367
|
+
const cardPlaceUrl = cardIds.cidDecimal ? `https://www.google.com/maps?cid=${cardIds.cidDecimal}` : card.placeUrl;
|
|
19344
19368
|
const details = await this.openCardAndExtractDialog(page, card, options.includeServices);
|
|
19345
19369
|
results.push({
|
|
19346
19370
|
position: results.length + 1,
|
|
19347
19371
|
name: card.name,
|
|
19348
|
-
placeUrl: details?.placeUrl ??
|
|
19349
|
-
cid: details?.cid ??
|
|
19350
|
-
cidDecimal: details?.cidDecimal ??
|
|
19372
|
+
placeUrl: details?.placeUrl ?? cardPlaceUrl,
|
|
19373
|
+
cid: details?.cid ?? cardIds.cid,
|
|
19374
|
+
cidDecimal: details?.cidDecimal ?? cardIds.cidDecimal,
|
|
19351
19375
|
rating: details?.rating ?? card.rating,
|
|
19352
19376
|
reviewCount: details?.reviewCount ?? card.reviewCount,
|
|
19353
19377
|
category: details?.category ?? card.category,
|
|
@@ -19404,8 +19428,10 @@ var init_MapsSearchExtractor = __esm({
|
|
|
19404
19428
|
const phone = lines.map((line) => line.match(phonePattern)?.[0]).find((value) => Boolean(value)) ?? null;
|
|
19405
19429
|
const hoursStatus = normalize4((lines.find((line) => /\b(?:open|closed|opens|closes)\b/i.test(line)) ?? "").split("\xB7")[0] ?? "");
|
|
19406
19430
|
const address = lines.find((line) => addressPattern.test(line)) ?? null;
|
|
19407
|
-
const
|
|
19431
|
+
const anchorHrefs = Array.from(card.querySelectorAll("a[href]")).map((anchor) => anchor.href);
|
|
19432
|
+
const websiteUrl = anchorHrefs.find((href) => /^https?:/i.test(href) && !/google\.|gstatic|googleusercontent/.test(href)) ?? null;
|
|
19408
19433
|
if (!rating && !(phone && websiteUrl)) continue;
|
|
19434
|
+
const fidHref = anchorHrefs.find((href) => /(0x[0-9a-f]+):(0x[0-9a-f]+)/i.test(href)) ?? null;
|
|
19409
19435
|
const key = `${name.toLowerCase()}|${lines.join(" ").toLowerCase()}`;
|
|
19410
19436
|
if (seen.has(key)) continue;
|
|
19411
19437
|
seen.add(key);
|
|
@@ -19425,7 +19451,7 @@ var init_MapsSearchExtractor = __esm({
|
|
|
19425
19451
|
phone,
|
|
19426
19452
|
hoursStatus,
|
|
19427
19453
|
websiteUrl,
|
|
19428
|
-
directionsUrl: `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent([name, address].filter(Boolean).join(", "))}`,
|
|
19454
|
+
directionsUrl: fidHref ?? `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent([name, address].filter(Boolean).join(", "))}`,
|
|
19429
19455
|
metadata: lines.slice(0, 20)
|
|
19430
19456
|
});
|
|
19431
19457
|
}
|
|
@@ -20164,7 +20190,9 @@ async function runMapsSearchWithRotation(options) {
|
|
|
20164
20190
|
...options,
|
|
20165
20191
|
proxyZip,
|
|
20166
20192
|
kernelProxyId: options.proxyMode === "none" ? void 0 : resolution2.kernelProxyId,
|
|
20167
|
-
kernelProxyResolution: resolution2.resolution
|
|
20193
|
+
kernelProxyResolution: resolution2.resolution,
|
|
20194
|
+
serpRedirect: options.proxyMode === "none" && attemptIndex >= 1 && attemptIndex < maxAttempts - 1,
|
|
20195
|
+
forceDirectEgress: options.proxyMode === "none" && attemptIndex >= 1
|
|
20168
20196
|
};
|
|
20169
20197
|
const isHeadfulEscalation = attemptIndex === maxAttempts - 1;
|
|
20170
20198
|
const baseCtx = currentCostContext();
|
|
@@ -29786,7 +29814,7 @@ var PACKAGE_VERSION;
|
|
|
29786
29814
|
var init_version = __esm({
|
|
29787
29815
|
"src/version.ts"() {
|
|
29788
29816
|
"use strict";
|
|
29789
|
-
PACKAGE_VERSION = "0.32.
|
|
29817
|
+
PACKAGE_VERSION = "0.32.3";
|
|
29790
29818
|
}
|
|
29791
29819
|
});
|
|
29792
29820
|
|
|
@@ -30237,7 +30265,7 @@ var init_meta_ad_creative_media = __esm({
|
|
|
30237
30265
|
});
|
|
30238
30266
|
|
|
30239
30267
|
// src/mcp/mcp-tool-schemas.ts
|
|
30240
|
-
var import_zod35, HarvestPaaInputSchema, ExtractUrlInputSchema, DiffPageInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, CheckSiteExportInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, ArtifactPointerOutputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, DiffPageOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, CheckSiteExportOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, 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, GoogleCalendarCreateEventInputSchema, GoogleCalendarCreateEventOutputSchema, ZoomCreateMeetingInputSchema, ZoomCreateMeetingOutputSchema;
|
|
30268
|
+
var import_zod35, HarvestPaaInputSchema, ExtractUrlInputSchema, DiffPageInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, CheckSiteExportInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, ArtifactPointerOutputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, DiffPageOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, CheckSiteExportOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, 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;
|
|
30241
30269
|
var init_mcp_tool_schemas = __esm({
|
|
30242
30270
|
"src/mcp/mcp-tool-schemas.ts"() {
|
|
30243
30271
|
"use strict";
|
|
@@ -31644,6 +31672,37 @@ var init_mcp_tool_schemas = __esm({
|
|
|
31644
31672
|
result: import_zod35.z.unknown().optional(),
|
|
31645
31673
|
error: NullableString
|
|
31646
31674
|
};
|
|
31675
|
+
GmailSearchContactsInputSchema = {
|
|
31676
|
+
connectionId: import_zod35.z.string().min(1).describe("A Gmail connectionId from list_service_connections. Read-only \u2014 does not require actionsEnabled."),
|
|
31677
|
+
query: import_zod35.z.string().min(1).max(500).describe('Gmail search syntax, e.g. "from:brandnorth.com after:2026/03/22" or "brandnorth.com".'),
|
|
31678
|
+
maxMessages: import_zod35.z.number().int().min(1).max(150).default(50).describe("Max messages to fetch and aggregate in this call. Paginate with pageToken for more; the response reports totalMatches and truncated so undercoverage is never silent."),
|
|
31679
|
+
pageToken: import_zod35.z.string().optional().describe("Continuation token from a prior response to fetch the next page.")
|
|
31680
|
+
};
|
|
31681
|
+
GmailSearchContactsOutputSchema = {
|
|
31682
|
+
ok: import_zod35.z.boolean(),
|
|
31683
|
+
totalMatches: import_zod35.z.number().optional().describe("Gmail's estimated total matches for the query, independent of how many were fetched this call."),
|
|
31684
|
+
messagesFetched: import_zod35.z.number().optional(),
|
|
31685
|
+
truncated: import_zod35.z.boolean().optional().describe("True when totalMatches exceeds messagesFetched \u2014 more results exist, use nextPageToken."),
|
|
31686
|
+
nextPageToken: NullableString,
|
|
31687
|
+
messages: import_zod35.z.array(import_zod35.z.object({
|
|
31688
|
+
id: import_zod35.z.string(),
|
|
31689
|
+
date: NullableString,
|
|
31690
|
+
from: NullableString,
|
|
31691
|
+
to: NullableString,
|
|
31692
|
+
subject: NullableString,
|
|
31693
|
+
snippet: NullableString
|
|
31694
|
+
})).optional(),
|
|
31695
|
+
contacts: import_zod35.z.array(import_zod35.z.object({
|
|
31696
|
+
email: import_zod35.z.string(),
|
|
31697
|
+
name: NullableString,
|
|
31698
|
+
domain: NullableString,
|
|
31699
|
+
messageCount: import_zod35.z.number(),
|
|
31700
|
+
firstSeen: NullableString,
|
|
31701
|
+
lastSeen: NullableString,
|
|
31702
|
+
sampleSubjects: import_zod35.z.array(import_zod35.z.string())
|
|
31703
|
+
})).optional().describe("Messages deduped by sender email address, newest-to-oldest within this fetch only \u2014 not the full query result set when truncated."),
|
|
31704
|
+
error: NullableString
|
|
31705
|
+
};
|
|
31647
31706
|
GoogleCalendarCreateEventInputSchema = {
|
|
31648
31707
|
connectionId: import_zod35.z.string().min(1).describe("A Google Calendar connectionId from list_service_connections, with actionsEnabled true."),
|
|
31649
31708
|
calendarId: import_zod35.z.string().min(1).default("primary").describe('Calendar to create the event in. Default "primary".'),
|
|
@@ -32390,6 +32449,13 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
32390
32449
|
outputSchema: recordOutputSchema("gmail_send_message", GmailSendMessageOutputSchema),
|
|
32391
32450
|
annotations: { title: "Send Gmail Message", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
32392
32451
|
}, async (input) => executor.gmailSendMessage(input));
|
|
32452
|
+
server.registerTool("gmail_search_contacts", {
|
|
32453
|
+
title: "Search Gmail Contacts",
|
|
32454
|
+
description: 'Search Gmail with standard Gmail query syntax (e.g. "from:acme.com after:2026/03/22") and get back deduplicated sender contacts (email, name, domain, message count, first/last seen, sample subjects) instead of raw messages. Read-only \u2014 works on any connected Gmail connection from list_service_connections, no actionsEnabled required. Use this instead of looping list-messages/get-message yourself: those return bare message IDs and full raw MIME per message, which does not scale past a handful of messages. Reports totalMatches and truncated so incomplete coverage from a large result set is never silent \u2014 pass the returned nextPageToken to continue.',
|
|
32455
|
+
inputSchema: GmailSearchContactsInputSchema,
|
|
32456
|
+
outputSchema: recordOutputSchema("gmail_search_contacts", GmailSearchContactsOutputSchema),
|
|
32457
|
+
annotations: { title: "Search Gmail Contacts", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
32458
|
+
}, async (input) => executor.gmailSearchContacts(input));
|
|
32393
32459
|
server.registerTool("google_calendar_create_event", {
|
|
32394
32460
|
title: "Create Calendar Event",
|
|
32395
32461
|
description: "Create a complete event on a connected, action-enabled Google Calendar connection. Always preserve the supplied purpose in description, include the Zoom join link when available, and include every explicitly named invitee in attendees. Do not create a bare meeting event. Requires a connectionId from list_service_connections with actionsEnabled true.",
|
|
@@ -32799,6 +32865,9 @@ var init_http_mcp_tool_executor = __esm({
|
|
|
32799
32865
|
gmailSendMessage(input) {
|
|
32800
32866
|
return this.call("/schedule-connections/actions/gmail/send-message", input);
|
|
32801
32867
|
}
|
|
32868
|
+
gmailSearchContacts(input) {
|
|
32869
|
+
return this.call("/schedule-connections/actions/gmail/search-contacts", input);
|
|
32870
|
+
}
|
|
32802
32871
|
googleCalendarCreateEvent(input) {
|
|
32803
32872
|
return this.call("/schedule-connections/actions/google-calendar/create-event", input);
|
|
32804
32873
|
}
|
|
@@ -40060,6 +40129,225 @@ var init_browser_agent_console = __esm({
|
|
|
40060
40129
|
}
|
|
40061
40130
|
});
|
|
40062
40131
|
|
|
40132
|
+
// src/api/connected-account-billing.ts
|
|
40133
|
+
async function ensureConnectedAccountBillingSchema() {
|
|
40134
|
+
await getDb().execute(`CREATE TABLE IF NOT EXISTS connected_account_billing (
|
|
40135
|
+
user_id INTEGER PRIMARY KEY,
|
|
40136
|
+
stripe_subscription_id TEXT,
|
|
40137
|
+
stripe_subscription_item_id TEXT,
|
|
40138
|
+
price_id TEXT,
|
|
40139
|
+
quantity INTEGER NOT NULL DEFAULT 0,
|
|
40140
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
40141
|
+
last_error_code TEXT,
|
|
40142
|
+
synced_at TEXT,
|
|
40143
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
40144
|
+
)`);
|
|
40145
|
+
}
|
|
40146
|
+
async function getConnectedAccountBillingState(userId) {
|
|
40147
|
+
await ensureConnectedAccountBillingSchema();
|
|
40148
|
+
const result = await getDb().execute({
|
|
40149
|
+
sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
|
|
40150
|
+
args: [Number(userId)]
|
|
40151
|
+
});
|
|
40152
|
+
return result.rows[0] ? result.rows[0] : null;
|
|
40153
|
+
}
|
|
40154
|
+
async function setConnectedAccountBillingState(input) {
|
|
40155
|
+
await ensureConnectedAccountBillingSchema();
|
|
40156
|
+
await getDb().execute({
|
|
40157
|
+
sql: `INSERT INTO connected_account_billing
|
|
40158
|
+
(user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, synced_at, updated_at)
|
|
40159
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, CASE WHEN ? THEN datetime('now') ELSE NULL END, datetime('now'))
|
|
40160
|
+
ON CONFLICT(user_id) DO UPDATE SET
|
|
40161
|
+
stripe_subscription_id = excluded.stripe_subscription_id,
|
|
40162
|
+
stripe_subscription_item_id = excluded.stripe_subscription_item_id,
|
|
40163
|
+
price_id = excluded.price_id,
|
|
40164
|
+
quantity = excluded.quantity,
|
|
40165
|
+
status = excluded.status,
|
|
40166
|
+
last_error_code = excluded.last_error_code,
|
|
40167
|
+
synced_at = CASE WHEN ? THEN datetime('now') ELSE connected_account_billing.synced_at END,
|
|
40168
|
+
updated_at = datetime('now')`,
|
|
40169
|
+
args: [
|
|
40170
|
+
Number(input.userId),
|
|
40171
|
+
input.stripeSubscriptionId,
|
|
40172
|
+
input.stripeSubscriptionItemId,
|
|
40173
|
+
input.priceId,
|
|
40174
|
+
input.quantity,
|
|
40175
|
+
input.status,
|
|
40176
|
+
input.lastErrorCode,
|
|
40177
|
+
input.synced ? 1 : 0,
|
|
40178
|
+
input.synced ? 1 : 0
|
|
40179
|
+
]
|
|
40180
|
+
});
|
|
40181
|
+
const state = await getConnectedAccountBillingState(input.userId);
|
|
40182
|
+
if (!state) throw new Error("connected account billing state was not persisted");
|
|
40183
|
+
return state;
|
|
40184
|
+
}
|
|
40185
|
+
function connectedAccountPriceId() {
|
|
40186
|
+
const configured = process.env.CONNECTED_ACCOUNT_PRICE_ID?.trim();
|
|
40187
|
+
if (configured) return configured;
|
|
40188
|
+
return DEFAULT_CONNECTED_ACCOUNT_PRICE_ID || null;
|
|
40189
|
+
}
|
|
40190
|
+
function connectedAccountBillingView(actualQuantity, state) {
|
|
40191
|
+
const quantity = Math.max(0, Math.round(actualQuantity));
|
|
40192
|
+
return {
|
|
40193
|
+
provider: "nango",
|
|
40194
|
+
billingMode: "flat_recurring_usd_plus_credits",
|
|
40195
|
+
priceId: connectedAccountPriceId(),
|
|
40196
|
+
unitAmountUsd: CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
|
|
40197
|
+
interval: "month",
|
|
40198
|
+
quantity,
|
|
40199
|
+
projectedMonthlyUsd: quantity * CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
|
|
40200
|
+
status: state?.status ?? "pending",
|
|
40201
|
+
lastErrorCode: state?.last_error_code ?? null,
|
|
40202
|
+
syncedAt: state?.synced_at ?? null,
|
|
40203
|
+
usage: CONNECTED_USAGE_RATE_POLICY.usage
|
|
40204
|
+
};
|
|
40205
|
+
}
|
|
40206
|
+
function findBasePlanItem(subscription) {
|
|
40207
|
+
return subscription.items.data.find((item) => !!item.price?.id && item.price.id in SUBSCRIPTION_TIERS);
|
|
40208
|
+
}
|
|
40209
|
+
function findConnectedAccountItem(subscription, priceId = connectedAccountPriceId()) {
|
|
40210
|
+
return priceId ? subscription.items.data.find((item) => item.price?.id === priceId) : void 0;
|
|
40211
|
+
}
|
|
40212
|
+
function liveSubscription(subscription) {
|
|
40213
|
+
return subscription.status === "active" || subscription.status === "trialing";
|
|
40214
|
+
}
|
|
40215
|
+
function safeStripeErrorCode(error) {
|
|
40216
|
+
if (error instanceof ConnectedAccountBillingError) return error.code;
|
|
40217
|
+
return "stripe_connection_billing_failed";
|
|
40218
|
+
}
|
|
40219
|
+
async function persistFailure(user, actualQuantity, error, subscriptionItemId = null) {
|
|
40220
|
+
await setConnectedAccountBillingState({
|
|
40221
|
+
userId: user.id,
|
|
40222
|
+
stripeSubscriptionId: user.subscription_id,
|
|
40223
|
+
stripeSubscriptionItemId: subscriptionItemId,
|
|
40224
|
+
priceId: connectedAccountPriceId(),
|
|
40225
|
+
quantity: actualQuantity,
|
|
40226
|
+
status: error instanceof ConnectedAccountBillingError && error.code !== "stripe_connection_billing_failed" ? "blocked" : "error",
|
|
40227
|
+
lastErrorCode: safeStripeErrorCode(error),
|
|
40228
|
+
synced: false
|
|
40229
|
+
});
|
|
40230
|
+
}
|
|
40231
|
+
async function reconcileConnectedAccountBilling(user, actualQuantity, stripeClient) {
|
|
40232
|
+
if (!Number.isSafeInteger(actualQuantity) || actualQuantity < 0) {
|
|
40233
|
+
throw new Error("actualQuantity must be a non-negative safe integer");
|
|
40234
|
+
}
|
|
40235
|
+
const priceId = connectedAccountPriceId();
|
|
40236
|
+
if (!priceId) {
|
|
40237
|
+
const error = new ConnectedAccountBillingError(
|
|
40238
|
+
"Connected-account billing is not configured.",
|
|
40239
|
+
"connected_account_price_not_configured",
|
|
40240
|
+
503
|
|
40241
|
+
);
|
|
40242
|
+
await persistFailure(user, actualQuantity, error);
|
|
40243
|
+
throw error;
|
|
40244
|
+
}
|
|
40245
|
+
if (!user.subscription_id) {
|
|
40246
|
+
if (actualQuantity === 0) {
|
|
40247
|
+
const state = await setConnectedAccountBillingState({
|
|
40248
|
+
userId: user.id,
|
|
40249
|
+
stripeSubscriptionId: null,
|
|
40250
|
+
stripeSubscriptionItemId: null,
|
|
40251
|
+
priceId,
|
|
40252
|
+
quantity: 0,
|
|
40253
|
+
status: "synced",
|
|
40254
|
+
lastErrorCode: null,
|
|
40255
|
+
synced: true
|
|
40256
|
+
});
|
|
40257
|
+
return connectedAccountBillingView(0, state);
|
|
40258
|
+
}
|
|
40259
|
+
const error = new ConnectedAccountBillingError(
|
|
40260
|
+
"An active paid plan is required before connected accounts can be billed.",
|
|
40261
|
+
"paid_plan_required",
|
|
40262
|
+
403
|
|
40263
|
+
);
|
|
40264
|
+
await persistFailure(user, actualQuantity, error);
|
|
40265
|
+
throw error;
|
|
40266
|
+
}
|
|
40267
|
+
const client2 = stripeClient ?? (() => {
|
|
40268
|
+
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
40269
|
+
if (!secret2) {
|
|
40270
|
+
throw new ConnectedAccountBillingError(
|
|
40271
|
+
"Stripe is not configured.",
|
|
40272
|
+
"connected_account_price_not_configured",
|
|
40273
|
+
503
|
|
40274
|
+
);
|
|
40275
|
+
}
|
|
40276
|
+
return new import_stripe.default(secret2, { apiVersion: STRIPE_API_VERSION });
|
|
40277
|
+
})();
|
|
40278
|
+
let currentItem;
|
|
40279
|
+
try {
|
|
40280
|
+
const subscription = await client2.subscriptions.retrieve(user.subscription_id);
|
|
40281
|
+
currentItem = findConnectedAccountItem(subscription, priceId);
|
|
40282
|
+
if (!liveSubscription(subscription)) {
|
|
40283
|
+
throw new ConnectedAccountBillingError(
|
|
40284
|
+
"The plan subscription is not active.",
|
|
40285
|
+
"subscription_not_active",
|
|
40286
|
+
402
|
|
40287
|
+
);
|
|
40288
|
+
}
|
|
40289
|
+
if (!findBasePlanItem(subscription)) {
|
|
40290
|
+
throw new ConnectedAccountBillingError(
|
|
40291
|
+
"The base plan item could not be found on this subscription.",
|
|
40292
|
+
"subscription_plan_item_missing",
|
|
40293
|
+
409
|
|
40294
|
+
);
|
|
40295
|
+
}
|
|
40296
|
+
const currentQuantity = Math.max(0, currentItem?.quantity ?? 0);
|
|
40297
|
+
let resultingItem = currentItem;
|
|
40298
|
+
if (actualQuantity !== currentQuantity) {
|
|
40299
|
+
const updated = await client2.subscriptions.update(subscription.id, {
|
|
40300
|
+
items: actualQuantity === 0 ? currentItem ? [{ id: currentItem.id, deleted: true }] : [] : currentItem ? [{ id: currentItem.id, quantity: actualQuantity }] : [{ price: priceId, quantity: actualQuantity }],
|
|
40301
|
+
proration_behavior: "create_prorations"
|
|
40302
|
+
});
|
|
40303
|
+
resultingItem = findConnectedAccountItem(updated, priceId);
|
|
40304
|
+
}
|
|
40305
|
+
const state = await setConnectedAccountBillingState({
|
|
40306
|
+
userId: user.id,
|
|
40307
|
+
stripeSubscriptionId: subscription.id,
|
|
40308
|
+
stripeSubscriptionItemId: resultingItem?.id ?? null,
|
|
40309
|
+
priceId,
|
|
40310
|
+
quantity: actualQuantity,
|
|
40311
|
+
status: "synced",
|
|
40312
|
+
lastErrorCode: null,
|
|
40313
|
+
synced: true
|
|
40314
|
+
});
|
|
40315
|
+
return connectedAccountBillingView(actualQuantity, state);
|
|
40316
|
+
} catch (error) {
|
|
40317
|
+
await persistFailure(user, actualQuantity, error, currentItem?.id ?? null);
|
|
40318
|
+
if (error instanceof ConnectedAccountBillingError) throw error;
|
|
40319
|
+
throw new ConnectedAccountBillingError(
|
|
40320
|
+
"Unable to update connected-account billing in Stripe.",
|
|
40321
|
+
"stripe_connection_billing_failed",
|
|
40322
|
+
503
|
|
40323
|
+
);
|
|
40324
|
+
}
|
|
40325
|
+
}
|
|
40326
|
+
async function currentConnectedAccountBillingView(userId, actualQuantity) {
|
|
40327
|
+
return connectedAccountBillingView(actualQuantity, await getConnectedAccountBillingState(userId));
|
|
40328
|
+
}
|
|
40329
|
+
var import_stripe, DEFAULT_CONNECTED_ACCOUNT_PRICE_ID, STRIPE_API_VERSION, ConnectedAccountBillingError;
|
|
40330
|
+
var init_connected_account_billing = __esm({
|
|
40331
|
+
"src/api/connected-account-billing.ts"() {
|
|
40332
|
+
"use strict";
|
|
40333
|
+
import_stripe = __toESM(require("stripe"), 1);
|
|
40334
|
+
init_db();
|
|
40335
|
+
init_rates();
|
|
40336
|
+
DEFAULT_CONNECTED_ACCOUNT_PRICE_ID = "price_1TtYPAS8aAcsk3TGHOgrDZiQ";
|
|
40337
|
+
STRIPE_API_VERSION = "2026-02-25.clover";
|
|
40338
|
+
ConnectedAccountBillingError = class extends Error {
|
|
40339
|
+
constructor(message, code, status = 409) {
|
|
40340
|
+
super(message);
|
|
40341
|
+
this.code = code;
|
|
40342
|
+
this.status = status;
|
|
40343
|
+
this.name = "ConnectedAccountBillingError";
|
|
40344
|
+
}
|
|
40345
|
+
code;
|
|
40346
|
+
status;
|
|
40347
|
+
};
|
|
40348
|
+
}
|
|
40349
|
+
});
|
|
40350
|
+
|
|
40063
40351
|
// src/api/stripe-routes.ts
|
|
40064
40352
|
function linePriceId(line) {
|
|
40065
40353
|
const l = line;
|
|
@@ -40073,16 +40361,17 @@ async function resolveUser2(customerId, emailFallback) {
|
|
|
40073
40361
|
if (user) await setStripeCustomerId(user.id, customerId);
|
|
40074
40362
|
return user;
|
|
40075
40363
|
}
|
|
40076
|
-
var
|
|
40364
|
+
var import_stripe2, import_hono19, stripe, stripeApp;
|
|
40077
40365
|
var init_stripe_routes = __esm({
|
|
40078
40366
|
"src/api/stripe-routes.ts"() {
|
|
40079
40367
|
"use strict";
|
|
40080
|
-
|
|
40368
|
+
import_stripe2 = __toESM(require("stripe"), 1);
|
|
40081
40369
|
import_hono19 = require("hono");
|
|
40082
40370
|
init_db();
|
|
40083
40371
|
init_rates();
|
|
40084
40372
|
init_memory();
|
|
40085
|
-
|
|
40373
|
+
init_connected_account_billing();
|
|
40374
|
+
stripe = new import_stripe2.default(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
|
|
40086
40375
|
stripeApp = new import_hono19.Hono();
|
|
40087
40376
|
stripeApp.post("/webhooks", async (c) => {
|
|
40088
40377
|
const sig = c.req.header("stripe-signature");
|
|
@@ -40099,15 +40388,23 @@ var init_stripe_routes = __esm({
|
|
|
40099
40388
|
const invoice = event.data.object;
|
|
40100
40389
|
const memLineId = invoice.lines.data.map(linePriceId).find((id) => id && id in MEMORY_PLANS);
|
|
40101
40390
|
if (memLineId) return c.json({ received: true });
|
|
40102
|
-
const
|
|
40103
|
-
|
|
40104
|
-
|
|
40105
|
-
|
|
40106
|
-
|
|
40107
|
-
|
|
40108
|
-
|
|
40109
|
-
|
|
40110
|
-
|
|
40391
|
+
const invoiceHasTierLine = invoice.lines.data.some((l) => {
|
|
40392
|
+
const id = linePriceId(l);
|
|
40393
|
+
return id && id in SUBSCRIPTION_TIERS;
|
|
40394
|
+
});
|
|
40395
|
+
if (invoiceHasTierLine) {
|
|
40396
|
+
const subId = invoice.subscription;
|
|
40397
|
+
const liveBasePlanPriceId = subId ? findBasePlanItem(await stripe.subscriptions.retrieve(subId))?.price?.id : void 0;
|
|
40398
|
+
const tierPriceId = liveBasePlanPriceId ?? invoice.lines.data.map(linePriceId).find((id) => id && id in SUBSCRIPTION_TIERS);
|
|
40399
|
+
const tier = tierPriceId ? SUBSCRIPTION_TIERS[tierPriceId] : void 0;
|
|
40400
|
+
if (tier) {
|
|
40401
|
+
const user = await resolveUser2(invoice.customer, invoice.customer_email ?? void 0);
|
|
40402
|
+
if (user && invoice.id && !await ledgerExistsForStripePI(invoice.id)) {
|
|
40403
|
+
await creditMc(user.id, tier.credits_mc, LedgerOperation.SUBSCRIPTION, `${tier.label} subscription credits`, invoice.id);
|
|
40404
|
+
await setSubscriptionTier(user.id, tier.tier, tier.concurrency, subId ?? user.subscription_id);
|
|
40405
|
+
const credentials = await syncScheduledActionCredentials(user);
|
|
40406
|
+
if (!credentials.ok) console.warn("[stripe] scheduled-action credential sync failed:", credentials.error ?? "unknown error");
|
|
40407
|
+
}
|
|
40111
40408
|
}
|
|
40112
40409
|
}
|
|
40113
40410
|
}
|
|
@@ -43476,8 +43773,8 @@ async function reconcileDiscoveredNangoConnections(identity, discovered) {
|
|
|
43476
43773
|
sql: `
|
|
43477
43774
|
INSERT INTO service_connections (
|
|
43478
43775
|
id, user_id, provider_config_key, provider, transport, upstream_connection_id,
|
|
43479
|
-
label, lifecycle_status, reconnect_required, legacy_owner_identity, created_at, updated_at
|
|
43480
|
-
) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
|
|
43776
|
+
label, lifecycle_status, reconnect_required, actions_enabled, legacy_owner_identity, created_at, updated_at
|
|
43777
|
+
) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, 1, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
|
|
43481
43778
|
ON CONFLICT(user_id, provider_config_key, upstream_connection_id) DO UPDATE SET
|
|
43482
43779
|
provider = excluded.provider,
|
|
43483
43780
|
label = COALESCE(excluded.label, service_connections.label),
|
|
@@ -45593,225 +45890,6 @@ var init_scheduler_integration_auth = __esm({
|
|
|
45593
45890
|
}
|
|
45594
45891
|
});
|
|
45595
45892
|
|
|
45596
|
-
// src/api/connected-account-billing.ts
|
|
45597
|
-
async function ensureConnectedAccountBillingSchema() {
|
|
45598
|
-
await getDb().execute(`CREATE TABLE IF NOT EXISTS connected_account_billing (
|
|
45599
|
-
user_id INTEGER PRIMARY KEY,
|
|
45600
|
-
stripe_subscription_id TEXT,
|
|
45601
|
-
stripe_subscription_item_id TEXT,
|
|
45602
|
-
price_id TEXT,
|
|
45603
|
-
quantity INTEGER NOT NULL DEFAULT 0,
|
|
45604
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
45605
|
-
last_error_code TEXT,
|
|
45606
|
-
synced_at TEXT,
|
|
45607
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
45608
|
-
)`);
|
|
45609
|
-
}
|
|
45610
|
-
async function getConnectedAccountBillingState(userId) {
|
|
45611
|
-
await ensureConnectedAccountBillingSchema();
|
|
45612
|
-
const result = await getDb().execute({
|
|
45613
|
-
sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
|
|
45614
|
-
args: [Number(userId)]
|
|
45615
|
-
});
|
|
45616
|
-
return result.rows[0] ? result.rows[0] : null;
|
|
45617
|
-
}
|
|
45618
|
-
async function setConnectedAccountBillingState(input) {
|
|
45619
|
-
await ensureConnectedAccountBillingSchema();
|
|
45620
|
-
await getDb().execute({
|
|
45621
|
-
sql: `INSERT INTO connected_account_billing
|
|
45622
|
-
(user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, synced_at, updated_at)
|
|
45623
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, CASE WHEN ? THEN datetime('now') ELSE NULL END, datetime('now'))
|
|
45624
|
-
ON CONFLICT(user_id) DO UPDATE SET
|
|
45625
|
-
stripe_subscription_id = excluded.stripe_subscription_id,
|
|
45626
|
-
stripe_subscription_item_id = excluded.stripe_subscription_item_id,
|
|
45627
|
-
price_id = excluded.price_id,
|
|
45628
|
-
quantity = excluded.quantity,
|
|
45629
|
-
status = excluded.status,
|
|
45630
|
-
last_error_code = excluded.last_error_code,
|
|
45631
|
-
synced_at = CASE WHEN ? THEN datetime('now') ELSE connected_account_billing.synced_at END,
|
|
45632
|
-
updated_at = datetime('now')`,
|
|
45633
|
-
args: [
|
|
45634
|
-
Number(input.userId),
|
|
45635
|
-
input.stripeSubscriptionId,
|
|
45636
|
-
input.stripeSubscriptionItemId,
|
|
45637
|
-
input.priceId,
|
|
45638
|
-
input.quantity,
|
|
45639
|
-
input.status,
|
|
45640
|
-
input.lastErrorCode,
|
|
45641
|
-
input.synced ? 1 : 0,
|
|
45642
|
-
input.synced ? 1 : 0
|
|
45643
|
-
]
|
|
45644
|
-
});
|
|
45645
|
-
const state = await getConnectedAccountBillingState(input.userId);
|
|
45646
|
-
if (!state) throw new Error("connected account billing state was not persisted");
|
|
45647
|
-
return state;
|
|
45648
|
-
}
|
|
45649
|
-
function connectedAccountPriceId() {
|
|
45650
|
-
const configured = process.env.CONNECTED_ACCOUNT_PRICE_ID?.trim();
|
|
45651
|
-
if (configured) return configured;
|
|
45652
|
-
return DEFAULT_CONNECTED_ACCOUNT_PRICE_ID || null;
|
|
45653
|
-
}
|
|
45654
|
-
function connectedAccountBillingView(actualQuantity, state) {
|
|
45655
|
-
const quantity = Math.max(0, Math.round(actualQuantity));
|
|
45656
|
-
return {
|
|
45657
|
-
provider: "nango",
|
|
45658
|
-
billingMode: "flat_recurring_usd_plus_credits",
|
|
45659
|
-
priceId: connectedAccountPriceId(),
|
|
45660
|
-
unitAmountUsd: CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
|
|
45661
|
-
interval: "month",
|
|
45662
|
-
quantity,
|
|
45663
|
-
projectedMonthlyUsd: quantity * CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
|
|
45664
|
-
status: state?.status ?? "pending",
|
|
45665
|
-
lastErrorCode: state?.last_error_code ?? null,
|
|
45666
|
-
syncedAt: state?.synced_at ?? null,
|
|
45667
|
-
usage: CONNECTED_USAGE_RATE_POLICY.usage
|
|
45668
|
-
};
|
|
45669
|
-
}
|
|
45670
|
-
function findBasePlanItem(subscription) {
|
|
45671
|
-
return subscription.items.data.find((item) => !!item.price?.id && item.price.id in SUBSCRIPTION_TIERS);
|
|
45672
|
-
}
|
|
45673
|
-
function findConnectedAccountItem(subscription, priceId = connectedAccountPriceId()) {
|
|
45674
|
-
return priceId ? subscription.items.data.find((item) => item.price?.id === priceId) : void 0;
|
|
45675
|
-
}
|
|
45676
|
-
function liveSubscription(subscription) {
|
|
45677
|
-
return subscription.status === "active" || subscription.status === "trialing";
|
|
45678
|
-
}
|
|
45679
|
-
function safeStripeErrorCode(error) {
|
|
45680
|
-
if (error instanceof ConnectedAccountBillingError) return error.code;
|
|
45681
|
-
return "stripe_connection_billing_failed";
|
|
45682
|
-
}
|
|
45683
|
-
async function persistFailure(user, actualQuantity, error, subscriptionItemId = null) {
|
|
45684
|
-
await setConnectedAccountBillingState({
|
|
45685
|
-
userId: user.id,
|
|
45686
|
-
stripeSubscriptionId: user.subscription_id,
|
|
45687
|
-
stripeSubscriptionItemId: subscriptionItemId,
|
|
45688
|
-
priceId: connectedAccountPriceId(),
|
|
45689
|
-
quantity: actualQuantity,
|
|
45690
|
-
status: error instanceof ConnectedAccountBillingError && error.code !== "stripe_connection_billing_failed" ? "blocked" : "error",
|
|
45691
|
-
lastErrorCode: safeStripeErrorCode(error),
|
|
45692
|
-
synced: false
|
|
45693
|
-
});
|
|
45694
|
-
}
|
|
45695
|
-
async function reconcileConnectedAccountBilling(user, actualQuantity, stripeClient) {
|
|
45696
|
-
if (!Number.isSafeInteger(actualQuantity) || actualQuantity < 0) {
|
|
45697
|
-
throw new Error("actualQuantity must be a non-negative safe integer");
|
|
45698
|
-
}
|
|
45699
|
-
const priceId = connectedAccountPriceId();
|
|
45700
|
-
if (!priceId) {
|
|
45701
|
-
const error = new ConnectedAccountBillingError(
|
|
45702
|
-
"Connected-account billing is not configured.",
|
|
45703
|
-
"connected_account_price_not_configured",
|
|
45704
|
-
503
|
|
45705
|
-
);
|
|
45706
|
-
await persistFailure(user, actualQuantity, error);
|
|
45707
|
-
throw error;
|
|
45708
|
-
}
|
|
45709
|
-
if (!user.subscription_id) {
|
|
45710
|
-
if (actualQuantity === 0) {
|
|
45711
|
-
const state = await setConnectedAccountBillingState({
|
|
45712
|
-
userId: user.id,
|
|
45713
|
-
stripeSubscriptionId: null,
|
|
45714
|
-
stripeSubscriptionItemId: null,
|
|
45715
|
-
priceId,
|
|
45716
|
-
quantity: 0,
|
|
45717
|
-
status: "synced",
|
|
45718
|
-
lastErrorCode: null,
|
|
45719
|
-
synced: true
|
|
45720
|
-
});
|
|
45721
|
-
return connectedAccountBillingView(0, state);
|
|
45722
|
-
}
|
|
45723
|
-
const error = new ConnectedAccountBillingError(
|
|
45724
|
-
"An active paid plan is required before connected accounts can be billed.",
|
|
45725
|
-
"paid_plan_required",
|
|
45726
|
-
403
|
|
45727
|
-
);
|
|
45728
|
-
await persistFailure(user, actualQuantity, error);
|
|
45729
|
-
throw error;
|
|
45730
|
-
}
|
|
45731
|
-
const client2 = stripeClient ?? (() => {
|
|
45732
|
-
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
45733
|
-
if (!secret2) {
|
|
45734
|
-
throw new ConnectedAccountBillingError(
|
|
45735
|
-
"Stripe is not configured.",
|
|
45736
|
-
"connected_account_price_not_configured",
|
|
45737
|
-
503
|
|
45738
|
-
);
|
|
45739
|
-
}
|
|
45740
|
-
return new import_stripe2.default(secret2, { apiVersion: STRIPE_API_VERSION });
|
|
45741
|
-
})();
|
|
45742
|
-
let currentItem;
|
|
45743
|
-
try {
|
|
45744
|
-
const subscription = await client2.subscriptions.retrieve(user.subscription_id);
|
|
45745
|
-
currentItem = findConnectedAccountItem(subscription, priceId);
|
|
45746
|
-
if (!liveSubscription(subscription)) {
|
|
45747
|
-
throw new ConnectedAccountBillingError(
|
|
45748
|
-
"The plan subscription is not active.",
|
|
45749
|
-
"subscription_not_active",
|
|
45750
|
-
402
|
|
45751
|
-
);
|
|
45752
|
-
}
|
|
45753
|
-
if (!findBasePlanItem(subscription)) {
|
|
45754
|
-
throw new ConnectedAccountBillingError(
|
|
45755
|
-
"The base plan item could not be found on this subscription.",
|
|
45756
|
-
"subscription_plan_item_missing",
|
|
45757
|
-
409
|
|
45758
|
-
);
|
|
45759
|
-
}
|
|
45760
|
-
const currentQuantity = Math.max(0, currentItem?.quantity ?? 0);
|
|
45761
|
-
let resultingItem = currentItem;
|
|
45762
|
-
if (actualQuantity !== currentQuantity) {
|
|
45763
|
-
const updated = await client2.subscriptions.update(subscription.id, {
|
|
45764
|
-
items: actualQuantity === 0 ? currentItem ? [{ id: currentItem.id, deleted: true }] : [] : currentItem ? [{ id: currentItem.id, quantity: actualQuantity }] : [{ price: priceId, quantity: actualQuantity }],
|
|
45765
|
-
proration_behavior: "create_prorations"
|
|
45766
|
-
});
|
|
45767
|
-
resultingItem = findConnectedAccountItem(updated, priceId);
|
|
45768
|
-
}
|
|
45769
|
-
const state = await setConnectedAccountBillingState({
|
|
45770
|
-
userId: user.id,
|
|
45771
|
-
stripeSubscriptionId: subscription.id,
|
|
45772
|
-
stripeSubscriptionItemId: resultingItem?.id ?? null,
|
|
45773
|
-
priceId,
|
|
45774
|
-
quantity: actualQuantity,
|
|
45775
|
-
status: "synced",
|
|
45776
|
-
lastErrorCode: null,
|
|
45777
|
-
synced: true
|
|
45778
|
-
});
|
|
45779
|
-
return connectedAccountBillingView(actualQuantity, state);
|
|
45780
|
-
} catch (error) {
|
|
45781
|
-
await persistFailure(user, actualQuantity, error, currentItem?.id ?? null);
|
|
45782
|
-
if (error instanceof ConnectedAccountBillingError) throw error;
|
|
45783
|
-
throw new ConnectedAccountBillingError(
|
|
45784
|
-
"Unable to update connected-account billing in Stripe.",
|
|
45785
|
-
"stripe_connection_billing_failed",
|
|
45786
|
-
503
|
|
45787
|
-
);
|
|
45788
|
-
}
|
|
45789
|
-
}
|
|
45790
|
-
async function currentConnectedAccountBillingView(userId, actualQuantity) {
|
|
45791
|
-
return connectedAccountBillingView(actualQuantity, await getConnectedAccountBillingState(userId));
|
|
45792
|
-
}
|
|
45793
|
-
var import_stripe2, DEFAULT_CONNECTED_ACCOUNT_PRICE_ID, STRIPE_API_VERSION, ConnectedAccountBillingError;
|
|
45794
|
-
var init_connected_account_billing = __esm({
|
|
45795
|
-
"src/api/connected-account-billing.ts"() {
|
|
45796
|
-
"use strict";
|
|
45797
|
-
import_stripe2 = __toESM(require("stripe"), 1);
|
|
45798
|
-
init_db();
|
|
45799
|
-
init_rates();
|
|
45800
|
-
DEFAULT_CONNECTED_ACCOUNT_PRICE_ID = "price_1TtYPAS8aAcsk3TGHOgrDZiQ";
|
|
45801
|
-
STRIPE_API_VERSION = "2026-02-25.clover";
|
|
45802
|
-
ConnectedAccountBillingError = class extends Error {
|
|
45803
|
-
constructor(message, code, status = 409) {
|
|
45804
|
-
super(message);
|
|
45805
|
-
this.code = code;
|
|
45806
|
-
this.status = status;
|
|
45807
|
-
this.name = "ConnectedAccountBillingError";
|
|
45808
|
-
}
|
|
45809
|
-
code;
|
|
45810
|
-
status;
|
|
45811
|
-
};
|
|
45812
|
-
}
|
|
45813
|
-
});
|
|
45814
|
-
|
|
45815
45893
|
// src/api/webhook.ts
|
|
45816
45894
|
async function deliverWebhook(url, payload, retries = 3) {
|
|
45817
45895
|
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
@@ -46185,6 +46263,22 @@ function schedulerIntegrationAuthError(c, error) {
|
|
|
46185
46263
|
}
|
|
46186
46264
|
return scheduleConnectionError(c, error, "The scheduler integration request failed.");
|
|
46187
46265
|
}
|
|
46266
|
+
function toolResultText(result) {
|
|
46267
|
+
if (!result || typeof result !== "object") return null;
|
|
46268
|
+
const content = result.content;
|
|
46269
|
+
if (!Array.isArray(content)) return null;
|
|
46270
|
+
const textItem = content.find((item) => item && typeof item === "object" && item.type === "text");
|
|
46271
|
+
const text2 = textItem?.text;
|
|
46272
|
+
return typeof text2 === "string" ? text2 : null;
|
|
46273
|
+
}
|
|
46274
|
+
function parseEmailAddress(headerValue) {
|
|
46275
|
+
const value = headerValue?.trim();
|
|
46276
|
+
if (!value) return null;
|
|
46277
|
+
const angled = value.match(/^"?([^"<]*?)"?\s*<([^>]+)>$/);
|
|
46278
|
+
if (angled) return { email: angled[2].trim().toLowerCase(), name: angled[1].trim() || null };
|
|
46279
|
+
const bare = value.match(/^([^\s<>]+@[^\s<>]+)$/);
|
|
46280
|
+
return bare ? { email: bare[1].trim().toLowerCase(), name: null } : null;
|
|
46281
|
+
}
|
|
46188
46282
|
async function forwardIntegrationAlias(c, targetPath) {
|
|
46189
46283
|
const url = new URL(c.req.url);
|
|
46190
46284
|
url.pathname = targetPath;
|
|
@@ -46229,6 +46323,22 @@ async function checkHarvestLimits(user, reuseLockId) {
|
|
|
46229
46323
|
}
|
|
46230
46324
|
return null;
|
|
46231
46325
|
}
|
|
46326
|
+
async function chargeTierChangeNow(stripeClient, subscriptionId, customerId) {
|
|
46327
|
+
try {
|
|
46328
|
+
const draft = await stripeClient.invoices.create({
|
|
46329
|
+
customer: customerId,
|
|
46330
|
+
subscription: subscriptionId,
|
|
46331
|
+
auto_advance: true,
|
|
46332
|
+
collection_method: "charge_automatically"
|
|
46333
|
+
});
|
|
46334
|
+
const finalized = await stripeClient.invoices.finalizeInvoice(draft.id);
|
|
46335
|
+
if (finalized.amount_due <= 0 || finalized.status === "paid") return { ok: true, amountDue: finalized.amount_due };
|
|
46336
|
+
const paid = await stripeClient.invoices.pay(finalized.id);
|
|
46337
|
+
return { ok: paid.status === "paid", amountDue: paid.amount_due };
|
|
46338
|
+
} catch (err) {
|
|
46339
|
+
return { ok: false, amountDue: 0, error: err instanceof Error ? err.message : "Unable to charge the plan change immediately." };
|
|
46340
|
+
}
|
|
46341
|
+
}
|
|
46232
46342
|
var import_resend2, import_hono25, import_hono26, import_factory6, import_cookie2, import_stripe3, secureCookies2, isProduction2, sessionCookieOptions2, requireAllowedOrigin, auth2, adminAuth, sessionAuth, requireIntegrationsTier, requirePaidSchedulingTier, app, STRIPE_API_VERSION2, SYNC_HARVEST_TIMEOUT_OVERRIDE_MS;
|
|
46233
46343
|
var init_server = __esm({
|
|
46234
46344
|
"src/api/server.ts"() {
|
|
@@ -47058,7 +47168,7 @@ var init_server = __esm({
|
|
|
47058
47168
|
user.email,
|
|
47059
47169
|
connectionId,
|
|
47060
47170
|
{ channel: body.channel, text: body.text },
|
|
47061
|
-
|
|
47171
|
+
"send-message",
|
|
47062
47172
|
connectedActionIdempotencyKey(c)
|
|
47063
47173
|
);
|
|
47064
47174
|
return c.json({ ok: true, result });
|
|
@@ -47078,7 +47188,7 @@ var init_server = __esm({
|
|
|
47078
47188
|
user.email,
|
|
47079
47189
|
connectionId,
|
|
47080
47190
|
{ to: body.to, subject: body.subject, body: body.body },
|
|
47081
|
-
|
|
47191
|
+
"send-message",
|
|
47082
47192
|
connectedActionIdempotencyKey(c)
|
|
47083
47193
|
);
|
|
47084
47194
|
return c.json({ ok: true, result });
|
|
@@ -47086,6 +47196,60 @@ var init_server = __esm({
|
|
|
47086
47196
|
return scheduleConnectionError(c, err, "Unable to send the email.");
|
|
47087
47197
|
}
|
|
47088
47198
|
});
|
|
47199
|
+
app.post("/schedule-connections/actions/gmail/search-contacts", auth2, requireIntegrationsTier, async (c) => {
|
|
47200
|
+
const user = c.get("user");
|
|
47201
|
+
const body = await c.req.json().catch(() => ({}));
|
|
47202
|
+
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
47203
|
+
const query = typeof body.query === "string" ? body.query.trim() : "";
|
|
47204
|
+
const maxMessages = Math.min(150, Math.max(1, Number.isFinite(body.maxMessages) ? Number(body.maxMessages) : 50));
|
|
47205
|
+
const pageToken = typeof body.pageToken === "string" ? body.pageToken : void 0;
|
|
47206
|
+
if (!connectionId || !query) return c.json({ ok: false, error: "connectionId and query are required." }, 400);
|
|
47207
|
+
try {
|
|
47208
|
+
const listRaw = await callScheduleConnectionRead(user.email, connectionId, "list-messages", { q: query, maxResults: maxMessages, pageToken });
|
|
47209
|
+
const listText = toolResultText(listRaw);
|
|
47210
|
+
const list = listText ? JSON.parse(listText) : {};
|
|
47211
|
+
const ids = (list.messages ?? []).map((m) => m.id);
|
|
47212
|
+
const messages = [];
|
|
47213
|
+
const contacts = /* @__PURE__ */ new Map();
|
|
47214
|
+
for (const id of ids) {
|
|
47215
|
+
const msgRaw = await callScheduleConnectionRead(user.email, connectionId, "get-message", { id, format: "metadata" });
|
|
47216
|
+
const msgText = toolResultText(msgRaw);
|
|
47217
|
+
if (!msgText) continue;
|
|
47218
|
+
const msg = JSON.parse(msgText);
|
|
47219
|
+
const headers = new Map((msg.payload?.headers ?? []).map((h) => [h.name.toLowerCase(), h.value]));
|
|
47220
|
+
const date = headers.get("date") ?? null;
|
|
47221
|
+
const fromRaw = headers.get("from");
|
|
47222
|
+
const from = parseEmailAddress(fromRaw);
|
|
47223
|
+
const subject = headers.get("subject") ?? null;
|
|
47224
|
+
messages.push({ id, date, from: fromRaw ?? null, to: headers.get("to") ?? null, subject, snippet: msg.snippet ?? null });
|
|
47225
|
+
if (from) {
|
|
47226
|
+
const domain = from.email.split("@")[1] ?? null;
|
|
47227
|
+
const existing = contacts.get(from.email);
|
|
47228
|
+
if (existing) {
|
|
47229
|
+
existing.messageCount += 1;
|
|
47230
|
+
existing.name = existing.name ?? from.name;
|
|
47231
|
+
if (subject && existing.sampleSubjects.length < 5 && !existing.sampleSubjects.includes(subject)) existing.sampleSubjects.push(subject);
|
|
47232
|
+
if (date && (!existing.lastSeen || date > existing.lastSeen)) existing.lastSeen = date;
|
|
47233
|
+
if (date && (!existing.firstSeen || date < existing.firstSeen)) existing.firstSeen = date;
|
|
47234
|
+
} else {
|
|
47235
|
+
contacts.set(from.email, { email: from.email, name: from.name, domain, messageCount: 1, firstSeen: date, lastSeen: date, sampleSubjects: subject ? [subject] : [] });
|
|
47236
|
+
}
|
|
47237
|
+
}
|
|
47238
|
+
}
|
|
47239
|
+
const totalMatches = list.resultSizeEstimate;
|
|
47240
|
+
return c.json({
|
|
47241
|
+
ok: true,
|
|
47242
|
+
totalMatches,
|
|
47243
|
+
messagesFetched: messages.length,
|
|
47244
|
+
truncated: typeof totalMatches === "number" ? totalMatches > messages.length : Boolean(list.nextPageToken),
|
|
47245
|
+
nextPageToken: list.nextPageToken ?? null,
|
|
47246
|
+
messages,
|
|
47247
|
+
contacts: Array.from(contacts.values())
|
|
47248
|
+
});
|
|
47249
|
+
} catch (err) {
|
|
47250
|
+
return scheduleConnectionError(c, err, "Unable to search Gmail contacts.");
|
|
47251
|
+
}
|
|
47252
|
+
});
|
|
47089
47253
|
app.post("/schedule-connections/actions/google-calendar/create-event", auth2, requireIntegrationsTier, async (c) => {
|
|
47090
47254
|
const user = c.get("user");
|
|
47091
47255
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -47104,7 +47268,7 @@ var init_server = __esm({
|
|
|
47104
47268
|
start: { dateTime: body.startDateTime, timeZone },
|
|
47105
47269
|
end: { dateTime: body.endDateTime, timeZone },
|
|
47106
47270
|
attendees
|
|
47107
|
-
},
|
|
47271
|
+
}, "create-event", connectedActionIdempotencyKey(c));
|
|
47108
47272
|
return c.json({ ok: true, result });
|
|
47109
47273
|
} catch (err) {
|
|
47110
47274
|
return scheduleConnectionError(c, err, "Unable to create the calendar event.");
|
|
@@ -47124,7 +47288,7 @@ var init_server = __esm({
|
|
|
47124
47288
|
durationMinutes: typeof body.durationMinutes === "number" ? body.durationMinutes : 30,
|
|
47125
47289
|
timezone: typeof body.timezone === "string" ? body.timezone : void 0,
|
|
47126
47290
|
agenda: body.agenda
|
|
47127
|
-
},
|
|
47291
|
+
}, "create-meeting", connectedActionIdempotencyKey(c));
|
|
47128
47292
|
return c.json({ ok: true, result });
|
|
47129
47293
|
} catch (err) {
|
|
47130
47294
|
return scheduleConnectionError(c, err, "Unable to create the Zoom meeting.");
|
|
@@ -48131,7 +48295,16 @@ var init_server = __esm({
|
|
|
48131
48295
|
items: [{ id: itemId, price: tier.price_id }],
|
|
48132
48296
|
proration_behavior: "create_prorations"
|
|
48133
48297
|
});
|
|
48134
|
-
|
|
48298
|
+
const billed = await chargeTierChangeNow(stripeClient, user.subscription_id, customerId);
|
|
48299
|
+
if (!billed.ok) {
|
|
48300
|
+
return c.json({
|
|
48301
|
+
updated: true,
|
|
48302
|
+
tier: tier.tier,
|
|
48303
|
+
billed: false,
|
|
48304
|
+
error: billed.error ?? "Plan changed, but the prorated charge could not be collected. Update your payment method and it will retry automatically."
|
|
48305
|
+
});
|
|
48306
|
+
}
|
|
48307
|
+
return c.json({ updated: true, tier: tier.tier, billed: true });
|
|
48135
48308
|
}
|
|
48136
48309
|
}
|
|
48137
48310
|
const session = await stripeClient.checkout.sessions.create({
|
|
@@ -48248,7 +48421,16 @@ var init_server = __esm({
|
|
|
48248
48421
|
items: [{ id: itemId, price: tier.price_id }],
|
|
48249
48422
|
proration_behavior: "create_prorations"
|
|
48250
48423
|
});
|
|
48251
|
-
|
|
48424
|
+
const billed = await chargeTierChangeNow(stripeClient, user.subscription_id, customerId);
|
|
48425
|
+
if (!billed.ok) {
|
|
48426
|
+
return c.json({
|
|
48427
|
+
updated: true,
|
|
48428
|
+
tier: tier.tier,
|
|
48429
|
+
billed: false,
|
|
48430
|
+
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.`
|
|
48431
|
+
});
|
|
48432
|
+
}
|
|
48433
|
+
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.` });
|
|
48252
48434
|
}
|
|
48253
48435
|
}
|
|
48254
48436
|
const session = await stripeClient.checkout.sessions.create({
|