mcp-scraper 0.32.2 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin/api-server.cjs +432 -260
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +5 -5
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +2 -2
- 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 +47 -6
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/bin/paa-harvest.js +1 -1
- package/dist/{chunk-MKZIL4EA.js → chunk-AT2PFWHW.js} +5 -5
- package/dist/chunk-AT2PFWHW.js.map +1 -0
- package/dist/{chunk-AEMABLEL.js → chunk-ERORC7AP.js} +1 -3
- package/dist/chunk-IASKABFL.js +7 -0
- package/dist/chunk-IASKABFL.js.map +1 -0
- package/dist/{chunk-XEED325T.js → chunk-JURXY3DP.js} +48 -7
- package/dist/chunk-JURXY3DP.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/{server-LTZDJHFG.js → server-RKAKXRAR.js} +378 -247
- package/dist/server-RKAKXRAR.js.map +1 -0
- package/dist/{worker-ZZCZNY3R.js → worker-JU5FX3NE.js} +2 -2
- package/docs/adr/0003-waive-unrecoverable-scheduled-model-cost.md +22 -0
- package/docs/adr/README.md +1 -0
- package/docs/mcp-tool-manifest.generated.json +170 -13
- package/package.json +1 -1
- package/dist/chunk-MKZIL4EA.js.map +0 -1
- package/dist/chunk-XEED325T.js.map +0 -1
- package/dist/chunk-XLO5CZ6T.js +0 -7
- package/dist/chunk-XLO5CZ6T.js.map +0 -1
- package/dist/server-LTZDJHFG.js.map +0 -1
- /package/dist/{chunk-AEMABLEL.js.map → chunk-ERORC7AP.js.map} +0 -0
- /package/dist/{worker-ZZCZNY3R.js.map → worker-JU5FX3NE.js.map} +0 -0
package/dist/bin/api-server.cjs
CHANGED
|
@@ -15945,7 +15945,7 @@ var init_server_schemas = __esm({
|
|
|
15945
15945
|
gl: import_zod15.z.string().optional(),
|
|
15946
15946
|
hl: import_zod15.z.string().optional(),
|
|
15947
15947
|
device: import_zod15.z.enum(["desktop", "mobile"]).optional(),
|
|
15948
|
-
proxyMode: import_zod15.z.enum(["
|
|
15948
|
+
proxyMode: import_zod15.z.enum(["configured", "none"]).optional(),
|
|
15949
15949
|
proxyZip: import_zod15.z.string().regex(/^\d{5}$/).optional(),
|
|
15950
15950
|
debug: import_zod15.z.boolean().optional(),
|
|
15951
15951
|
serpOnly: import_zod15.z.boolean().optional(),
|
|
@@ -19253,22 +19253,35 @@ var init_video_routes = __esm({
|
|
|
19253
19253
|
function buildGoogleLocalResultsUrl(input) {
|
|
19254
19254
|
const query = [input.query, input.location].filter(Boolean).join(" ");
|
|
19255
19255
|
const params = new URLSearchParams({ q: query, udm: "1", gl: input.gl, hl: input.hl, pws: "0" });
|
|
19256
|
-
if (input.location) params.set("uule", encodeUule(normalizeLocation(input.location)));
|
|
19257
19256
|
return `https://www.google.com/search?${params.toString()}`;
|
|
19258
19257
|
}
|
|
19259
19258
|
function buildGoogleOrganicSearchUrl(input) {
|
|
19260
19259
|
const query = [input.query, input.location].filter(Boolean).join(" ");
|
|
19261
19260
|
const params = new URLSearchParams({ q: query, gl: input.gl, hl: input.hl, pws: "0" });
|
|
19262
|
-
if (input.location) params.set("uule", encodeUule(normalizeLocation(input.location)));
|
|
19263
19261
|
return `https://www.google.com/search?${params.toString()}`;
|
|
19264
19262
|
}
|
|
19263
|
+
function mapsIdentifiersFromUrl(url) {
|
|
19264
|
+
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);
|
|
19265
|
+
if (fid) {
|
|
19266
|
+
try {
|
|
19267
|
+
return { cid: `${fid[1]}:${fid[2]}`, cidDecimal: BigInt(fid[2]).toString() };
|
|
19268
|
+
} catch {
|
|
19269
|
+
return { cid: `${fid[1]}:${fid[2]}`, cidDecimal: null };
|
|
19270
|
+
}
|
|
19271
|
+
}
|
|
19272
|
+
try {
|
|
19273
|
+
const decimal = new URL(url).searchParams.get("cid");
|
|
19274
|
+
return { cid: null, cidDecimal: decimal && /^\d+$/.test(decimal) ? decimal : null };
|
|
19275
|
+
} catch {
|
|
19276
|
+
return { cid: null, cidDecimal: null };
|
|
19277
|
+
}
|
|
19278
|
+
}
|
|
19265
19279
|
var LOCAL_RESULTS_PAGE_LIMIT, LOCAL_RESULTS_WAIT_MS, MAPS_REDIRECT_BASE, MapsSearchExtractor;
|
|
19266
19280
|
var init_MapsSearchExtractor = __esm({
|
|
19267
19281
|
"src/extractor/MapsSearchExtractor.ts"() {
|
|
19268
19282
|
"use strict";
|
|
19269
19283
|
init_errors();
|
|
19270
19284
|
init_BrowserDriver();
|
|
19271
|
-
init_uule();
|
|
19272
19285
|
LOCAL_RESULTS_PAGE_LIMIT = 8;
|
|
19273
19286
|
LOCAL_RESULTS_WAIT_MS = 1e3;
|
|
19274
19287
|
MAPS_REDIRECT_BASE = "https://serp-redirector.vercel.app";
|
|
@@ -19347,13 +19360,15 @@ var init_MapsSearchExtractor = __esm({
|
|
|
19347
19360
|
for (const card of cards) {
|
|
19348
19361
|
if (results.length >= options.maxResults || seen.has(card.cardKey)) continue;
|
|
19349
19362
|
seen.add(card.cardKey);
|
|
19363
|
+
const cardIds = mapsIdentifiersFromUrl(card.directionsUrl ?? "");
|
|
19364
|
+
const cardPlaceUrl = cardIds.cidDecimal ? `https://www.google.com/maps?cid=${cardIds.cidDecimal}` : card.placeUrl;
|
|
19350
19365
|
const details = await this.openCardAndExtractDialog(page, card, options.includeServices);
|
|
19351
19366
|
results.push({
|
|
19352
19367
|
position: results.length + 1,
|
|
19353
19368
|
name: card.name,
|
|
19354
|
-
placeUrl: details?.placeUrl ??
|
|
19355
|
-
cid: details?.cid ??
|
|
19356
|
-
cidDecimal: details?.cidDecimal ??
|
|
19369
|
+
placeUrl: details?.placeUrl ?? cardPlaceUrl,
|
|
19370
|
+
cid: details?.cid ?? cardIds.cid,
|
|
19371
|
+
cidDecimal: details?.cidDecimal ?? cardIds.cidDecimal,
|
|
19357
19372
|
rating: details?.rating ?? card.rating,
|
|
19358
19373
|
reviewCount: details?.reviewCount ?? card.reviewCount,
|
|
19359
19374
|
category: details?.category ?? card.category,
|
|
@@ -19410,8 +19425,10 @@ var init_MapsSearchExtractor = __esm({
|
|
|
19410
19425
|
const phone = lines.map((line) => line.match(phonePattern)?.[0]).find((value) => Boolean(value)) ?? null;
|
|
19411
19426
|
const hoursStatus = normalize4((lines.find((line) => /\b(?:open|closed|opens|closes)\b/i.test(line)) ?? "").split("\xB7")[0] ?? "");
|
|
19412
19427
|
const address = lines.find((line) => addressPattern.test(line)) ?? null;
|
|
19413
|
-
const
|
|
19428
|
+
const anchorHrefs = Array.from(card.querySelectorAll("a[href]")).map((anchor) => anchor.href);
|
|
19429
|
+
const websiteUrl = anchorHrefs.find((href) => /^https?:/i.test(href) && !/google\.|gstatic|googleusercontent/.test(href)) ?? null;
|
|
19414
19430
|
if (!rating && !(phone && websiteUrl)) continue;
|
|
19431
|
+
const fidHref = anchorHrefs.find((href) => /(0x[0-9a-f]+):(0x[0-9a-f]+)/i.test(href)) ?? null;
|
|
19415
19432
|
const key = `${name.toLowerCase()}|${lines.join(" ").toLowerCase()}`;
|
|
19416
19433
|
if (seen.has(key)) continue;
|
|
19417
19434
|
seen.add(key);
|
|
@@ -19431,7 +19448,7 @@ var init_MapsSearchExtractor = __esm({
|
|
|
19431
19448
|
phone,
|
|
19432
19449
|
hoursStatus,
|
|
19433
19450
|
websiteUrl,
|
|
19434
|
-
directionsUrl: `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent([name, address].filter(Boolean).join(", "))}`,
|
|
19451
|
+
directionsUrl: fidHref ?? `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent([name, address].filter(Boolean).join(", "))}`,
|
|
19435
19452
|
metadata: lines.slice(0, 20)
|
|
19436
19453
|
});
|
|
19437
19454
|
}
|
|
@@ -20143,6 +20160,7 @@ async function cleanupDisposableProxy(kernelApiKey, proxyId, eventName) {
|
|
|
20143
20160
|
}
|
|
20144
20161
|
}
|
|
20145
20162
|
async function runMapsSearchWithRotation(options) {
|
|
20163
|
+
if (options.proxyMode === "location") options = { ...options, proxyMode: "none" };
|
|
20146
20164
|
const attempts = [];
|
|
20147
20165
|
const maxAttempts = mapsSearchMaxAttemptsForProxyMode(options.proxyMode) + 1;
|
|
20148
20166
|
const started = Date.now();
|
|
@@ -24118,7 +24136,7 @@ var init_directory_workflow = __esm({
|
|
|
24118
24136
|
saveCsv: import_zod26.z.boolean().default(true),
|
|
24119
24137
|
gl: import_zod26.z.string().length(2).default("us"),
|
|
24120
24138
|
hl: import_zod26.z.string().length(2).default("en"),
|
|
24121
|
-
proxyMode: import_zod26.z.enum(["
|
|
24139
|
+
proxyMode: import_zod26.z.enum(["configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
|
|
24122
24140
|
proxyZip: import_zod26.z.string().regex(/^\d{5}$/).optional(),
|
|
24123
24141
|
debug: import_zod26.z.boolean().default(false),
|
|
24124
24142
|
headless: import_zod26.z.boolean().default(true),
|
|
@@ -24774,7 +24792,7 @@ var init_directory = __esm({
|
|
|
24774
24792
|
maxCities: import_zod28.z.number().int().min(1).max(100).default(25),
|
|
24775
24793
|
maxResultsPerCity: import_zod28.z.number().int().min(1).max(50).default(20),
|
|
24776
24794
|
concurrency: import_zod28.z.number().int().min(1).max(5).default(5),
|
|
24777
|
-
proxyMode: import_zod28.z.enum(["
|
|
24795
|
+
proxyMode: import_zod28.z.enum(["configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
|
|
24778
24796
|
saveCsv: import_zod28.z.boolean().default(true)
|
|
24779
24797
|
});
|
|
24780
24798
|
DIRECTORY_CSV_HEADERS = [
|
|
@@ -24929,7 +24947,7 @@ var init_get_leads = __esm({
|
|
|
24929
24947
|
enrichWebsites: import_zod29.z.boolean().default(true).describe("Visit each business website (home + contact pages) to harvest email and social links. Uses the proxy/browser-backed extractor so blocked sites still resolve."),
|
|
24930
24948
|
hydrateReviewCounts: import_zod29.z.boolean().default(true).describe("Deep-dive each profile to confirm the review count and booking URL that the Maps search list omits."),
|
|
24931
24949
|
concurrency: import_zod29.z.number().int().min(1).max(4).default(3).describe("How many businesses to enrich in parallel. Keep low to respect per-account concurrency limits."),
|
|
24932
|
-
proxyMode: import_zod29.z.enum(["
|
|
24950
|
+
proxyMode: import_zod29.z.enum(["configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy behavior for the Maps search. Leave unset for clean egress; country/region localization comes from gl/hl plus the city or region in the query.")
|
|
24933
24951
|
});
|
|
24934
24952
|
LEADS_CSV_HEADERS = [
|
|
24935
24953
|
"position",
|
|
@@ -25148,7 +25166,7 @@ var init_local_competitive_audit = __esm({
|
|
|
25148
25166
|
hydrateTop: import_zod30.z.number().int().min(0).max(10).default(5),
|
|
25149
25167
|
maxReviews: import_zod30.z.number().int().min(0).max(500).default(50),
|
|
25150
25168
|
concurrency: import_zod30.z.number().int().min(1).max(5).default(5),
|
|
25151
|
-
proxyMode: import_zod30.z.enum(["
|
|
25169
|
+
proxyMode: import_zod30.z.enum(["configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
|
|
25152
25170
|
returnPartial: import_zod30.z.boolean().default(true)
|
|
25153
25171
|
});
|
|
25154
25172
|
localCompetitiveAuditWorkflowDefinition = {
|
|
@@ -25601,7 +25619,7 @@ var init_comparison_briefs = __esm({
|
|
|
25601
25619
|
init_directory();
|
|
25602
25620
|
init_seo_workflow_utils();
|
|
25603
25621
|
init_schemas3();
|
|
25604
|
-
ProxyModeSchema = import_zod31.z.enum(["
|
|
25622
|
+
ProxyModeSchema = import_zod31.z.enum(["configured", "none"]);
|
|
25605
25623
|
MapComparisonInputSchema = import_zod31.z.object({
|
|
25606
25624
|
query: import_zod31.z.string().min(1),
|
|
25607
25625
|
location: import_zod31.z.string().optional(),
|
|
@@ -29794,7 +29812,7 @@ var PACKAGE_VERSION;
|
|
|
29794
29812
|
var init_version = __esm({
|
|
29795
29813
|
"src/version.ts"() {
|
|
29796
29814
|
"use strict";
|
|
29797
|
-
PACKAGE_VERSION = "0.
|
|
29815
|
+
PACKAGE_VERSION = "0.33.0";
|
|
29798
29816
|
}
|
|
29799
29817
|
});
|
|
29800
29818
|
|
|
@@ -30245,7 +30263,7 @@ var init_meta_ad_creative_media = __esm({
|
|
|
30245
30263
|
});
|
|
30246
30264
|
|
|
30247
30265
|
// src/mcp/mcp-tool-schemas.ts
|
|
30248
|
-
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;
|
|
30266
|
+
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;
|
|
30249
30267
|
var init_mcp_tool_schemas = __esm({
|
|
30250
30268
|
"src/mcp/mcp-tool-schemas.ts"() {
|
|
30251
30269
|
"use strict";
|
|
@@ -30258,7 +30276,7 @@ var init_mcp_tool_schemas = __esm({
|
|
|
30258
30276
|
gl: import_zod35.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
|
|
30259
30277
|
hl: import_zod35.z.string().default("en").describe("Google interface/content language inferred from the user request."),
|
|
30260
30278
|
device: import_zod35.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings."),
|
|
30261
|
-
proxyMode: import_zod35.z.enum(["
|
|
30279
|
+
proxyMode: import_zod35.z.enum(["configured", "none"]).default(DEFAULT_PROXY_MODE).describe("Leave unset for clean egress (the default). Country/region localization comes from gl/hl plus the city or region in the query \u2014 not from a proxy."),
|
|
30262
30280
|
proxyZip: import_zod35.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
|
|
30263
30281
|
debug: import_zod35.z.boolean().default(false).describe("Include sanitized diagnostics for debugging localization, CAPTCHA, or proxy behavior.")
|
|
30264
30282
|
};
|
|
@@ -30427,7 +30445,7 @@ var init_mcp_tool_schemas = __esm({
|
|
|
30427
30445
|
hl: import_zod35.z.string().length(2).default("en").describe("Language inferred from user request."),
|
|
30428
30446
|
maxResults: import_zod35.z.number().int().min(1).max(50).default(10).describe("Number of candidates to return. Default 10, maximum 50."),
|
|
30429
30447
|
includeServices: import_zod35.z.boolean().default(false).describe("Open each returned business profile to include its configured services and areas served when available. Adds a page visit per business; does not collect review cards."),
|
|
30430
|
-
proxyMode: import_zod35.z.enum(["
|
|
30448
|
+
proxyMode: import_zod35.z.enum(["configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Leave unset for the default route (stealth browser on the managed ISP proxy, retried on a fresh session when Google blocks). Country/region localization comes from the city or region in the query plus gl/hl \u2014 not from a proxy."),
|
|
30431
30449
|
proxyZip: import_zod35.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override, only used when proxyMode is location."),
|
|
30432
30450
|
debug: import_zod35.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
|
|
30433
30451
|
};
|
|
@@ -30442,7 +30460,7 @@ var init_mcp_tool_schemas = __esm({
|
|
|
30442
30460
|
includeZipGroups: import_zod35.z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available (MCP_SCRAPER_USZIPS_CSV_PATH or usZipsCsvPath)."),
|
|
30443
30461
|
usZipsCsvPath: import_zod35.z.string().optional().describe("Local/test-only path to a US ZIPS CSV (state_abbr, zipcode, county, city columns). Deployed APIs should use MCP_SCRAPER_USZIPS_CSV_PATH instead. For ZIP enrichment, set MCP_SCRAPER_USZIPS_CSV_PATH on the server, or pass this in local/test mode."),
|
|
30444
30462
|
saveCsv: import_zod35.z.boolean().default(true).describe("Save a directory-ready CSV of results to the MCP Scraper output directory and return its path."),
|
|
30445
|
-
proxyMode: import_zod35.z.enum(["
|
|
30463
|
+
proxyMode: import_zod35.z.enum(["configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy behavior per city search. Leave unset for the default route (stealth browser on the managed ISP proxy, retried fresh on a Google block). Country/region localization comes from the city or region in the query plus gl/hl \u2014 not from a proxy."),
|
|
30446
30464
|
proxyZip: import_zod35.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting; normally omitted."),
|
|
30447
30465
|
debug: import_zod35.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
|
|
30448
30466
|
};
|
|
@@ -31282,7 +31300,7 @@ var init_mcp_tool_schemas = __esm({
|
|
|
31282
31300
|
gl: import_zod35.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
|
|
31283
31301
|
hl: import_zod35.z.string().default("en").describe("Google interface/content language inferred from user request."),
|
|
31284
31302
|
device: import_zod35.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings."),
|
|
31285
|
-
proxyMode: import_zod35.z.enum(["
|
|
31303
|
+
proxyMode: import_zod35.z.enum(["configured", "none"]).default(DEFAULT_PROXY_MODE).describe("Leave unset for clean egress (the default). Country/region localization comes from gl/hl plus the city or region in the query \u2014 not from a proxy."),
|
|
31286
31304
|
proxyZip: import_zod35.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
|
|
31287
31305
|
debug: import_zod35.z.boolean().default(false).describe("Include sanitized diagnostics for debugging localization, CAPTCHA, or proxy behavior."),
|
|
31288
31306
|
pages: import_zod35.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132).")
|
|
@@ -31293,7 +31311,7 @@ var init_mcp_tool_schemas = __esm({
|
|
|
31293
31311
|
gl: import_zod35.z.string().length(2).default("us").describe("Google country code inferred from the requested market."),
|
|
31294
31312
|
hl: import_zod35.z.string().default("en").describe("Google interface/content language inferred from the user request."),
|
|
31295
31313
|
device: import_zod35.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings/evidence."),
|
|
31296
|
-
proxyMode: import_zod35.z.enum(["
|
|
31314
|
+
proxyMode: import_zod35.z.enum(["configured", "none"]).default(DEFAULT_PROXY_MODE).describe("Leave unset for clean egress (the default). Country/region localization comes from gl/hl plus the city or region in the query \u2014 not from a proxy."),
|
|
31297
31315
|
proxyZip: import_zod35.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
|
|
31298
31316
|
pages: import_zod35.z.number().int().min(1).max(2).default(1).describe("Google result pages to capture. Use 2 only for deeper ranking evidence."),
|
|
31299
31317
|
debug: import_zod35.z.boolean().default(false).describe("Include sanitized browser/proxy/location diagnostics."),
|
|
@@ -31652,6 +31670,37 @@ var init_mcp_tool_schemas = __esm({
|
|
|
31652
31670
|
result: import_zod35.z.unknown().optional(),
|
|
31653
31671
|
error: NullableString
|
|
31654
31672
|
};
|
|
31673
|
+
GmailSearchContactsInputSchema = {
|
|
31674
|
+
connectionId: import_zod35.z.string().min(1).describe("A Gmail connectionId from list_service_connections. Read-only \u2014 does not require actionsEnabled."),
|
|
31675
|
+
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".'),
|
|
31676
|
+
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."),
|
|
31677
|
+
pageToken: import_zod35.z.string().optional().describe("Continuation token from a prior response to fetch the next page.")
|
|
31678
|
+
};
|
|
31679
|
+
GmailSearchContactsOutputSchema = {
|
|
31680
|
+
ok: import_zod35.z.boolean(),
|
|
31681
|
+
totalMatches: import_zod35.z.number().optional().describe("Gmail's estimated total matches for the query, independent of how many were fetched this call."),
|
|
31682
|
+
messagesFetched: import_zod35.z.number().optional(),
|
|
31683
|
+
truncated: import_zod35.z.boolean().optional().describe("True when totalMatches exceeds messagesFetched \u2014 more results exist, use nextPageToken."),
|
|
31684
|
+
nextPageToken: NullableString,
|
|
31685
|
+
messages: import_zod35.z.array(import_zod35.z.object({
|
|
31686
|
+
id: import_zod35.z.string(),
|
|
31687
|
+
date: NullableString,
|
|
31688
|
+
from: NullableString,
|
|
31689
|
+
to: NullableString,
|
|
31690
|
+
subject: NullableString,
|
|
31691
|
+
snippet: NullableString
|
|
31692
|
+
})).optional(),
|
|
31693
|
+
contacts: import_zod35.z.array(import_zod35.z.object({
|
|
31694
|
+
email: import_zod35.z.string(),
|
|
31695
|
+
name: NullableString,
|
|
31696
|
+
domain: NullableString,
|
|
31697
|
+
messageCount: import_zod35.z.number(),
|
|
31698
|
+
firstSeen: NullableString,
|
|
31699
|
+
lastSeen: NullableString,
|
|
31700
|
+
sampleSubjects: import_zod35.z.array(import_zod35.z.string())
|
|
31701
|
+
})).optional().describe("Messages deduped by sender email address, newest-to-oldest within this fetch only \u2014 not the full query result set when truncated."),
|
|
31702
|
+
error: NullableString
|
|
31703
|
+
};
|
|
31655
31704
|
GoogleCalendarCreateEventInputSchema = {
|
|
31656
31705
|
connectionId: import_zod35.z.string().min(1).describe("A Google Calendar connectionId from list_service_connections, with actionsEnabled true."),
|
|
31657
31706
|
calendarId: import_zod35.z.string().min(1).default("primary").describe('Calendar to create the event in. Default "primary".'),
|
|
@@ -32398,6 +32447,13 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
32398
32447
|
outputSchema: recordOutputSchema("gmail_send_message", GmailSendMessageOutputSchema),
|
|
32399
32448
|
annotations: { title: "Send Gmail Message", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
32400
32449
|
}, async (input) => executor.gmailSendMessage(input));
|
|
32450
|
+
server.registerTool("gmail_search_contacts", {
|
|
32451
|
+
title: "Search Gmail Contacts",
|
|
32452
|
+
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.',
|
|
32453
|
+
inputSchema: GmailSearchContactsInputSchema,
|
|
32454
|
+
outputSchema: recordOutputSchema("gmail_search_contacts", GmailSearchContactsOutputSchema),
|
|
32455
|
+
annotations: { title: "Search Gmail Contacts", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
32456
|
+
}, async (input) => executor.gmailSearchContacts(input));
|
|
32401
32457
|
server.registerTool("google_calendar_create_event", {
|
|
32402
32458
|
title: "Create Calendar Event",
|
|
32403
32459
|
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.",
|
|
@@ -32807,6 +32863,9 @@ var init_http_mcp_tool_executor = __esm({
|
|
|
32807
32863
|
gmailSendMessage(input) {
|
|
32808
32864
|
return this.call("/schedule-connections/actions/gmail/send-message", input);
|
|
32809
32865
|
}
|
|
32866
|
+
gmailSearchContacts(input) {
|
|
32867
|
+
return this.call("/schedule-connections/actions/gmail/search-contacts", input);
|
|
32868
|
+
}
|
|
32810
32869
|
googleCalendarCreateEvent(input) {
|
|
32811
32870
|
return this.call("/schedule-connections/actions/google-calendar/create-event", input);
|
|
32812
32871
|
}
|
|
@@ -40068,6 +40127,225 @@ var init_browser_agent_console = __esm({
|
|
|
40068
40127
|
}
|
|
40069
40128
|
});
|
|
40070
40129
|
|
|
40130
|
+
// src/api/connected-account-billing.ts
|
|
40131
|
+
async function ensureConnectedAccountBillingSchema() {
|
|
40132
|
+
await getDb().execute(`CREATE TABLE IF NOT EXISTS connected_account_billing (
|
|
40133
|
+
user_id INTEGER PRIMARY KEY,
|
|
40134
|
+
stripe_subscription_id TEXT,
|
|
40135
|
+
stripe_subscription_item_id TEXT,
|
|
40136
|
+
price_id TEXT,
|
|
40137
|
+
quantity INTEGER NOT NULL DEFAULT 0,
|
|
40138
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
40139
|
+
last_error_code TEXT,
|
|
40140
|
+
synced_at TEXT,
|
|
40141
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
40142
|
+
)`);
|
|
40143
|
+
}
|
|
40144
|
+
async function getConnectedAccountBillingState(userId) {
|
|
40145
|
+
await ensureConnectedAccountBillingSchema();
|
|
40146
|
+
const result = await getDb().execute({
|
|
40147
|
+
sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
|
|
40148
|
+
args: [Number(userId)]
|
|
40149
|
+
});
|
|
40150
|
+
return result.rows[0] ? result.rows[0] : null;
|
|
40151
|
+
}
|
|
40152
|
+
async function setConnectedAccountBillingState(input) {
|
|
40153
|
+
await ensureConnectedAccountBillingSchema();
|
|
40154
|
+
await getDb().execute({
|
|
40155
|
+
sql: `INSERT INTO connected_account_billing
|
|
40156
|
+
(user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, synced_at, updated_at)
|
|
40157
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, CASE WHEN ? THEN datetime('now') ELSE NULL END, datetime('now'))
|
|
40158
|
+
ON CONFLICT(user_id) DO UPDATE SET
|
|
40159
|
+
stripe_subscription_id = excluded.stripe_subscription_id,
|
|
40160
|
+
stripe_subscription_item_id = excluded.stripe_subscription_item_id,
|
|
40161
|
+
price_id = excluded.price_id,
|
|
40162
|
+
quantity = excluded.quantity,
|
|
40163
|
+
status = excluded.status,
|
|
40164
|
+
last_error_code = excluded.last_error_code,
|
|
40165
|
+
synced_at = CASE WHEN ? THEN datetime('now') ELSE connected_account_billing.synced_at END,
|
|
40166
|
+
updated_at = datetime('now')`,
|
|
40167
|
+
args: [
|
|
40168
|
+
Number(input.userId),
|
|
40169
|
+
input.stripeSubscriptionId,
|
|
40170
|
+
input.stripeSubscriptionItemId,
|
|
40171
|
+
input.priceId,
|
|
40172
|
+
input.quantity,
|
|
40173
|
+
input.status,
|
|
40174
|
+
input.lastErrorCode,
|
|
40175
|
+
input.synced ? 1 : 0,
|
|
40176
|
+
input.synced ? 1 : 0
|
|
40177
|
+
]
|
|
40178
|
+
});
|
|
40179
|
+
const state = await getConnectedAccountBillingState(input.userId);
|
|
40180
|
+
if (!state) throw new Error("connected account billing state was not persisted");
|
|
40181
|
+
return state;
|
|
40182
|
+
}
|
|
40183
|
+
function connectedAccountPriceId() {
|
|
40184
|
+
const configured = process.env.CONNECTED_ACCOUNT_PRICE_ID?.trim();
|
|
40185
|
+
if (configured) return configured;
|
|
40186
|
+
return DEFAULT_CONNECTED_ACCOUNT_PRICE_ID || null;
|
|
40187
|
+
}
|
|
40188
|
+
function connectedAccountBillingView(actualQuantity, state) {
|
|
40189
|
+
const quantity = Math.max(0, Math.round(actualQuantity));
|
|
40190
|
+
return {
|
|
40191
|
+
provider: "nango",
|
|
40192
|
+
billingMode: "flat_recurring_usd_plus_credits",
|
|
40193
|
+
priceId: connectedAccountPriceId(),
|
|
40194
|
+
unitAmountUsd: CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
|
|
40195
|
+
interval: "month",
|
|
40196
|
+
quantity,
|
|
40197
|
+
projectedMonthlyUsd: quantity * CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
|
|
40198
|
+
status: state?.status ?? "pending",
|
|
40199
|
+
lastErrorCode: state?.last_error_code ?? null,
|
|
40200
|
+
syncedAt: state?.synced_at ?? null,
|
|
40201
|
+
usage: CONNECTED_USAGE_RATE_POLICY.usage
|
|
40202
|
+
};
|
|
40203
|
+
}
|
|
40204
|
+
function findBasePlanItem(subscription) {
|
|
40205
|
+
return subscription.items.data.find((item) => !!item.price?.id && item.price.id in SUBSCRIPTION_TIERS);
|
|
40206
|
+
}
|
|
40207
|
+
function findConnectedAccountItem(subscription, priceId = connectedAccountPriceId()) {
|
|
40208
|
+
return priceId ? subscription.items.data.find((item) => item.price?.id === priceId) : void 0;
|
|
40209
|
+
}
|
|
40210
|
+
function liveSubscription(subscription) {
|
|
40211
|
+
return subscription.status === "active" || subscription.status === "trialing";
|
|
40212
|
+
}
|
|
40213
|
+
function safeStripeErrorCode(error) {
|
|
40214
|
+
if (error instanceof ConnectedAccountBillingError) return error.code;
|
|
40215
|
+
return "stripe_connection_billing_failed";
|
|
40216
|
+
}
|
|
40217
|
+
async function persistFailure(user, actualQuantity, error, subscriptionItemId = null) {
|
|
40218
|
+
await setConnectedAccountBillingState({
|
|
40219
|
+
userId: user.id,
|
|
40220
|
+
stripeSubscriptionId: user.subscription_id,
|
|
40221
|
+
stripeSubscriptionItemId: subscriptionItemId,
|
|
40222
|
+
priceId: connectedAccountPriceId(),
|
|
40223
|
+
quantity: actualQuantity,
|
|
40224
|
+
status: error instanceof ConnectedAccountBillingError && error.code !== "stripe_connection_billing_failed" ? "blocked" : "error",
|
|
40225
|
+
lastErrorCode: safeStripeErrorCode(error),
|
|
40226
|
+
synced: false
|
|
40227
|
+
});
|
|
40228
|
+
}
|
|
40229
|
+
async function reconcileConnectedAccountBilling(user, actualQuantity, stripeClient) {
|
|
40230
|
+
if (!Number.isSafeInteger(actualQuantity) || actualQuantity < 0) {
|
|
40231
|
+
throw new Error("actualQuantity must be a non-negative safe integer");
|
|
40232
|
+
}
|
|
40233
|
+
const priceId = connectedAccountPriceId();
|
|
40234
|
+
if (!priceId) {
|
|
40235
|
+
const error = new ConnectedAccountBillingError(
|
|
40236
|
+
"Connected-account billing is not configured.",
|
|
40237
|
+
"connected_account_price_not_configured",
|
|
40238
|
+
503
|
|
40239
|
+
);
|
|
40240
|
+
await persistFailure(user, actualQuantity, error);
|
|
40241
|
+
throw error;
|
|
40242
|
+
}
|
|
40243
|
+
if (!user.subscription_id) {
|
|
40244
|
+
if (actualQuantity === 0) {
|
|
40245
|
+
const state = await setConnectedAccountBillingState({
|
|
40246
|
+
userId: user.id,
|
|
40247
|
+
stripeSubscriptionId: null,
|
|
40248
|
+
stripeSubscriptionItemId: null,
|
|
40249
|
+
priceId,
|
|
40250
|
+
quantity: 0,
|
|
40251
|
+
status: "synced",
|
|
40252
|
+
lastErrorCode: null,
|
|
40253
|
+
synced: true
|
|
40254
|
+
});
|
|
40255
|
+
return connectedAccountBillingView(0, state);
|
|
40256
|
+
}
|
|
40257
|
+
const error = new ConnectedAccountBillingError(
|
|
40258
|
+
"An active paid plan is required before connected accounts can be billed.",
|
|
40259
|
+
"paid_plan_required",
|
|
40260
|
+
403
|
|
40261
|
+
);
|
|
40262
|
+
await persistFailure(user, actualQuantity, error);
|
|
40263
|
+
throw error;
|
|
40264
|
+
}
|
|
40265
|
+
const client2 = stripeClient ?? (() => {
|
|
40266
|
+
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
40267
|
+
if (!secret2) {
|
|
40268
|
+
throw new ConnectedAccountBillingError(
|
|
40269
|
+
"Stripe is not configured.",
|
|
40270
|
+
"connected_account_price_not_configured",
|
|
40271
|
+
503
|
|
40272
|
+
);
|
|
40273
|
+
}
|
|
40274
|
+
return new import_stripe.default(secret2, { apiVersion: STRIPE_API_VERSION });
|
|
40275
|
+
})();
|
|
40276
|
+
let currentItem;
|
|
40277
|
+
try {
|
|
40278
|
+
const subscription = await client2.subscriptions.retrieve(user.subscription_id);
|
|
40279
|
+
currentItem = findConnectedAccountItem(subscription, priceId);
|
|
40280
|
+
if (!liveSubscription(subscription)) {
|
|
40281
|
+
throw new ConnectedAccountBillingError(
|
|
40282
|
+
"The plan subscription is not active.",
|
|
40283
|
+
"subscription_not_active",
|
|
40284
|
+
402
|
|
40285
|
+
);
|
|
40286
|
+
}
|
|
40287
|
+
if (!findBasePlanItem(subscription)) {
|
|
40288
|
+
throw new ConnectedAccountBillingError(
|
|
40289
|
+
"The base plan item could not be found on this subscription.",
|
|
40290
|
+
"subscription_plan_item_missing",
|
|
40291
|
+
409
|
|
40292
|
+
);
|
|
40293
|
+
}
|
|
40294
|
+
const currentQuantity = Math.max(0, currentItem?.quantity ?? 0);
|
|
40295
|
+
let resultingItem = currentItem;
|
|
40296
|
+
if (actualQuantity !== currentQuantity) {
|
|
40297
|
+
const updated = await client2.subscriptions.update(subscription.id, {
|
|
40298
|
+
items: actualQuantity === 0 ? currentItem ? [{ id: currentItem.id, deleted: true }] : [] : currentItem ? [{ id: currentItem.id, quantity: actualQuantity }] : [{ price: priceId, quantity: actualQuantity }],
|
|
40299
|
+
proration_behavior: "create_prorations"
|
|
40300
|
+
});
|
|
40301
|
+
resultingItem = findConnectedAccountItem(updated, priceId);
|
|
40302
|
+
}
|
|
40303
|
+
const state = await setConnectedAccountBillingState({
|
|
40304
|
+
userId: user.id,
|
|
40305
|
+
stripeSubscriptionId: subscription.id,
|
|
40306
|
+
stripeSubscriptionItemId: resultingItem?.id ?? null,
|
|
40307
|
+
priceId,
|
|
40308
|
+
quantity: actualQuantity,
|
|
40309
|
+
status: "synced",
|
|
40310
|
+
lastErrorCode: null,
|
|
40311
|
+
synced: true
|
|
40312
|
+
});
|
|
40313
|
+
return connectedAccountBillingView(actualQuantity, state);
|
|
40314
|
+
} catch (error) {
|
|
40315
|
+
await persistFailure(user, actualQuantity, error, currentItem?.id ?? null);
|
|
40316
|
+
if (error instanceof ConnectedAccountBillingError) throw error;
|
|
40317
|
+
throw new ConnectedAccountBillingError(
|
|
40318
|
+
"Unable to update connected-account billing in Stripe.",
|
|
40319
|
+
"stripe_connection_billing_failed",
|
|
40320
|
+
503
|
|
40321
|
+
);
|
|
40322
|
+
}
|
|
40323
|
+
}
|
|
40324
|
+
async function currentConnectedAccountBillingView(userId, actualQuantity) {
|
|
40325
|
+
return connectedAccountBillingView(actualQuantity, await getConnectedAccountBillingState(userId));
|
|
40326
|
+
}
|
|
40327
|
+
var import_stripe, DEFAULT_CONNECTED_ACCOUNT_PRICE_ID, STRIPE_API_VERSION, ConnectedAccountBillingError;
|
|
40328
|
+
var init_connected_account_billing = __esm({
|
|
40329
|
+
"src/api/connected-account-billing.ts"() {
|
|
40330
|
+
"use strict";
|
|
40331
|
+
import_stripe = __toESM(require("stripe"), 1);
|
|
40332
|
+
init_db();
|
|
40333
|
+
init_rates();
|
|
40334
|
+
DEFAULT_CONNECTED_ACCOUNT_PRICE_ID = "price_1TtYPAS8aAcsk3TGHOgrDZiQ";
|
|
40335
|
+
STRIPE_API_VERSION = "2026-02-25.clover";
|
|
40336
|
+
ConnectedAccountBillingError = class extends Error {
|
|
40337
|
+
constructor(message, code, status = 409) {
|
|
40338
|
+
super(message);
|
|
40339
|
+
this.code = code;
|
|
40340
|
+
this.status = status;
|
|
40341
|
+
this.name = "ConnectedAccountBillingError";
|
|
40342
|
+
}
|
|
40343
|
+
code;
|
|
40344
|
+
status;
|
|
40345
|
+
};
|
|
40346
|
+
}
|
|
40347
|
+
});
|
|
40348
|
+
|
|
40071
40349
|
// src/api/stripe-routes.ts
|
|
40072
40350
|
function linePriceId(line) {
|
|
40073
40351
|
const l = line;
|
|
@@ -40081,16 +40359,17 @@ async function resolveUser2(customerId, emailFallback) {
|
|
|
40081
40359
|
if (user) await setStripeCustomerId(user.id, customerId);
|
|
40082
40360
|
return user;
|
|
40083
40361
|
}
|
|
40084
|
-
var
|
|
40362
|
+
var import_stripe2, import_hono19, stripe, stripeApp;
|
|
40085
40363
|
var init_stripe_routes = __esm({
|
|
40086
40364
|
"src/api/stripe-routes.ts"() {
|
|
40087
40365
|
"use strict";
|
|
40088
|
-
|
|
40366
|
+
import_stripe2 = __toESM(require("stripe"), 1);
|
|
40089
40367
|
import_hono19 = require("hono");
|
|
40090
40368
|
init_db();
|
|
40091
40369
|
init_rates();
|
|
40092
40370
|
init_memory();
|
|
40093
|
-
|
|
40371
|
+
init_connected_account_billing();
|
|
40372
|
+
stripe = new import_stripe2.default(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
|
|
40094
40373
|
stripeApp = new import_hono19.Hono();
|
|
40095
40374
|
stripeApp.post("/webhooks", async (c) => {
|
|
40096
40375
|
const sig = c.req.header("stripe-signature");
|
|
@@ -40107,15 +40386,23 @@ var init_stripe_routes = __esm({
|
|
|
40107
40386
|
const invoice = event.data.object;
|
|
40108
40387
|
const memLineId = invoice.lines.data.map(linePriceId).find((id) => id && id in MEMORY_PLANS);
|
|
40109
40388
|
if (memLineId) return c.json({ received: true });
|
|
40110
|
-
const
|
|
40111
|
-
|
|
40112
|
-
|
|
40113
|
-
|
|
40114
|
-
|
|
40115
|
-
|
|
40116
|
-
|
|
40117
|
-
|
|
40118
|
-
|
|
40389
|
+
const invoiceHasTierLine = invoice.lines.data.some((l) => {
|
|
40390
|
+
const id = linePriceId(l);
|
|
40391
|
+
return id && id in SUBSCRIPTION_TIERS;
|
|
40392
|
+
});
|
|
40393
|
+
if (invoiceHasTierLine) {
|
|
40394
|
+
const subId = invoice.subscription;
|
|
40395
|
+
const liveBasePlanPriceId = subId ? findBasePlanItem(await stripe.subscriptions.retrieve(subId))?.price?.id : void 0;
|
|
40396
|
+
const tierPriceId = liveBasePlanPriceId ?? invoice.lines.data.map(linePriceId).find((id) => id && id in SUBSCRIPTION_TIERS);
|
|
40397
|
+
const tier = tierPriceId ? SUBSCRIPTION_TIERS[tierPriceId] : void 0;
|
|
40398
|
+
if (tier) {
|
|
40399
|
+
const user = await resolveUser2(invoice.customer, invoice.customer_email ?? void 0);
|
|
40400
|
+
if (user && invoice.id && !await ledgerExistsForStripePI(invoice.id)) {
|
|
40401
|
+
await creditMc(user.id, tier.credits_mc, LedgerOperation.SUBSCRIPTION, `${tier.label} subscription credits`, invoice.id);
|
|
40402
|
+
await setSubscriptionTier(user.id, tier.tier, tier.concurrency, subId ?? user.subscription_id);
|
|
40403
|
+
const credentials = await syncScheduledActionCredentials(user);
|
|
40404
|
+
if (!credentials.ok) console.warn("[stripe] scheduled-action credential sync failed:", credentials.error ?? "unknown error");
|
|
40405
|
+
}
|
|
40119
40406
|
}
|
|
40120
40407
|
}
|
|
40121
40408
|
}
|
|
@@ -43484,8 +43771,8 @@ async function reconcileDiscoveredNangoConnections(identity, discovered) {
|
|
|
43484
43771
|
sql: `
|
|
43485
43772
|
INSERT INTO service_connections (
|
|
43486
43773
|
id, user_id, provider_config_key, provider, transport, upstream_connection_id,
|
|
43487
|
-
label, lifecycle_status, reconnect_required, legacy_owner_identity, created_at, updated_at
|
|
43488
|
-
) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
|
|
43774
|
+
label, lifecycle_status, reconnect_required, actions_enabled, legacy_owner_identity, created_at, updated_at
|
|
43775
|
+
) VALUES (?, ?, ?, ?, 'nango', ?, ?, ?, ?, 1, ?, COALESCE(?, datetime('now')), COALESCE(?, datetime('now')))
|
|
43489
43776
|
ON CONFLICT(user_id, provider_config_key, upstream_connection_id) DO UPDATE SET
|
|
43490
43777
|
provider = excluded.provider,
|
|
43491
43778
|
label = COALESCE(excluded.label, service_connections.label),
|
|
@@ -45601,225 +45888,6 @@ var init_scheduler_integration_auth = __esm({
|
|
|
45601
45888
|
}
|
|
45602
45889
|
});
|
|
45603
45890
|
|
|
45604
|
-
// src/api/connected-account-billing.ts
|
|
45605
|
-
async function ensureConnectedAccountBillingSchema() {
|
|
45606
|
-
await getDb().execute(`CREATE TABLE IF NOT EXISTS connected_account_billing (
|
|
45607
|
-
user_id INTEGER PRIMARY KEY,
|
|
45608
|
-
stripe_subscription_id TEXT,
|
|
45609
|
-
stripe_subscription_item_id TEXT,
|
|
45610
|
-
price_id TEXT,
|
|
45611
|
-
quantity INTEGER NOT NULL DEFAULT 0,
|
|
45612
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
45613
|
-
last_error_code TEXT,
|
|
45614
|
-
synced_at TEXT,
|
|
45615
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
45616
|
-
)`);
|
|
45617
|
-
}
|
|
45618
|
-
async function getConnectedAccountBillingState(userId) {
|
|
45619
|
-
await ensureConnectedAccountBillingSchema();
|
|
45620
|
-
const result = await getDb().execute({
|
|
45621
|
-
sql: "SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",
|
|
45622
|
-
args: [Number(userId)]
|
|
45623
|
-
});
|
|
45624
|
-
return result.rows[0] ? result.rows[0] : null;
|
|
45625
|
-
}
|
|
45626
|
-
async function setConnectedAccountBillingState(input) {
|
|
45627
|
-
await ensureConnectedAccountBillingSchema();
|
|
45628
|
-
await getDb().execute({
|
|
45629
|
-
sql: `INSERT INTO connected_account_billing
|
|
45630
|
-
(user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, synced_at, updated_at)
|
|
45631
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, CASE WHEN ? THEN datetime('now') ELSE NULL END, datetime('now'))
|
|
45632
|
-
ON CONFLICT(user_id) DO UPDATE SET
|
|
45633
|
-
stripe_subscription_id = excluded.stripe_subscription_id,
|
|
45634
|
-
stripe_subscription_item_id = excluded.stripe_subscription_item_id,
|
|
45635
|
-
price_id = excluded.price_id,
|
|
45636
|
-
quantity = excluded.quantity,
|
|
45637
|
-
status = excluded.status,
|
|
45638
|
-
last_error_code = excluded.last_error_code,
|
|
45639
|
-
synced_at = CASE WHEN ? THEN datetime('now') ELSE connected_account_billing.synced_at END,
|
|
45640
|
-
updated_at = datetime('now')`,
|
|
45641
|
-
args: [
|
|
45642
|
-
Number(input.userId),
|
|
45643
|
-
input.stripeSubscriptionId,
|
|
45644
|
-
input.stripeSubscriptionItemId,
|
|
45645
|
-
input.priceId,
|
|
45646
|
-
input.quantity,
|
|
45647
|
-
input.status,
|
|
45648
|
-
input.lastErrorCode,
|
|
45649
|
-
input.synced ? 1 : 0,
|
|
45650
|
-
input.synced ? 1 : 0
|
|
45651
|
-
]
|
|
45652
|
-
});
|
|
45653
|
-
const state = await getConnectedAccountBillingState(input.userId);
|
|
45654
|
-
if (!state) throw new Error("connected account billing state was not persisted");
|
|
45655
|
-
return state;
|
|
45656
|
-
}
|
|
45657
|
-
function connectedAccountPriceId() {
|
|
45658
|
-
const configured = process.env.CONNECTED_ACCOUNT_PRICE_ID?.trim();
|
|
45659
|
-
if (configured) return configured;
|
|
45660
|
-
return DEFAULT_CONNECTED_ACCOUNT_PRICE_ID || null;
|
|
45661
|
-
}
|
|
45662
|
-
function connectedAccountBillingView(actualQuantity, state) {
|
|
45663
|
-
const quantity = Math.max(0, Math.round(actualQuantity));
|
|
45664
|
-
return {
|
|
45665
|
-
provider: "nango",
|
|
45666
|
-
billingMode: "flat_recurring_usd_plus_credits",
|
|
45667
|
-
priceId: connectedAccountPriceId(),
|
|
45668
|
-
unitAmountUsd: CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
|
|
45669
|
-
interval: "month",
|
|
45670
|
-
quantity,
|
|
45671
|
-
projectedMonthlyUsd: quantity * CONNECTED_ACTIVE_CONNECTION_MONTHLY_USD,
|
|
45672
|
-
status: state?.status ?? "pending",
|
|
45673
|
-
lastErrorCode: state?.last_error_code ?? null,
|
|
45674
|
-
syncedAt: state?.synced_at ?? null,
|
|
45675
|
-
usage: CONNECTED_USAGE_RATE_POLICY.usage
|
|
45676
|
-
};
|
|
45677
|
-
}
|
|
45678
|
-
function findBasePlanItem(subscription) {
|
|
45679
|
-
return subscription.items.data.find((item) => !!item.price?.id && item.price.id in SUBSCRIPTION_TIERS);
|
|
45680
|
-
}
|
|
45681
|
-
function findConnectedAccountItem(subscription, priceId = connectedAccountPriceId()) {
|
|
45682
|
-
return priceId ? subscription.items.data.find((item) => item.price?.id === priceId) : void 0;
|
|
45683
|
-
}
|
|
45684
|
-
function liveSubscription(subscription) {
|
|
45685
|
-
return subscription.status === "active" || subscription.status === "trialing";
|
|
45686
|
-
}
|
|
45687
|
-
function safeStripeErrorCode(error) {
|
|
45688
|
-
if (error instanceof ConnectedAccountBillingError) return error.code;
|
|
45689
|
-
return "stripe_connection_billing_failed";
|
|
45690
|
-
}
|
|
45691
|
-
async function persistFailure(user, actualQuantity, error, subscriptionItemId = null) {
|
|
45692
|
-
await setConnectedAccountBillingState({
|
|
45693
|
-
userId: user.id,
|
|
45694
|
-
stripeSubscriptionId: user.subscription_id,
|
|
45695
|
-
stripeSubscriptionItemId: subscriptionItemId,
|
|
45696
|
-
priceId: connectedAccountPriceId(),
|
|
45697
|
-
quantity: actualQuantity,
|
|
45698
|
-
status: error instanceof ConnectedAccountBillingError && error.code !== "stripe_connection_billing_failed" ? "blocked" : "error",
|
|
45699
|
-
lastErrorCode: safeStripeErrorCode(error),
|
|
45700
|
-
synced: false
|
|
45701
|
-
});
|
|
45702
|
-
}
|
|
45703
|
-
async function reconcileConnectedAccountBilling(user, actualQuantity, stripeClient) {
|
|
45704
|
-
if (!Number.isSafeInteger(actualQuantity) || actualQuantity < 0) {
|
|
45705
|
-
throw new Error("actualQuantity must be a non-negative safe integer");
|
|
45706
|
-
}
|
|
45707
|
-
const priceId = connectedAccountPriceId();
|
|
45708
|
-
if (!priceId) {
|
|
45709
|
-
const error = new ConnectedAccountBillingError(
|
|
45710
|
-
"Connected-account billing is not configured.",
|
|
45711
|
-
"connected_account_price_not_configured",
|
|
45712
|
-
503
|
|
45713
|
-
);
|
|
45714
|
-
await persistFailure(user, actualQuantity, error);
|
|
45715
|
-
throw error;
|
|
45716
|
-
}
|
|
45717
|
-
if (!user.subscription_id) {
|
|
45718
|
-
if (actualQuantity === 0) {
|
|
45719
|
-
const state = await setConnectedAccountBillingState({
|
|
45720
|
-
userId: user.id,
|
|
45721
|
-
stripeSubscriptionId: null,
|
|
45722
|
-
stripeSubscriptionItemId: null,
|
|
45723
|
-
priceId,
|
|
45724
|
-
quantity: 0,
|
|
45725
|
-
status: "synced",
|
|
45726
|
-
lastErrorCode: null,
|
|
45727
|
-
synced: true
|
|
45728
|
-
});
|
|
45729
|
-
return connectedAccountBillingView(0, state);
|
|
45730
|
-
}
|
|
45731
|
-
const error = new ConnectedAccountBillingError(
|
|
45732
|
-
"An active paid plan is required before connected accounts can be billed.",
|
|
45733
|
-
"paid_plan_required",
|
|
45734
|
-
403
|
|
45735
|
-
);
|
|
45736
|
-
await persistFailure(user, actualQuantity, error);
|
|
45737
|
-
throw error;
|
|
45738
|
-
}
|
|
45739
|
-
const client2 = stripeClient ?? (() => {
|
|
45740
|
-
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
45741
|
-
if (!secret2) {
|
|
45742
|
-
throw new ConnectedAccountBillingError(
|
|
45743
|
-
"Stripe is not configured.",
|
|
45744
|
-
"connected_account_price_not_configured",
|
|
45745
|
-
503
|
|
45746
|
-
);
|
|
45747
|
-
}
|
|
45748
|
-
return new import_stripe2.default(secret2, { apiVersion: STRIPE_API_VERSION });
|
|
45749
|
-
})();
|
|
45750
|
-
let currentItem;
|
|
45751
|
-
try {
|
|
45752
|
-
const subscription = await client2.subscriptions.retrieve(user.subscription_id);
|
|
45753
|
-
currentItem = findConnectedAccountItem(subscription, priceId);
|
|
45754
|
-
if (!liveSubscription(subscription)) {
|
|
45755
|
-
throw new ConnectedAccountBillingError(
|
|
45756
|
-
"The plan subscription is not active.",
|
|
45757
|
-
"subscription_not_active",
|
|
45758
|
-
402
|
|
45759
|
-
);
|
|
45760
|
-
}
|
|
45761
|
-
if (!findBasePlanItem(subscription)) {
|
|
45762
|
-
throw new ConnectedAccountBillingError(
|
|
45763
|
-
"The base plan item could not be found on this subscription.",
|
|
45764
|
-
"subscription_plan_item_missing",
|
|
45765
|
-
409
|
|
45766
|
-
);
|
|
45767
|
-
}
|
|
45768
|
-
const currentQuantity = Math.max(0, currentItem?.quantity ?? 0);
|
|
45769
|
-
let resultingItem = currentItem;
|
|
45770
|
-
if (actualQuantity !== currentQuantity) {
|
|
45771
|
-
const updated = await client2.subscriptions.update(subscription.id, {
|
|
45772
|
-
items: actualQuantity === 0 ? currentItem ? [{ id: currentItem.id, deleted: true }] : [] : currentItem ? [{ id: currentItem.id, quantity: actualQuantity }] : [{ price: priceId, quantity: actualQuantity }],
|
|
45773
|
-
proration_behavior: "create_prorations"
|
|
45774
|
-
});
|
|
45775
|
-
resultingItem = findConnectedAccountItem(updated, priceId);
|
|
45776
|
-
}
|
|
45777
|
-
const state = await setConnectedAccountBillingState({
|
|
45778
|
-
userId: user.id,
|
|
45779
|
-
stripeSubscriptionId: subscription.id,
|
|
45780
|
-
stripeSubscriptionItemId: resultingItem?.id ?? null,
|
|
45781
|
-
priceId,
|
|
45782
|
-
quantity: actualQuantity,
|
|
45783
|
-
status: "synced",
|
|
45784
|
-
lastErrorCode: null,
|
|
45785
|
-
synced: true
|
|
45786
|
-
});
|
|
45787
|
-
return connectedAccountBillingView(actualQuantity, state);
|
|
45788
|
-
} catch (error) {
|
|
45789
|
-
await persistFailure(user, actualQuantity, error, currentItem?.id ?? null);
|
|
45790
|
-
if (error instanceof ConnectedAccountBillingError) throw error;
|
|
45791
|
-
throw new ConnectedAccountBillingError(
|
|
45792
|
-
"Unable to update connected-account billing in Stripe.",
|
|
45793
|
-
"stripe_connection_billing_failed",
|
|
45794
|
-
503
|
|
45795
|
-
);
|
|
45796
|
-
}
|
|
45797
|
-
}
|
|
45798
|
-
async function currentConnectedAccountBillingView(userId, actualQuantity) {
|
|
45799
|
-
return connectedAccountBillingView(actualQuantity, await getConnectedAccountBillingState(userId));
|
|
45800
|
-
}
|
|
45801
|
-
var import_stripe2, DEFAULT_CONNECTED_ACCOUNT_PRICE_ID, STRIPE_API_VERSION, ConnectedAccountBillingError;
|
|
45802
|
-
var init_connected_account_billing = __esm({
|
|
45803
|
-
"src/api/connected-account-billing.ts"() {
|
|
45804
|
-
"use strict";
|
|
45805
|
-
import_stripe2 = __toESM(require("stripe"), 1);
|
|
45806
|
-
init_db();
|
|
45807
|
-
init_rates();
|
|
45808
|
-
DEFAULT_CONNECTED_ACCOUNT_PRICE_ID = "price_1TtYPAS8aAcsk3TGHOgrDZiQ";
|
|
45809
|
-
STRIPE_API_VERSION = "2026-02-25.clover";
|
|
45810
|
-
ConnectedAccountBillingError = class extends Error {
|
|
45811
|
-
constructor(message, code, status = 409) {
|
|
45812
|
-
super(message);
|
|
45813
|
-
this.code = code;
|
|
45814
|
-
this.status = status;
|
|
45815
|
-
this.name = "ConnectedAccountBillingError";
|
|
45816
|
-
}
|
|
45817
|
-
code;
|
|
45818
|
-
status;
|
|
45819
|
-
};
|
|
45820
|
-
}
|
|
45821
|
-
});
|
|
45822
|
-
|
|
45823
45891
|
// src/api/webhook.ts
|
|
45824
45892
|
async function deliverWebhook(url, payload, retries = 3) {
|
|
45825
45893
|
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
@@ -46193,6 +46261,22 @@ function schedulerIntegrationAuthError(c, error) {
|
|
|
46193
46261
|
}
|
|
46194
46262
|
return scheduleConnectionError(c, error, "The scheduler integration request failed.");
|
|
46195
46263
|
}
|
|
46264
|
+
function toolResultText(result) {
|
|
46265
|
+
if (!result || typeof result !== "object") return null;
|
|
46266
|
+
const content = result.content;
|
|
46267
|
+
if (!Array.isArray(content)) return null;
|
|
46268
|
+
const textItem = content.find((item) => item && typeof item === "object" && item.type === "text");
|
|
46269
|
+
const text2 = textItem?.text;
|
|
46270
|
+
return typeof text2 === "string" ? text2 : null;
|
|
46271
|
+
}
|
|
46272
|
+
function parseEmailAddress(headerValue) {
|
|
46273
|
+
const value = headerValue?.trim();
|
|
46274
|
+
if (!value) return null;
|
|
46275
|
+
const angled = value.match(/^"?([^"<]*?)"?\s*<([^>]+)>$/);
|
|
46276
|
+
if (angled) return { email: angled[2].trim().toLowerCase(), name: angled[1].trim() || null };
|
|
46277
|
+
const bare = value.match(/^([^\s<>]+@[^\s<>]+)$/);
|
|
46278
|
+
return bare ? { email: bare[1].trim().toLowerCase(), name: null } : null;
|
|
46279
|
+
}
|
|
46196
46280
|
async function forwardIntegrationAlias(c, targetPath) {
|
|
46197
46281
|
const url = new URL(c.req.url);
|
|
46198
46282
|
url.pathname = targetPath;
|
|
@@ -46237,6 +46321,22 @@ async function checkHarvestLimits(user, reuseLockId) {
|
|
|
46237
46321
|
}
|
|
46238
46322
|
return null;
|
|
46239
46323
|
}
|
|
46324
|
+
async function chargeTierChangeNow(stripeClient, subscriptionId, customerId) {
|
|
46325
|
+
try {
|
|
46326
|
+
const draft = await stripeClient.invoices.create({
|
|
46327
|
+
customer: customerId,
|
|
46328
|
+
subscription: subscriptionId,
|
|
46329
|
+
auto_advance: true,
|
|
46330
|
+
collection_method: "charge_automatically"
|
|
46331
|
+
});
|
|
46332
|
+
const finalized = await stripeClient.invoices.finalizeInvoice(draft.id);
|
|
46333
|
+
if (finalized.amount_due <= 0 || finalized.status === "paid") return { ok: true, amountDue: finalized.amount_due };
|
|
46334
|
+
const paid = await stripeClient.invoices.pay(finalized.id);
|
|
46335
|
+
return { ok: paid.status === "paid", amountDue: paid.amount_due };
|
|
46336
|
+
} catch (err) {
|
|
46337
|
+
return { ok: false, amountDue: 0, error: err instanceof Error ? err.message : "Unable to charge the plan change immediately." };
|
|
46338
|
+
}
|
|
46339
|
+
}
|
|
46240
46340
|
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;
|
|
46241
46341
|
var init_server = __esm({
|
|
46242
46342
|
"src/api/server.ts"() {
|
|
@@ -47066,7 +47166,7 @@ var init_server = __esm({
|
|
|
47066
47166
|
user.email,
|
|
47067
47167
|
connectionId,
|
|
47068
47168
|
{ channel: body.channel, text: body.text },
|
|
47069
|
-
|
|
47169
|
+
"send-message",
|
|
47070
47170
|
connectedActionIdempotencyKey(c)
|
|
47071
47171
|
);
|
|
47072
47172
|
return c.json({ ok: true, result });
|
|
@@ -47086,7 +47186,7 @@ var init_server = __esm({
|
|
|
47086
47186
|
user.email,
|
|
47087
47187
|
connectionId,
|
|
47088
47188
|
{ to: body.to, subject: body.subject, body: body.body },
|
|
47089
|
-
|
|
47189
|
+
"send-message",
|
|
47090
47190
|
connectedActionIdempotencyKey(c)
|
|
47091
47191
|
);
|
|
47092
47192
|
return c.json({ ok: true, result });
|
|
@@ -47094,6 +47194,60 @@ var init_server = __esm({
|
|
|
47094
47194
|
return scheduleConnectionError(c, err, "Unable to send the email.");
|
|
47095
47195
|
}
|
|
47096
47196
|
});
|
|
47197
|
+
app.post("/schedule-connections/actions/gmail/search-contacts", auth2, requireIntegrationsTier, async (c) => {
|
|
47198
|
+
const user = c.get("user");
|
|
47199
|
+
const body = await c.req.json().catch(() => ({}));
|
|
47200
|
+
const connectionId = providerConfigKeyFrom(body.connectionId);
|
|
47201
|
+
const query = typeof body.query === "string" ? body.query.trim() : "";
|
|
47202
|
+
const maxMessages = Math.min(150, Math.max(1, Number.isFinite(body.maxMessages) ? Number(body.maxMessages) : 50));
|
|
47203
|
+
const pageToken = typeof body.pageToken === "string" ? body.pageToken : void 0;
|
|
47204
|
+
if (!connectionId || !query) return c.json({ ok: false, error: "connectionId and query are required." }, 400);
|
|
47205
|
+
try {
|
|
47206
|
+
const listRaw = await callScheduleConnectionRead(user.email, connectionId, "list-messages", { q: query, maxResults: maxMessages, pageToken });
|
|
47207
|
+
const listText = toolResultText(listRaw);
|
|
47208
|
+
const list = listText ? JSON.parse(listText) : {};
|
|
47209
|
+
const ids = (list.messages ?? []).map((m) => m.id);
|
|
47210
|
+
const messages = [];
|
|
47211
|
+
const contacts = /* @__PURE__ */ new Map();
|
|
47212
|
+
for (const id of ids) {
|
|
47213
|
+
const msgRaw = await callScheduleConnectionRead(user.email, connectionId, "get-message", { id, format: "metadata" });
|
|
47214
|
+
const msgText = toolResultText(msgRaw);
|
|
47215
|
+
if (!msgText) continue;
|
|
47216
|
+
const msg = JSON.parse(msgText);
|
|
47217
|
+
const headers = new Map((msg.payload?.headers ?? []).map((h) => [h.name.toLowerCase(), h.value]));
|
|
47218
|
+
const date = headers.get("date") ?? null;
|
|
47219
|
+
const fromRaw = headers.get("from");
|
|
47220
|
+
const from = parseEmailAddress(fromRaw);
|
|
47221
|
+
const subject = headers.get("subject") ?? null;
|
|
47222
|
+
messages.push({ id, date, from: fromRaw ?? null, to: headers.get("to") ?? null, subject, snippet: msg.snippet ?? null });
|
|
47223
|
+
if (from) {
|
|
47224
|
+
const domain = from.email.split("@")[1] ?? null;
|
|
47225
|
+
const existing = contacts.get(from.email);
|
|
47226
|
+
if (existing) {
|
|
47227
|
+
existing.messageCount += 1;
|
|
47228
|
+
existing.name = existing.name ?? from.name;
|
|
47229
|
+
if (subject && existing.sampleSubjects.length < 5 && !existing.sampleSubjects.includes(subject)) existing.sampleSubjects.push(subject);
|
|
47230
|
+
if (date && (!existing.lastSeen || date > existing.lastSeen)) existing.lastSeen = date;
|
|
47231
|
+
if (date && (!existing.firstSeen || date < existing.firstSeen)) existing.firstSeen = date;
|
|
47232
|
+
} else {
|
|
47233
|
+
contacts.set(from.email, { email: from.email, name: from.name, domain, messageCount: 1, firstSeen: date, lastSeen: date, sampleSubjects: subject ? [subject] : [] });
|
|
47234
|
+
}
|
|
47235
|
+
}
|
|
47236
|
+
}
|
|
47237
|
+
const totalMatches = list.resultSizeEstimate;
|
|
47238
|
+
return c.json({
|
|
47239
|
+
ok: true,
|
|
47240
|
+
totalMatches,
|
|
47241
|
+
messagesFetched: messages.length,
|
|
47242
|
+
truncated: typeof totalMatches === "number" ? totalMatches > messages.length : Boolean(list.nextPageToken),
|
|
47243
|
+
nextPageToken: list.nextPageToken ?? null,
|
|
47244
|
+
messages,
|
|
47245
|
+
contacts: Array.from(contacts.values())
|
|
47246
|
+
});
|
|
47247
|
+
} catch (err) {
|
|
47248
|
+
return scheduleConnectionError(c, err, "Unable to search Gmail contacts.");
|
|
47249
|
+
}
|
|
47250
|
+
});
|
|
47097
47251
|
app.post("/schedule-connections/actions/google-calendar/create-event", auth2, requireIntegrationsTier, async (c) => {
|
|
47098
47252
|
const user = c.get("user");
|
|
47099
47253
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -47112,7 +47266,7 @@ var init_server = __esm({
|
|
|
47112
47266
|
start: { dateTime: body.startDateTime, timeZone },
|
|
47113
47267
|
end: { dateTime: body.endDateTime, timeZone },
|
|
47114
47268
|
attendees
|
|
47115
|
-
},
|
|
47269
|
+
}, "create-event", connectedActionIdempotencyKey(c));
|
|
47116
47270
|
return c.json({ ok: true, result });
|
|
47117
47271
|
} catch (err) {
|
|
47118
47272
|
return scheduleConnectionError(c, err, "Unable to create the calendar event.");
|
|
@@ -47132,7 +47286,7 @@ var init_server = __esm({
|
|
|
47132
47286
|
durationMinutes: typeof body.durationMinutes === "number" ? body.durationMinutes : 30,
|
|
47133
47287
|
timezone: typeof body.timezone === "string" ? body.timezone : void 0,
|
|
47134
47288
|
agenda: body.agenda
|
|
47135
|
-
},
|
|
47289
|
+
}, "create-meeting", connectedActionIdempotencyKey(c));
|
|
47136
47290
|
return c.json({ ok: true, result });
|
|
47137
47291
|
} catch (err) {
|
|
47138
47292
|
return scheduleConnectionError(c, err, "Unable to create the Zoom meeting.");
|
|
@@ -48139,7 +48293,16 @@ var init_server = __esm({
|
|
|
48139
48293
|
items: [{ id: itemId, price: tier.price_id }],
|
|
48140
48294
|
proration_behavior: "create_prorations"
|
|
48141
48295
|
});
|
|
48142
|
-
|
|
48296
|
+
const billed = await chargeTierChangeNow(stripeClient, user.subscription_id, customerId);
|
|
48297
|
+
if (!billed.ok) {
|
|
48298
|
+
return c.json({
|
|
48299
|
+
updated: true,
|
|
48300
|
+
tier: tier.tier,
|
|
48301
|
+
billed: false,
|
|
48302
|
+
error: billed.error ?? "Plan changed, but the prorated charge could not be collected. Update your payment method and it will retry automatically."
|
|
48303
|
+
});
|
|
48304
|
+
}
|
|
48305
|
+
return c.json({ updated: true, tier: tier.tier, billed: true });
|
|
48143
48306
|
}
|
|
48144
48307
|
}
|
|
48145
48308
|
const session = await stripeClient.checkout.sessions.create({
|
|
@@ -48256,7 +48419,16 @@ var init_server = __esm({
|
|
|
48256
48419
|
items: [{ id: itemId, price: tier.price_id }],
|
|
48257
48420
|
proration_behavior: "create_prorations"
|
|
48258
48421
|
});
|
|
48259
|
-
|
|
48422
|
+
const billed = await chargeTierChangeNow(stripeClient, user.subscription_id, customerId);
|
|
48423
|
+
if (!billed.ok) {
|
|
48424
|
+
return c.json({
|
|
48425
|
+
updated: true,
|
|
48426
|
+
tier: tier.tier,
|
|
48427
|
+
billed: false,
|
|
48428
|
+
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.`
|
|
48429
|
+
});
|
|
48430
|
+
}
|
|
48431
|
+
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.` });
|
|
48260
48432
|
}
|
|
48261
48433
|
}
|
|
48262
48434
|
const session = await stripeClient.checkout.sessions.create({
|