mcp-scraper 0.2.7 → 0.2.9
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 +44 -5
- package/dist/bin/api-server.cjs +2417 -648
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/browser-agent-stdio-server.cjs +1 -1
- package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
- package/dist/bin/browser-agent-stdio-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +1276 -0
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -0
- package/dist/bin/mcp-scraper-cli.d.cts +1 -0
- package/dist/bin/mcp-scraper-cli.d.ts +1 -0
- package/dist/bin/mcp-scraper-cli.js +476 -0
- package/dist/bin/mcp-scraper-cli.js.map +1 -0
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs +69 -14
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.js +3 -3
- package/dist/bin/mcp-scraper-install.cjs +5 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +5 -1
- package/dist/bin/mcp-scraper-install.js.map +1 -1
- package/dist/bin/mcp-stdio-server.cjs +69 -14
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/bin/paa-harvest.cjs +42 -6
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +1 -1
- package/dist/{chunk-3TF6UT2P.js → chunk-5E6KJC26.js} +2 -2
- package/dist/{chunk-D4CJBZBY.js → chunk-7GCCOT3M.js} +372 -1
- package/dist/chunk-7GCCOT3M.js.map +1 -0
- package/dist/chunk-BJDCPCIE.js +7 -0
- package/dist/chunk-BJDCPCIE.js.map +1 -0
- package/dist/{chunk-MY3S7EX7.js → chunk-CQTAKXBN.js} +43 -7
- package/dist/chunk-CQTAKXBN.js.map +1 -0
- package/dist/chunk-L6IS63WS.js +869 -0
- package/dist/chunk-L6IS63WS.js.map +1 -0
- package/dist/{chunk-IQOCZGJJ.js → chunk-ROS67BNV.js} +5 -5
- package/dist/{chunk-7SI6XIR3.js → chunk-XAY5U67D.js} +70 -15
- package/dist/chunk-XAY5U67D.js.map +1 -0
- package/dist/{db-YWCNHBLH.js → db-BVHYI57K.js} +38 -2
- package/dist/index.cjs +42 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{server-RIKRBDOI.js → server-BKJ7LULQ.js} +818 -394
- package/dist/server-BKJ7LULQ.js.map +1 -0
- package/dist/{worker-NAKGTIF5.js → worker-TDJQ6TH3.js} +8 -8
- package/docs/mcp-tool-quality-spec.md +3 -1
- package/docs/specs/agent-ready-seo-packet-spec.md +237 -0
- package/docs/specs/cli-agent-wiring-spec.md +203 -0
- package/docs/specs/deferred-work-spec.md +12 -0
- package/docs/specs/local-competitive-audit-spec.md +312 -0
- package/docs/specs/scheduled-workflows-api-spec.md +304 -0
- package/docs/specs/seo-cli-growth-roadmap-spec.md +179 -0
- package/docs/specs/seo-workflow-runner-and-reports-spec.md +241 -0
- package/package.json +3 -2
- package/dist/chunk-7SI6XIR3.js.map +0 -1
- package/dist/chunk-D4CJBZBY.js.map +0 -1
- package/dist/chunk-IZE5UW7Y.js +0 -7
- package/dist/chunk-IZE5UW7Y.js.map +0 -1
- package/dist/chunk-MY3S7EX7.js.map +0 -1
- package/dist/server-RIKRBDOI.js.map +0 -1
- /package/dist/{chunk-3TF6UT2P.js.map → chunk-5E6KJC26.js.map} +0 -0
- /package/dist/{chunk-IQOCZGJJ.js.map → chunk-ROS67BNV.js.map} +0 -0
- /package/dist/{db-YWCNHBLH.js.map → db-BVHYI57K.js.map} +0 -0
- /package/dist/{worker-NAKGTIF5.js.map → worker-TDJQ6TH3.js.map} +0 -0
|
@@ -15,45 +15,14 @@ import {
|
|
|
15
15
|
harvestProblemResponse,
|
|
16
16
|
insufficientBalanceResponse,
|
|
17
17
|
serializeHarvestProblem
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import {
|
|
20
|
-
BrowserDriver,
|
|
21
|
-
MapsPlaceOptionsSchema,
|
|
22
|
-
MapsSearchOptionsSchema,
|
|
23
|
-
MapsSelectors,
|
|
24
|
-
RawMapsAboutAttributeSchema,
|
|
25
|
-
RawMapsHoursRowSchema,
|
|
26
|
-
RawMapsOverviewSchema,
|
|
27
|
-
RawMapsReviewStatsSchema,
|
|
28
|
-
browserServiceApiKey,
|
|
29
|
-
browserServiceProxyId,
|
|
30
|
-
buildYouTubeChannelVideosUrl,
|
|
31
|
-
deleteKernelProxyId,
|
|
32
|
-
harvest,
|
|
33
|
-
resolveKernelProxyId
|
|
34
|
-
} from "./chunk-MY3S7EX7.js";
|
|
35
|
-
import {
|
|
36
|
-
CaptureSerpPageSnapshotsInputSchema,
|
|
37
|
-
CaptureSerpSnapshotInputSchema,
|
|
38
|
-
HttpMcpToolExecutor,
|
|
39
|
-
buildPaaExtractorMcpServer,
|
|
40
|
-
configureReportSaving,
|
|
41
|
-
harvestTimeoutBudget,
|
|
42
|
-
liveWebToolAnnotations,
|
|
43
|
-
outputBaseDir
|
|
44
|
-
} from "./chunk-7SI6XIR3.js";
|
|
45
|
-
import {
|
|
46
|
-
CaptchaError,
|
|
47
|
-
RECAPTCHA_INSTRUCTIONS,
|
|
48
|
-
sanitizeVendorName
|
|
49
|
-
} from "./chunk-M2S27J6Z.js";
|
|
50
|
-
import "./chunk-IZE5UW7Y.js";
|
|
18
|
+
} from "./chunk-ROS67BNV.js";
|
|
51
19
|
import {
|
|
52
20
|
SiteAuditJobRowSchema,
|
|
53
21
|
cancelJob,
|
|
54
22
|
checkRateLimit,
|
|
55
23
|
claimMonthlyFreeRefresh,
|
|
56
24
|
completeJob,
|
|
25
|
+
completeWorkflowRunRecord,
|
|
57
26
|
consumePasswordResetToken,
|
|
58
27
|
countActiveJobsForUser,
|
|
59
28
|
countActiveUsers,
|
|
@@ -61,10 +30,14 @@ import {
|
|
|
61
30
|
createPasswordResetToken,
|
|
62
31
|
createRunningJob,
|
|
63
32
|
createUser,
|
|
33
|
+
createWorkflowRun,
|
|
34
|
+
createWorkflowSchedule,
|
|
64
35
|
creditMc,
|
|
65
36
|
deactivateUser,
|
|
66
37
|
debitMc,
|
|
38
|
+
deleteWorkflowSchedule,
|
|
67
39
|
failJob,
|
|
40
|
+
failWorkflowRunRecord,
|
|
68
41
|
getDb,
|
|
69
42
|
getJob,
|
|
70
43
|
getLedger,
|
|
@@ -73,15 +46,27 @@ import {
|
|
|
73
46
|
getUserById,
|
|
74
47
|
getUserByStripeCustomerId,
|
|
75
48
|
getUserStats,
|
|
49
|
+
getWorkflowArtifact,
|
|
50
|
+
getWorkflowRun,
|
|
51
|
+
getWorkflowSchedule,
|
|
76
52
|
ledgerExistsForOperation,
|
|
77
53
|
ledgerExistsForStripePI,
|
|
54
|
+
listDueWorkflowSchedules,
|
|
78
55
|
listHarvestAttempts,
|
|
79
56
|
listJobs,
|
|
80
57
|
listRequestEvents,
|
|
81
58
|
listUsers,
|
|
59
|
+
listWorkflowArtifacts,
|
|
60
|
+
listWorkflowRuns,
|
|
61
|
+
listWorkflowSchedules,
|
|
82
62
|
logRequestEvent,
|
|
63
|
+
markWorkflowRunRunning,
|
|
64
|
+
markWorkflowScheduleRan,
|
|
65
|
+
patchWorkflowSchedule,
|
|
83
66
|
reconcileBalanceMc,
|
|
84
67
|
recordStripeEvent,
|
|
68
|
+
recordWorkflowWebhookDelivery,
|
|
69
|
+
replaceWorkflowArtifacts,
|
|
85
70
|
revokeApiKey,
|
|
86
71
|
rotateApiKey,
|
|
87
72
|
setConcurrencySubId,
|
|
@@ -89,7 +74,47 @@ import {
|
|
|
89
74
|
setPassword,
|
|
90
75
|
setStripeCustomerId,
|
|
91
76
|
verifyPassword
|
|
92
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-7GCCOT3M.js";
|
|
78
|
+
import {
|
|
79
|
+
BrowserDriver,
|
|
80
|
+
MapsPlaceOptionsSchema,
|
|
81
|
+
MapsSearchOptionsSchema,
|
|
82
|
+
MapsSelectors,
|
|
83
|
+
RawMapsAboutAttributeSchema,
|
|
84
|
+
RawMapsHoursRowSchema,
|
|
85
|
+
RawMapsOverviewSchema,
|
|
86
|
+
RawMapsReviewStatsSchema,
|
|
87
|
+
browserServiceApiKey,
|
|
88
|
+
browserServiceProxyId,
|
|
89
|
+
buildYouTubeChannelVideosUrl,
|
|
90
|
+
deleteKernelProxyId,
|
|
91
|
+
harvest,
|
|
92
|
+
resolveKernelProxyId
|
|
93
|
+
} from "./chunk-CQTAKXBN.js";
|
|
94
|
+
import {
|
|
95
|
+
CaptureSerpPageSnapshotsInputSchema,
|
|
96
|
+
CaptureSerpSnapshotInputSchema,
|
|
97
|
+
HttpMcpToolExecutor,
|
|
98
|
+
buildPaaExtractorMcpServer,
|
|
99
|
+
configureReportSaving,
|
|
100
|
+
harvestTimeoutBudget,
|
|
101
|
+
liveWebToolAnnotations,
|
|
102
|
+
outputBaseDir
|
|
103
|
+
} from "./chunk-XAY5U67D.js";
|
|
104
|
+
import {
|
|
105
|
+
CaptchaError,
|
|
106
|
+
RECAPTCHA_INSTRUCTIONS,
|
|
107
|
+
sanitizeVendorName
|
|
108
|
+
} from "./chunk-M2S27J6Z.js";
|
|
109
|
+
import {
|
|
110
|
+
csvRecords,
|
|
111
|
+
listWorkflowDefinitions,
|
|
112
|
+
rowsToCsv,
|
|
113
|
+
runWorkflow,
|
|
114
|
+
workflowDefinition,
|
|
115
|
+
workflowOutputBaseDir
|
|
116
|
+
} from "./chunk-L6IS63WS.js";
|
|
117
|
+
import "./chunk-BJDCPCIE.js";
|
|
93
118
|
|
|
94
119
|
// src/api/outbound-sanitize.ts
|
|
95
120
|
var KEY_RENAMES = {
|
|
@@ -4848,7 +4873,7 @@ async function extractSite(opts) {
|
|
|
4848
4873
|
}
|
|
4849
4874
|
|
|
4850
4875
|
// src/api/server.ts
|
|
4851
|
-
import { Hono as
|
|
4876
|
+
import { Hono as Hono12 } from "hono";
|
|
4852
4877
|
import { serve as serveInngest } from "inngest/hono";
|
|
4853
4878
|
|
|
4854
4879
|
// src/inngest/client.ts
|
|
@@ -9893,24 +9918,205 @@ var MapsSearchExtractor = class {
|
|
|
9893
9918
|
}
|
|
9894
9919
|
};
|
|
9895
9920
|
|
|
9896
|
-
// src/
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
|
|
9921
|
+
// src/maps-search-rotation.ts
|
|
9922
|
+
var MAPS_SEARCH_MAX_ATTEMPTS = 3;
|
|
9923
|
+
var MAPS_SEARCH_LOCATION_PROXY_MAX_ATTEMPTS = 5;
|
|
9924
|
+
var MapsSearchRotationError = class extends Error {
|
|
9925
|
+
constructor(message, attempts, retryable, errorCode = "maps_search_failed") {
|
|
9926
|
+
super(message);
|
|
9927
|
+
this.attempts = attempts;
|
|
9928
|
+
this.retryable = retryable;
|
|
9929
|
+
this.errorCode = errorCode;
|
|
9930
|
+
}
|
|
9931
|
+
attempts;
|
|
9932
|
+
retryable;
|
|
9933
|
+
errorCode;
|
|
9934
|
+
name = "MapsSearchRotationError";
|
|
9935
|
+
};
|
|
9936
|
+
function mapsSearchMaxAttemptsForProxyMode(proxyMode) {
|
|
9937
|
+
return proxyMode === "location" ? MAPS_SEARCH_LOCATION_PROXY_MAX_ATTEMPTS : MAPS_SEARCH_MAX_ATTEMPTS;
|
|
9938
|
+
}
|
|
9939
|
+
function mapsSearchErrorMessage(err) {
|
|
9940
|
+
return sanitizeVendorName(err instanceof Error ? err.message : String(err));
|
|
9941
|
+
}
|
|
9942
|
+
function looksLikeProxyTunnelFailure(message) {
|
|
9943
|
+
return /ERR_TUNNEL_CONNECTION_FAILED|ERR_PROXY_CONNECTION_FAILED|ERR_SOCKS_CONNECTION_FAILED|tunnel connection failed|proxy connection failed|transport error: proxy/i.test(message);
|
|
9944
|
+
}
|
|
9945
|
+
function looksLikeProxyUnavailable(message) {
|
|
9946
|
+
return /proxy unavailable|proxy_unavailable|connection_test_failed|did not return a proxy id|configured fallback/i.test(message);
|
|
9947
|
+
}
|
|
9948
|
+
function looksLikeBrowserClosed(message) {
|
|
9949
|
+
return /Target page, context or browser has been closed|browser has been closed|context has been closed|page has been closed|browser not found|CDP session closed|Connection closed|WebSocket.*closed|Protocol error.*closed|Browser closed|disconnected/i.test(message);
|
|
9950
|
+
}
|
|
9951
|
+
function mapsSearchFailureOutcome(err) {
|
|
9952
|
+
if (err instanceof CaptchaError) return "captcha";
|
|
9953
|
+
const message = mapsSearchErrorMessage(err);
|
|
9954
|
+
if (/timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message)) return "timeout";
|
|
9955
|
+
if (looksLikeProxyTunnelFailure(message)) return "proxy_tunnel_failed";
|
|
9956
|
+
if (looksLikeProxyUnavailable(message)) return "proxy_unavailable";
|
|
9957
|
+
if (looksLikeBrowserClosed(message)) return "browser_closed";
|
|
9958
|
+
return "unknown_error";
|
|
9959
|
+
}
|
|
9960
|
+
function retryableMapsSearchError(err, proxyMode) {
|
|
9961
|
+
if (err instanceof CaptchaError) return true;
|
|
9962
|
+
const message = mapsSearchErrorMessage(err);
|
|
9963
|
+
if (/timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message)) return true;
|
|
9964
|
+
if (looksLikeBrowserClosed(message)) return true;
|
|
9965
|
+
return proxyMode === "location" && (looksLikeProxyTunnelFailure(message) || looksLikeProxyUnavailable(message));
|
|
9966
|
+
}
|
|
9967
|
+
function suffix(value) {
|
|
9968
|
+
return value ? value.slice(-8) : null;
|
|
9969
|
+
}
|
|
9970
|
+
function attemptFromDebug(input) {
|
|
9971
|
+
const network = input.debug.networkLocation;
|
|
9972
|
+
const browserSessionId = input.debug.kernel?.sessionId ?? null;
|
|
9973
|
+
const target = input.resolution?.target ?? null;
|
|
9974
|
+
return {
|
|
9975
|
+
attemptNumber: input.attemptIndex + 1,
|
|
9976
|
+
maxAttempts: input.maxAttempts,
|
|
9977
|
+
status: input.status,
|
|
9978
|
+
outcome: input.outcome,
|
|
9979
|
+
willRetry: input.willRetry,
|
|
9980
|
+
durationMs: input.durationMs,
|
|
9981
|
+
resultCount: input.resultCount,
|
|
9982
|
+
error: input.error,
|
|
9983
|
+
proxyMode: input.proxyMode,
|
|
9984
|
+
proxyResolutionSource: input.resolution?.source ?? null,
|
|
9985
|
+
proxyIdSuffix: input.resolution?.proxyIdSuffix ?? null,
|
|
9986
|
+
proxyTargetLevel: target?.level ?? null,
|
|
9987
|
+
proxyTargetLocation: target?.canonicalLocation ?? null,
|
|
9988
|
+
proxyTargetZip: target?.zip ?? null,
|
|
9989
|
+
browserSessionIdSuffix: suffix(browserSessionId),
|
|
9990
|
+
observedIp: network?.ip ?? null,
|
|
9991
|
+
observedCity: network?.city ?? null,
|
|
9992
|
+
observedRegion: network?.region ?? null
|
|
9993
|
+
};
|
|
9904
9994
|
}
|
|
9905
|
-
async function cleanupDisposableProxy(kernelApiKey, proxyId) {
|
|
9995
|
+
async function cleanupDisposableProxy(kernelApiKey, proxyId, eventName) {
|
|
9906
9996
|
if (!kernelApiKey || !proxyId) return;
|
|
9907
|
-
|
|
9997
|
+
try {
|
|
9998
|
+
await deleteKernelProxyId(kernelApiKey, proxyId);
|
|
9999
|
+
} catch (err) {
|
|
9908
10000
|
console.warn(JSON.stringify({
|
|
9909
|
-
event:
|
|
9910
|
-
proxy_id_suffix: proxyId
|
|
9911
|
-
message:
|
|
10001
|
+
event: eventName,
|
|
10002
|
+
proxy_id_suffix: suffix(proxyId),
|
|
10003
|
+
message: mapsSearchErrorMessage(err)
|
|
9912
10004
|
}));
|
|
9913
|
-
}
|
|
10005
|
+
}
|
|
10006
|
+
}
|
|
10007
|
+
async function runMapsSearchWithRotation(options) {
|
|
10008
|
+
const attempts = [];
|
|
10009
|
+
const maxAttempts = mapsSearchMaxAttemptsForProxyMode(options.proxyMode);
|
|
10010
|
+
const started = Date.now();
|
|
10011
|
+
let lastError = null;
|
|
10012
|
+
for (let attemptIndex = 0; attemptIndex < maxAttempts; attemptIndex += 1) {
|
|
10013
|
+
const attemptStarted = Date.now();
|
|
10014
|
+
const driver = new BrowserDriver();
|
|
10015
|
+
const extractor = new MapsSearchExtractor(driver);
|
|
10016
|
+
const proxyZip = options.proxyZipForAttempt?.(attemptIndex) ?? options.proxyZip;
|
|
10017
|
+
let resolution = null;
|
|
10018
|
+
let disposableProxyId;
|
|
10019
|
+
try {
|
|
10020
|
+
resolution = await resolveKernelProxyId({
|
|
10021
|
+
kernelApiKey: options.kernelApiKey,
|
|
10022
|
+
proxyMode: options.proxyMode,
|
|
10023
|
+
configuredKernelProxyId: options.kernelProxyId ?? options.configuredKernelProxyId,
|
|
10024
|
+
location: options.location,
|
|
10025
|
+
proxyZip,
|
|
10026
|
+
gl: options.gl,
|
|
10027
|
+
attemptIndex,
|
|
10028
|
+
fresh: options.proxyMode === "location"
|
|
10029
|
+
});
|
|
10030
|
+
disposableProxyId = resolution.disposableProxyId;
|
|
10031
|
+
const result = await extractor.extract({
|
|
10032
|
+
...options,
|
|
10033
|
+
proxyZip,
|
|
10034
|
+
kernelProxyId: options.proxyMode === "none" ? void 0 : resolution.kernelProxyId,
|
|
10035
|
+
kernelProxyResolution: resolution.resolution
|
|
10036
|
+
});
|
|
10037
|
+
const attempt = attemptFromDebug({
|
|
10038
|
+
attemptIndex,
|
|
10039
|
+
maxAttempts,
|
|
10040
|
+
status: "ok",
|
|
10041
|
+
outcome: "success",
|
|
10042
|
+
willRetry: false,
|
|
10043
|
+
durationMs: Date.now() - attemptStarted,
|
|
10044
|
+
resultCount: result.resultCount,
|
|
10045
|
+
error: null,
|
|
10046
|
+
proxyMode: options.proxyMode,
|
|
10047
|
+
resolution: resolution.resolution,
|
|
10048
|
+
debug: driver.getDebugSnapshot()
|
|
10049
|
+
});
|
|
10050
|
+
attempts.push(attempt);
|
|
10051
|
+
return {
|
|
10052
|
+
...result,
|
|
10053
|
+
attempts: attempts.slice(),
|
|
10054
|
+
durationMs: Date.now() - started
|
|
10055
|
+
};
|
|
10056
|
+
} catch (err) {
|
|
10057
|
+
lastError = err;
|
|
10058
|
+
const retryable = retryableMapsSearchError(err, options.proxyMode);
|
|
10059
|
+
const willRetry = attemptIndex < maxAttempts - 1 && retryable;
|
|
10060
|
+
const attempt = attemptFromDebug({
|
|
10061
|
+
attemptIndex,
|
|
10062
|
+
maxAttempts,
|
|
10063
|
+
status: "failed",
|
|
10064
|
+
outcome: mapsSearchFailureOutcome(err),
|
|
10065
|
+
willRetry,
|
|
10066
|
+
durationMs: Date.now() - attemptStarted,
|
|
10067
|
+
resultCount: 0,
|
|
10068
|
+
error: mapsSearchErrorMessage(err),
|
|
10069
|
+
proxyMode: options.proxyMode,
|
|
10070
|
+
resolution: resolution?.resolution ?? null,
|
|
10071
|
+
debug: driver.getDebugSnapshot()
|
|
10072
|
+
});
|
|
10073
|
+
attempts.push(attempt);
|
|
10074
|
+
console.warn(JSON.stringify({
|
|
10075
|
+
event: options.failureEventName ?? "maps_search_attempt_failed",
|
|
10076
|
+
...options.logContext,
|
|
10077
|
+
attempt_number: attempt.attemptNumber,
|
|
10078
|
+
max_attempts: attempt.maxAttempts,
|
|
10079
|
+
outcome: attempt.outcome,
|
|
10080
|
+
will_retry: attempt.willRetry,
|
|
10081
|
+
proxy_resolution_source: attempt.proxyResolutionSource,
|
|
10082
|
+
proxy_id_suffix: attempt.proxyIdSuffix,
|
|
10083
|
+
browser_session_id_suffix: attempt.browserSessionIdSuffix,
|
|
10084
|
+
observed_city: attempt.observedCity,
|
|
10085
|
+
observed_region: attempt.observedRegion,
|
|
10086
|
+
message: attempt.error
|
|
10087
|
+
}));
|
|
10088
|
+
if (!willRetry) {
|
|
10089
|
+
throw new MapsSearchRotationError(
|
|
10090
|
+
mapsSearchErrorMessage(err),
|
|
10091
|
+
attempts,
|
|
10092
|
+
retryable,
|
|
10093
|
+
err instanceof CaptchaError ? "captcha_or_blocked" : "maps_search_failed"
|
|
10094
|
+
);
|
|
10095
|
+
}
|
|
10096
|
+
} finally {
|
|
10097
|
+
await cleanupDisposableProxy(options.kernelApiKey, disposableProxyId, "maps_search_proxy_delete_failed");
|
|
10098
|
+
await driver.close();
|
|
10099
|
+
}
|
|
10100
|
+
}
|
|
10101
|
+
throw new MapsSearchRotationError(
|
|
10102
|
+
lastError ? mapsSearchErrorMessage(lastError) : "Maps search failed",
|
|
10103
|
+
attempts,
|
|
10104
|
+
true
|
|
10105
|
+
);
|
|
10106
|
+
}
|
|
10107
|
+
|
|
10108
|
+
// src/api/maps-routes.ts
|
|
10109
|
+
function mapsErrorResponse(c, err, errorCode) {
|
|
10110
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10111
|
+
const rotationError = err instanceof MapsSearchRotationError ? err : null;
|
|
10112
|
+
const blocked = msg.includes("CAPTCHA") || msg.includes("blocked");
|
|
10113
|
+
const retryable = rotationError?.retryable ?? blocked;
|
|
10114
|
+
return c.json({
|
|
10115
|
+
error: sanitizeVendorName(msg),
|
|
10116
|
+
error_code: rotationError?.errorCode ?? (blocked ? "captcha_or_blocked" : errorCode),
|
|
10117
|
+
retryable,
|
|
10118
|
+
attempts: rotationError?.attempts ?? void 0
|
|
10119
|
+
}, retryable ? 503 : 500);
|
|
9914
10120
|
}
|
|
9915
10121
|
var mapsApp = new Hono5();
|
|
9916
10122
|
mapsApp.post("/search", createApiKeyAuth(), async (c) => {
|
|
@@ -9930,24 +10136,10 @@ mapsApp.post("/search", createApiKeyAuth(), async (c) => {
|
|
|
9930
10136
|
[parsed.data.query, parsed.data.location].filter(Boolean).join(" ")
|
|
9931
10137
|
);
|
|
9932
10138
|
if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.maps_search), 402);
|
|
9933
|
-
const driver = new BrowserDriver();
|
|
9934
|
-
const extractor = new MapsSearchExtractor(driver);
|
|
9935
|
-
let disposableProxyId;
|
|
9936
10139
|
try {
|
|
9937
|
-
const
|
|
9938
|
-
kernelApiKey: parsed.data.kernelApiKey,
|
|
9939
|
-
proxyMode: parsed.data.proxyMode,
|
|
9940
|
-
configuredKernelProxyId: browserServiceProxyId(),
|
|
9941
|
-
location: parsed.data.location,
|
|
9942
|
-
proxyZip: parsed.data.proxyZip,
|
|
9943
|
-
gl: parsed.data.gl,
|
|
9944
|
-
fresh: parsed.data.proxyMode === "location"
|
|
9945
|
-
});
|
|
9946
|
-
disposableProxyId = resolution.disposableProxyId;
|
|
9947
|
-
const result = await extractor.extract({
|
|
10140
|
+
const result = await runMapsSearchWithRotation({
|
|
9948
10141
|
...parsed.data,
|
|
9949
|
-
|
|
9950
|
-
kernelProxyResolution: resolution.resolution
|
|
10142
|
+
configuredKernelProxyId: browserServiceProxyId()
|
|
9951
10143
|
});
|
|
9952
10144
|
await logRequestEvent({
|
|
9953
10145
|
userId: user.id,
|
|
@@ -9968,12 +10160,10 @@ mapsApp.post("/search", createApiKeyAuth(), async (c) => {
|
|
|
9968
10160
|
status: "failed",
|
|
9969
10161
|
query: [parsed.data.query, parsed.data.location].filter(Boolean).join(" "),
|
|
9970
10162
|
location: parsed.data.location,
|
|
9971
|
-
error: msg
|
|
10163
|
+
error: msg,
|
|
10164
|
+
result: err instanceof MapsSearchRotationError ? { attempts: err.attempts } : void 0
|
|
9972
10165
|
});
|
|
9973
|
-
return mapsErrorResponse(c,
|
|
9974
|
-
} finally {
|
|
9975
|
-
await cleanupDisposableProxy(parsed.data.kernelApiKey, disposableProxyId);
|
|
9976
|
-
await driver.close();
|
|
10166
|
+
return mapsErrorResponse(c, err, "maps_search_failed");
|
|
9977
10167
|
}
|
|
9978
10168
|
});
|
|
9979
10169
|
mapsApp.post("/place", createApiKeyAuth(), async (c) => {
|
|
@@ -10056,69 +10246,6 @@ import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
|
10056
10246
|
import { join as join4 } from "path";
|
|
10057
10247
|
import { z as z15 } from "zod";
|
|
10058
10248
|
|
|
10059
|
-
// src/directory/csv.ts
|
|
10060
|
-
function parseCsv(text) {
|
|
10061
|
-
const rows = [];
|
|
10062
|
-
let row = [];
|
|
10063
|
-
let field = "";
|
|
10064
|
-
let quoted = false;
|
|
10065
|
-
for (let i = 0; i < text.length; i += 1) {
|
|
10066
|
-
const ch = text[i];
|
|
10067
|
-
const next = text[i + 1];
|
|
10068
|
-
if (quoted) {
|
|
10069
|
-
if (ch === '"' && next === '"') {
|
|
10070
|
-
field += '"';
|
|
10071
|
-
i += 1;
|
|
10072
|
-
} else if (ch === '"') {
|
|
10073
|
-
quoted = false;
|
|
10074
|
-
} else {
|
|
10075
|
-
field += ch;
|
|
10076
|
-
}
|
|
10077
|
-
continue;
|
|
10078
|
-
}
|
|
10079
|
-
if (ch === '"') {
|
|
10080
|
-
quoted = true;
|
|
10081
|
-
} else if (ch === ",") {
|
|
10082
|
-
row.push(field);
|
|
10083
|
-
field = "";
|
|
10084
|
-
} else if (ch === "\n") {
|
|
10085
|
-
row.push(field);
|
|
10086
|
-
rows.push(row);
|
|
10087
|
-
row = [];
|
|
10088
|
-
field = "";
|
|
10089
|
-
} else if (ch !== "\r") {
|
|
10090
|
-
field += ch;
|
|
10091
|
-
}
|
|
10092
|
-
}
|
|
10093
|
-
if (field.length > 0 || row.length > 0) {
|
|
10094
|
-
row.push(field);
|
|
10095
|
-
rows.push(row);
|
|
10096
|
-
}
|
|
10097
|
-
return rows;
|
|
10098
|
-
}
|
|
10099
|
-
function csvRecords(text) {
|
|
10100
|
-
const rows = parseCsv(text).filter((row) => row.some((cell) => cell.trim() !== ""));
|
|
10101
|
-
const header = rows[0]?.map((cell) => cell.trim()) ?? [];
|
|
10102
|
-
return rows.slice(1).map((row) => {
|
|
10103
|
-
const record = {};
|
|
10104
|
-
for (let i = 0; i < header.length; i += 1) {
|
|
10105
|
-
record[header[i]] = row[i] ?? "";
|
|
10106
|
-
}
|
|
10107
|
-
return record;
|
|
10108
|
-
});
|
|
10109
|
-
}
|
|
10110
|
-
function csvCell(value) {
|
|
10111
|
-
if (value === null || value === void 0) return "";
|
|
10112
|
-
const text = String(value);
|
|
10113
|
-
return /[",\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
|
|
10114
|
-
}
|
|
10115
|
-
function rowsToCsv(headers, rows) {
|
|
10116
|
-
return [
|
|
10117
|
-
headers.join(","),
|
|
10118
|
-
...rows.map((row) => headers.map((header) => csvCell(row[header])).join(","))
|
|
10119
|
-
].join("\n") + "\n";
|
|
10120
|
-
}
|
|
10121
|
-
|
|
10122
10249
|
// src/directory/location-db.ts
|
|
10123
10250
|
import { access, readFile } from "fs/promises";
|
|
10124
10251
|
var POPULATION_YEARS = [2020, 2021, 2022, 2023, 2024, 2025];
|
|
@@ -10278,8 +10405,6 @@ async function resolveDirectoryMarkets(options) {
|
|
|
10278
10405
|
}
|
|
10279
10406
|
|
|
10280
10407
|
// src/directory/directory-workflow.ts
|
|
10281
|
-
var DIRECTORY_MAX_ATTEMPTS = 3;
|
|
10282
|
-
var DIRECTORY_LOCATION_PROXY_MAX_ATTEMPTS = 5;
|
|
10283
10408
|
var DirectoryWorkflowOptionsSchema = z15.object({
|
|
10284
10409
|
query: z15.string().min(1),
|
|
10285
10410
|
state: z15.string().min(2).default("TN"),
|
|
@@ -10299,36 +10424,9 @@ var DirectoryWorkflowOptionsSchema = z15.object({
|
|
|
10299
10424
|
headless: z15.boolean().default(true),
|
|
10300
10425
|
kernelApiKey: z15.string().optional()
|
|
10301
10426
|
});
|
|
10302
|
-
async function cleanupDisposableProxy2(kernelApiKey, proxyId) {
|
|
10303
|
-
if (!kernelApiKey || !proxyId) return;
|
|
10304
|
-
try {
|
|
10305
|
-
await deleteKernelProxyId(kernelApiKey, proxyId);
|
|
10306
|
-
} catch (err) {
|
|
10307
|
-
console.warn(JSON.stringify({
|
|
10308
|
-
event: "directory_workflow_proxy_delete_failed",
|
|
10309
|
-
proxy_id_suffix: proxyId.slice(-6),
|
|
10310
|
-
message: err instanceof Error ? err.message : String(err)
|
|
10311
|
-
}));
|
|
10312
|
-
}
|
|
10313
|
-
}
|
|
10314
|
-
function maxAttemptsForProxyMode(proxyMode) {
|
|
10315
|
-
return proxyMode === "location" ? DIRECTORY_LOCATION_PROXY_MAX_ATTEMPTS : DIRECTORY_MAX_ATTEMPTS;
|
|
10316
|
-
}
|
|
10317
10427
|
function errorMessage(err) {
|
|
10318
10428
|
return err instanceof Error ? err.message : String(err);
|
|
10319
10429
|
}
|
|
10320
|
-
function looksLikeProxyTunnelFailure(message) {
|
|
10321
|
-
return /ERR_TUNNEL_CONNECTION_FAILED|ERR_PROXY_CONNECTION_FAILED|ERR_SOCKS_CONNECTION_FAILED|tunnel connection failed|proxy connection failed|transport error: proxy/i.test(message);
|
|
10322
|
-
}
|
|
10323
|
-
function looksLikeProxyUnavailable(message) {
|
|
10324
|
-
return /proxy unavailable|proxy_unavailable|connection_test_failed|did not return a proxy id|configured fallback/i.test(message);
|
|
10325
|
-
}
|
|
10326
|
-
function retryableCitySearchError(err, proxyMode) {
|
|
10327
|
-
if (err instanceof CaptchaError) return true;
|
|
10328
|
-
const message = errorMessage(err);
|
|
10329
|
-
if (/timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message)) return true;
|
|
10330
|
-
return proxyMode === "location" && (looksLikeProxyTunnelFailure(message) || looksLikeProxyUnavailable(message));
|
|
10331
|
-
}
|
|
10332
10430
|
function proxyZipForAttempt(options, market, attemptIndex) {
|
|
10333
10431
|
if (options.proxyZip) return options.proxyZip;
|
|
10334
10432
|
if (!market.zips.length) return void 0;
|
|
@@ -10347,25 +10445,10 @@ async function mapLimit(items, limit, fn) {
|
|
|
10347
10445
|
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
|
|
10348
10446
|
return out;
|
|
10349
10447
|
}
|
|
10350
|
-
async function
|
|
10351
|
-
const
|
|
10352
|
-
const extractor = new MapsSearchExtractor(driver);
|
|
10353
|
-
const start = Date.now();
|
|
10354
|
-
let disposableProxyId;
|
|
10448
|
+
async function searchCity(options, market) {
|
|
10449
|
+
const started = Date.now();
|
|
10355
10450
|
try {
|
|
10356
|
-
const
|
|
10357
|
-
const resolution = await resolveKernelProxyId({
|
|
10358
|
-
kernelApiKey: options.kernelApiKey,
|
|
10359
|
-
proxyMode: options.proxyMode,
|
|
10360
|
-
configuredKernelProxyId: browserServiceProxyId(),
|
|
10361
|
-
location: market.location,
|
|
10362
|
-
proxyZip,
|
|
10363
|
-
gl: options.gl,
|
|
10364
|
-
attemptIndex,
|
|
10365
|
-
fresh: options.proxyMode === "location"
|
|
10366
|
-
});
|
|
10367
|
-
disposableProxyId = resolution.disposableProxyId;
|
|
10368
|
-
const result = await extractor.extract({
|
|
10451
|
+
const result = await runMapsSearchWithRotation({
|
|
10369
10452
|
query: options.query,
|
|
10370
10453
|
location: market.location,
|
|
10371
10454
|
gl: options.gl,
|
|
@@ -10373,11 +10456,16 @@ async function searchCityAttempt(options, market, attemptIndex) {
|
|
|
10373
10456
|
maxResults: options.maxResultsPerCity,
|
|
10374
10457
|
headless: options.headless,
|
|
10375
10458
|
kernelApiKey: options.kernelApiKey,
|
|
10376
|
-
kernelProxyId: options.proxyMode === "none" ? void 0 : resolution.kernelProxyId,
|
|
10377
|
-
kernelProxyResolution: resolution.resolution,
|
|
10378
10459
|
proxyMode: options.proxyMode,
|
|
10379
|
-
proxyZip,
|
|
10380
|
-
debug: options.debug
|
|
10460
|
+
proxyZip: options.proxyZip,
|
|
10461
|
+
debug: options.debug,
|
|
10462
|
+
configuredKernelProxyId: browserServiceProxyId(),
|
|
10463
|
+
proxyZipForAttempt: (attemptIndex) => proxyZipForAttempt(options, market, attemptIndex),
|
|
10464
|
+
failureEventName: "directory_workflow_city_attempt_failed",
|
|
10465
|
+
logContext: {
|
|
10466
|
+
city: market.city,
|
|
10467
|
+
state: market.state
|
|
10468
|
+
}
|
|
10381
10469
|
});
|
|
10382
10470
|
return {
|
|
10383
10471
|
city: market.city,
|
|
@@ -10393,50 +10481,29 @@ async function searchCityAttempt(options, market, attemptIndex) {
|
|
|
10393
10481
|
error: null,
|
|
10394
10482
|
resultCount: result.resultCount,
|
|
10395
10483
|
durationMs: result.durationMs,
|
|
10484
|
+
attempts: result.attempts ?? [],
|
|
10396
10485
|
results: result.results
|
|
10397
10486
|
};
|
|
10398
|
-
}
|
|
10399
|
-
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
max_attempts: maxAttempts,
|
|
10418
|
-
will_retry: willRetry,
|
|
10419
|
-
message: errorMessage(err)
|
|
10420
|
-
}));
|
|
10421
|
-
if (!willRetry) break;
|
|
10422
|
-
}
|
|
10487
|
+
} catch (err) {
|
|
10488
|
+
const attempts = err instanceof MapsSearchRotationError ? err.attempts : [];
|
|
10489
|
+
return {
|
|
10490
|
+
city: market.city,
|
|
10491
|
+
state: market.state,
|
|
10492
|
+
location: market.location,
|
|
10493
|
+
cityKey: market.cityKey,
|
|
10494
|
+
censusName: market.censusName,
|
|
10495
|
+
population: market.population,
|
|
10496
|
+
populationYear: market.populationYear,
|
|
10497
|
+
zips: market.zips,
|
|
10498
|
+
counties: market.counties,
|
|
10499
|
+
status: "failed",
|
|
10500
|
+
error: errorMessage(err),
|
|
10501
|
+
resultCount: 0,
|
|
10502
|
+
durationMs: Date.now() - started,
|
|
10503
|
+
attempts,
|
|
10504
|
+
results: []
|
|
10505
|
+
};
|
|
10423
10506
|
}
|
|
10424
|
-
return {
|
|
10425
|
-
city: market.city,
|
|
10426
|
-
state: market.state,
|
|
10427
|
-
location: market.location,
|
|
10428
|
-
cityKey: market.cityKey,
|
|
10429
|
-
censusName: market.censusName,
|
|
10430
|
-
population: market.population,
|
|
10431
|
-
populationYear: market.populationYear,
|
|
10432
|
-
zips: market.zips,
|
|
10433
|
-
counties: market.counties,
|
|
10434
|
-
status: "failed",
|
|
10435
|
-
error: lastError ? errorMessage(lastError) : "City Maps search failed",
|
|
10436
|
-
resultCount: 0,
|
|
10437
|
-
durationMs: Date.now() - started,
|
|
10438
|
-
results: []
|
|
10439
|
-
};
|
|
10440
10507
|
}
|
|
10441
10508
|
function csvRowsFor(result) {
|
|
10442
10509
|
const rows = [];
|
|
@@ -10457,6 +10524,7 @@ function csvRowsFor(result) {
|
|
|
10457
10524
|
result_position: null,
|
|
10458
10525
|
business_name: null,
|
|
10459
10526
|
review_stars: null,
|
|
10527
|
+
review_count: null,
|
|
10460
10528
|
category: null,
|
|
10461
10529
|
address: null,
|
|
10462
10530
|
phone: null,
|
|
@@ -10490,6 +10558,7 @@ function csvRowsFor(result) {
|
|
|
10490
10558
|
result_position: business.position,
|
|
10491
10559
|
business_name: business.name,
|
|
10492
10560
|
review_stars: business.rating,
|
|
10561
|
+
review_count: business.reviewCount,
|
|
10493
10562
|
category: business.category,
|
|
10494
10563
|
address: business.address,
|
|
10495
10564
|
phone: business.phone,
|
|
@@ -10530,6 +10599,7 @@ async function saveDirectoryCsv(result) {
|
|
|
10530
10599
|
"result_position",
|
|
10531
10600
|
"business_name",
|
|
10532
10601
|
"review_stars",
|
|
10602
|
+
"review_count",
|
|
10533
10603
|
"category",
|
|
10534
10604
|
"address",
|
|
10535
10605
|
"phone",
|
|
@@ -10632,8 +10702,360 @@ directoryApp.post("/run", createApiKeyAuth(), async (c) => {
|
|
|
10632
10702
|
}
|
|
10633
10703
|
});
|
|
10634
10704
|
|
|
10635
|
-
// src/api/
|
|
10705
|
+
// src/api/workflow-routes.ts
|
|
10706
|
+
import { createHmac } from "crypto";
|
|
10707
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
10636
10708
|
import { Hono as Hono7 } from "hono";
|
|
10709
|
+
import { z as z16 } from "zod";
|
|
10710
|
+
var workflowApp = new Hono7();
|
|
10711
|
+
var WorkflowInputSchema = z16.record(z16.unknown()).default({});
|
|
10712
|
+
var WorkflowIdSchema = z16.string().min(1);
|
|
10713
|
+
var CadenceSchema = z16.enum(["daily", "weekly", "monthly"]);
|
|
10714
|
+
var ScheduleStatusSchema = z16.enum(["active", "paused"]);
|
|
10715
|
+
var RunBodySchema = z16.object({
|
|
10716
|
+
workflowId: WorkflowIdSchema,
|
|
10717
|
+
input: WorkflowInputSchema,
|
|
10718
|
+
webhookUrl: z16.string().url().optional()
|
|
10719
|
+
});
|
|
10720
|
+
var ScheduleCreateSchema = z16.object({
|
|
10721
|
+
workflowId: WorkflowIdSchema,
|
|
10722
|
+
name: z16.string().min(1).max(120).optional(),
|
|
10723
|
+
input: WorkflowInputSchema,
|
|
10724
|
+
cadence: CadenceSchema.default("weekly"),
|
|
10725
|
+
timezone: z16.string().min(1).max(64).default("UTC"),
|
|
10726
|
+
webhookUrl: z16.string().url().optional(),
|
|
10727
|
+
nextRunAt: z16.string().datetime().optional()
|
|
10728
|
+
});
|
|
10729
|
+
var SchedulePatchSchema = z16.object({
|
|
10730
|
+
name: z16.string().min(1).max(120).optional(),
|
|
10731
|
+
status: ScheduleStatusSchema.optional(),
|
|
10732
|
+
input: WorkflowInputSchema.optional(),
|
|
10733
|
+
cadence: CadenceSchema.optional(),
|
|
10734
|
+
timezone: z16.string().min(1).max(64).optional(),
|
|
10735
|
+
webhookUrl: z16.string().url().nullable().optional(),
|
|
10736
|
+
nextRunAt: z16.string().datetime().nullable().optional()
|
|
10737
|
+
});
|
|
10738
|
+
function hostedWorkflowOutputDir() {
|
|
10739
|
+
return workflowOutputBaseDir(process.env.MCP_SCRAPER_WORKFLOW_OUTPUT_DIR?.trim() || "/tmp/mcp-scraper-workflows");
|
|
10740
|
+
}
|
|
10741
|
+
function originFromUrl(url) {
|
|
10742
|
+
const parsed = new URL(url);
|
|
10743
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
10744
|
+
}
|
|
10745
|
+
function addCadence(fromIso, cadence) {
|
|
10746
|
+
const next = new Date(fromIso);
|
|
10747
|
+
if (!Number.isFinite(next.getTime())) return new Date(Date.now() + 24 * 60 * 60 * 1e3).toISOString();
|
|
10748
|
+
if (cadence === "daily") next.setUTCDate(next.getUTCDate() + 1);
|
|
10749
|
+
else if (cadence === "weekly") next.setUTCDate(next.getUTCDate() + 7);
|
|
10750
|
+
else next.setUTCMonth(next.getUTCMonth() + 1);
|
|
10751
|
+
return next.toISOString();
|
|
10752
|
+
}
|
|
10753
|
+
function defaultNextRunAt(cadence) {
|
|
10754
|
+
return addCadence((/* @__PURE__ */ new Date()).toISOString(), cadence);
|
|
10755
|
+
}
|
|
10756
|
+
function validateWorkflowInput(workflowId, input) {
|
|
10757
|
+
const definition = workflowDefinition(workflowId);
|
|
10758
|
+
return definition.inputSchema.parse(input);
|
|
10759
|
+
}
|
|
10760
|
+
async function validateWebhookUrl(url) {
|
|
10761
|
+
if (!url) return null;
|
|
10762
|
+
const result = await validatePublicHttpUrl(url, { field: "webhookUrl", requireHttps: true });
|
|
10763
|
+
if (result.error) throw new Error(result.error);
|
|
10764
|
+
return url;
|
|
10765
|
+
}
|
|
10766
|
+
function runDownloadUrl(c, runId, artifactId) {
|
|
10767
|
+
return `${originFromUrl(c.req.url)}/workflows/runs/${runId}/artifacts/${artifactId}`;
|
|
10768
|
+
}
|
|
10769
|
+
function exposeRun(c, run, artifacts = []) {
|
|
10770
|
+
return {
|
|
10771
|
+
...run,
|
|
10772
|
+
artifacts: artifacts.map((artifact) => ({
|
|
10773
|
+
...artifact,
|
|
10774
|
+
downloadUrl: runDownloadUrl(c, run.id, artifact.id)
|
|
10775
|
+
}))
|
|
10776
|
+
};
|
|
10777
|
+
}
|
|
10778
|
+
async function persistRunArtifacts(runId, userId, manifest) {
|
|
10779
|
+
if (!manifest) return [];
|
|
10780
|
+
return replaceWorkflowArtifacts(runId, userId, manifest.artifacts);
|
|
10781
|
+
}
|
|
10782
|
+
async function readManifestFromSummary(summary) {
|
|
10783
|
+
if (!summary.reportPath) return null;
|
|
10784
|
+
const manifestPath = summary.reportPath.replace(/report\.html$/, "manifest.json");
|
|
10785
|
+
try {
|
|
10786
|
+
return JSON.parse(await readFile2(manifestPath, "utf8"));
|
|
10787
|
+
} catch {
|
|
10788
|
+
return null;
|
|
10789
|
+
}
|
|
10790
|
+
}
|
|
10791
|
+
function webhookSignature(body, timestamp) {
|
|
10792
|
+
const secret2 = process.env.MCP_SCRAPER_WEBHOOK_SECRET?.trim();
|
|
10793
|
+
if (!secret2) return null;
|
|
10794
|
+
return createHmac("sha256", secret2).update(`${timestamp}.${body}`).digest("hex");
|
|
10795
|
+
}
|
|
10796
|
+
async function deliverWorkflowWebhook(input) {
|
|
10797
|
+
if (!input.webhookUrl) return;
|
|
10798
|
+
const payload = {
|
|
10799
|
+
event: input.run.status === "failed" ? "workflow.run.failed" : input.run.status === "partial" ? "workflow.run.partial" : "workflow.run.succeeded",
|
|
10800
|
+
runId: input.run.id,
|
|
10801
|
+
scheduleId: input.scheduleId,
|
|
10802
|
+
workflowId: input.run.workflow_id,
|
|
10803
|
+
status: input.run.status,
|
|
10804
|
+
completedAt: input.run.completed_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
10805
|
+
summary: {
|
|
10806
|
+
title: input.summary?.title ?? input.run.workflow_id,
|
|
10807
|
+
counts: input.summary?.counts ?? {},
|
|
10808
|
+
warnings: input.summary?.warnings ?? [],
|
|
10809
|
+
errors: input.summary?.errors ?? (input.run.error_message ? [input.run.error_message] : [])
|
|
10810
|
+
},
|
|
10811
|
+
artifacts: input.artifacts.map((artifact) => ({
|
|
10812
|
+
kind: artifact.kind,
|
|
10813
|
+
label: artifact.label,
|
|
10814
|
+
path: artifact.path
|
|
10815
|
+
}))
|
|
10816
|
+
};
|
|
10817
|
+
const body = JSON.stringify(payload);
|
|
10818
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
10819
|
+
const headers = {
|
|
10820
|
+
"Content-Type": "application/json",
|
|
10821
|
+
"x-mcp-scraper-timestamp": timestamp
|
|
10822
|
+
};
|
|
10823
|
+
const signature = webhookSignature(body, timestamp);
|
|
10824
|
+
if (signature) headers["x-mcp-scraper-signature"] = signature;
|
|
10825
|
+
let lastError = null;
|
|
10826
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
10827
|
+
try {
|
|
10828
|
+
const res = await fetch(input.webhookUrl, { method: "POST", headers, body, signal: AbortSignal.timeout(15e3) });
|
|
10829
|
+
if (res.ok) {
|
|
10830
|
+
await recordWorkflowWebhookDelivery({ runId: input.run.id, scheduleId: input.scheduleId, userId: input.userId, webhookUrl: input.webhookUrl, status: "delivered", attempts: attempt });
|
|
10831
|
+
return;
|
|
10832
|
+
}
|
|
10833
|
+
lastError = `HTTP ${res.status}`;
|
|
10834
|
+
} catch (err) {
|
|
10835
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
10836
|
+
}
|
|
10837
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 500));
|
|
10838
|
+
}
|
|
10839
|
+
await recordWorkflowWebhookDelivery({ runId: input.run.id, scheduleId: input.scheduleId, userId: input.userId, webhookUrl: input.webhookUrl, status: "failed", attempts: 3, lastError });
|
|
10840
|
+
}
|
|
10841
|
+
async function executeWorkflowRun(input) {
|
|
10842
|
+
await markWorkflowRunRunning(input.runId);
|
|
10843
|
+
try {
|
|
10844
|
+
const summary = await runWorkflow(input.workflowId, input.workflowInput, {
|
|
10845
|
+
apiKey: input.user.api_key,
|
|
10846
|
+
apiUrl: input.apiUrl,
|
|
10847
|
+
outputDir: hostedWorkflowOutputDir(),
|
|
10848
|
+
runId: input.runId
|
|
10849
|
+
});
|
|
10850
|
+
const manifest = await readManifestFromSummary(summary);
|
|
10851
|
+
await completeWorkflowRunRecord(input.runId, summary.status, manifest ?? { summary });
|
|
10852
|
+
const run = await getWorkflowRun(input.runId, input.user.id);
|
|
10853
|
+
if (!run) throw new Error("Workflow run disappeared after execution");
|
|
10854
|
+
const artifacts = await persistRunArtifacts(input.runId, input.user.id, manifest);
|
|
10855
|
+
await deliverWorkflowWebhook({ userId: input.user.id, run, scheduleId: input.scheduleId ?? null, webhookUrl: input.webhookUrl ?? null, summary, artifacts });
|
|
10856
|
+
return { run, summary, artifacts };
|
|
10857
|
+
} catch (err) {
|
|
10858
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10859
|
+
await failWorkflowRunRecord(input.runId, message, { error: message });
|
|
10860
|
+
const run = await getWorkflowRun(input.runId, input.user.id);
|
|
10861
|
+
if (!run) throw err;
|
|
10862
|
+
await deliverWorkflowWebhook({ userId: input.user.id, run, scheduleId: input.scheduleId ?? null, webhookUrl: input.webhookUrl ?? null, summary: null, artifacts: [] });
|
|
10863
|
+
throw err;
|
|
10864
|
+
}
|
|
10865
|
+
}
|
|
10866
|
+
workflowApp.get("/definitions", createApiKeyAuth(), (c) => {
|
|
10867
|
+
return c.json({ workflows: listWorkflowDefinitions() });
|
|
10868
|
+
});
|
|
10869
|
+
workflowApp.post("/run", createApiKeyAuth(), async (c) => {
|
|
10870
|
+
const user = c.get("user");
|
|
10871
|
+
const parsed = RunBodySchema.safeParse(await c.req.json().catch(() => ({})));
|
|
10872
|
+
if (!parsed.success) return c.json({ error: parsed.error.issues[0]?.message ?? "Invalid request" }, 400);
|
|
10873
|
+
let input;
|
|
10874
|
+
let webhookUrl;
|
|
10875
|
+
try {
|
|
10876
|
+
input = validateWorkflowInput(parsed.data.workflowId, parsed.data.input);
|
|
10877
|
+
webhookUrl = await validateWebhookUrl(parsed.data.webhookUrl);
|
|
10878
|
+
} catch (err) {
|
|
10879
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 400);
|
|
10880
|
+
}
|
|
10881
|
+
const run = await createWorkflowRun({ userId: user.id, workflowId: parsed.data.workflowId, workflowInput: input });
|
|
10882
|
+
try {
|
|
10883
|
+
const executed = await executeWorkflowRun({
|
|
10884
|
+
runId: run.id,
|
|
10885
|
+
user,
|
|
10886
|
+
workflowId: parsed.data.workflowId,
|
|
10887
|
+
workflowInput: input,
|
|
10888
|
+
apiUrl: originFromUrl(c.req.url),
|
|
10889
|
+
webhookUrl
|
|
10890
|
+
});
|
|
10891
|
+
return c.json({ run: exposeRun(c, executed.run, executed.artifacts), summary: executed.summary });
|
|
10892
|
+
} catch (err) {
|
|
10893
|
+
const failed = await getWorkflowRun(run.id, user.id);
|
|
10894
|
+
return c.json({ run: failed, error: err instanceof Error ? err.message : String(err) }, 500);
|
|
10895
|
+
}
|
|
10896
|
+
});
|
|
10897
|
+
workflowApp.get("/runs", createApiKeyAuth(), async (c) => {
|
|
10898
|
+
const user = c.get("user");
|
|
10899
|
+
const runs = await listWorkflowRuns(user.id);
|
|
10900
|
+
return c.json({ runs });
|
|
10901
|
+
});
|
|
10902
|
+
workflowApp.get("/runs/:id", createApiKeyAuth(), async (c) => {
|
|
10903
|
+
const user = c.get("user");
|
|
10904
|
+
const run = await getWorkflowRun(c.req.param("id"), user.id);
|
|
10905
|
+
if (!run) return c.json({ error: "Run not found" }, 404);
|
|
10906
|
+
const artifacts = await listWorkflowArtifacts(run.id, user.id);
|
|
10907
|
+
return c.json({ run: exposeRun(c, run, artifacts) });
|
|
10908
|
+
});
|
|
10909
|
+
workflowApp.get("/runs/:id/artifacts/:artifactId", createApiKeyAuth(), async (c) => {
|
|
10910
|
+
const user = c.get("user");
|
|
10911
|
+
const run = await getWorkflowRun(c.req.param("id"), user.id);
|
|
10912
|
+
if (!run) return c.json({ error: "Run not found" }, 404);
|
|
10913
|
+
const artifact = await getWorkflowArtifact(c.req.param("artifactId"), user.id);
|
|
10914
|
+
if (!artifact || artifact.run_id !== run.id) return c.json({ error: "Artifact not found" }, 404);
|
|
10915
|
+
try {
|
|
10916
|
+
const content = await readFile2(artifact.path);
|
|
10917
|
+
return new Response(content, {
|
|
10918
|
+
headers: {
|
|
10919
|
+
"Content-Type": artifact.content_type,
|
|
10920
|
+
"Content-Disposition": `attachment; filename="${artifact.path.split("/").pop() || artifact.id}"`
|
|
10921
|
+
}
|
|
10922
|
+
});
|
|
10923
|
+
} catch {
|
|
10924
|
+
return c.json({ error: "Artifact file is no longer available" }, 410);
|
|
10925
|
+
}
|
|
10926
|
+
});
|
|
10927
|
+
workflowApp.post("/schedules", createApiKeyAuth(), async (c) => {
|
|
10928
|
+
const user = c.get("user");
|
|
10929
|
+
const parsed = ScheduleCreateSchema.safeParse(await c.req.json().catch(() => ({})));
|
|
10930
|
+
if (!parsed.success) return c.json({ error: parsed.error.issues[0]?.message ?? "Invalid request" }, 400);
|
|
10931
|
+
let input;
|
|
10932
|
+
let webhookUrl;
|
|
10933
|
+
try {
|
|
10934
|
+
input = validateWorkflowInput(parsed.data.workflowId, parsed.data.input);
|
|
10935
|
+
webhookUrl = await validateWebhookUrl(parsed.data.webhookUrl);
|
|
10936
|
+
} catch (err) {
|
|
10937
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 400);
|
|
10938
|
+
}
|
|
10939
|
+
const schedule = await createWorkflowSchedule({
|
|
10940
|
+
userId: user.id,
|
|
10941
|
+
workflowId: parsed.data.workflowId,
|
|
10942
|
+
name: parsed.data.name ?? workflowDefinition(parsed.data.workflowId).title,
|
|
10943
|
+
workflowInput: input,
|
|
10944
|
+
cadence: parsed.data.cadence,
|
|
10945
|
+
timezone: parsed.data.timezone,
|
|
10946
|
+
webhookUrl,
|
|
10947
|
+
nextRunAt: parsed.data.nextRunAt ?? defaultNextRunAt(parsed.data.cadence)
|
|
10948
|
+
});
|
|
10949
|
+
return c.json({ schedule }, 201);
|
|
10950
|
+
});
|
|
10951
|
+
workflowApp.get("/schedules", createApiKeyAuth(), async (c) => {
|
|
10952
|
+
const user = c.get("user");
|
|
10953
|
+
return c.json({ schedules: await listWorkflowSchedules(user.id) });
|
|
10954
|
+
});
|
|
10955
|
+
workflowApp.patch("/schedules/:id", createApiKeyAuth(), async (c) => {
|
|
10956
|
+
const user = c.get("user");
|
|
10957
|
+
const existing = await getWorkflowSchedule(c.req.param("id"), user.id);
|
|
10958
|
+
if (!existing) return c.json({ error: "Schedule not found" }, 404);
|
|
10959
|
+
const parsed = SchedulePatchSchema.safeParse(await c.req.json().catch(() => ({})));
|
|
10960
|
+
if (!parsed.success) return c.json({ error: parsed.error.issues[0]?.message ?? "Invalid request" }, 400);
|
|
10961
|
+
let nextInput;
|
|
10962
|
+
let webhookUrl;
|
|
10963
|
+
try {
|
|
10964
|
+
nextInput = parsed.data.input ? validateWorkflowInput(existing.workflow_id, parsed.data.input) : void 0;
|
|
10965
|
+
webhookUrl = parsed.data.webhookUrl === void 0 ? void 0 : await validateWebhookUrl(parsed.data.webhookUrl);
|
|
10966
|
+
} catch (err) {
|
|
10967
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 400);
|
|
10968
|
+
}
|
|
10969
|
+
const schedule = await patchWorkflowSchedule({
|
|
10970
|
+
id: existing.id,
|
|
10971
|
+
userId: user.id,
|
|
10972
|
+
name: parsed.data.name,
|
|
10973
|
+
status: parsed.data.status,
|
|
10974
|
+
workflowInput: nextInput,
|
|
10975
|
+
cadence: parsed.data.cadence,
|
|
10976
|
+
timezone: parsed.data.timezone,
|
|
10977
|
+
webhookUrl,
|
|
10978
|
+
nextRunAt: parsed.data.nextRunAt
|
|
10979
|
+
});
|
|
10980
|
+
return c.json({ schedule });
|
|
10981
|
+
});
|
|
10982
|
+
workflowApp.delete("/schedules/:id", createApiKeyAuth(), async (c) => {
|
|
10983
|
+
const user = c.get("user");
|
|
10984
|
+
const deleted = await deleteWorkflowSchedule(c.req.param("id"), user.id);
|
|
10985
|
+
if (!deleted) return c.json({ error: "Schedule not found" }, 404);
|
|
10986
|
+
return c.json({ ok: true });
|
|
10987
|
+
});
|
|
10988
|
+
workflowApp.post("/schedules/:id/run", createApiKeyAuth(), async (c) => {
|
|
10989
|
+
const user = c.get("user");
|
|
10990
|
+
const schedule = await getWorkflowSchedule(c.req.param("id"), user.id);
|
|
10991
|
+
if (!schedule) return c.json({ error: "Schedule not found" }, 404);
|
|
10992
|
+
const run = await createWorkflowRun({ userId: user.id, scheduleId: schedule.id, workflowId: schedule.workflow_id, workflowInput: schedule.input });
|
|
10993
|
+
try {
|
|
10994
|
+
const executed = await executeWorkflowRun({
|
|
10995
|
+
runId: run.id,
|
|
10996
|
+
user,
|
|
10997
|
+
workflowId: schedule.workflow_id,
|
|
10998
|
+
workflowInput: schedule.input,
|
|
10999
|
+
apiUrl: originFromUrl(c.req.url),
|
|
11000
|
+
scheduleId: schedule.id,
|
|
11001
|
+
webhookUrl: schedule.webhook_url
|
|
11002
|
+
});
|
|
11003
|
+
return c.json({ run: exposeRun(c, executed.run, executed.artifacts), summary: executed.summary });
|
|
11004
|
+
} catch (err) {
|
|
11005
|
+
const failed = await getWorkflowRun(run.id, user.id);
|
|
11006
|
+
return c.json({ run: failed, error: err instanceof Error ? err.message : String(err) }, 500);
|
|
11007
|
+
}
|
|
11008
|
+
});
|
|
11009
|
+
async function dispatchDueWorkflowSchedules(apiUrl, limit = 3) {
|
|
11010
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11011
|
+
const due = await listDueWorkflowSchedules(now, limit);
|
|
11012
|
+
const results = [];
|
|
11013
|
+
for (const schedule of due) {
|
|
11014
|
+
const user = await getUserById(schedule.user_id);
|
|
11015
|
+
if (!user) {
|
|
11016
|
+
results.push({ scheduleId: schedule.id, status: "failed", error: "User not found" });
|
|
11017
|
+
continue;
|
|
11018
|
+
}
|
|
11019
|
+
const scheduledFor = schedule.next_run_at ?? now;
|
|
11020
|
+
const run = await createWorkflowRun({
|
|
11021
|
+
userId: user.id,
|
|
11022
|
+
scheduleId: schedule.id,
|
|
11023
|
+
workflowId: schedule.workflow_id,
|
|
11024
|
+
workflowInput: schedule.input,
|
|
11025
|
+
idempotencyKey: `${schedule.id}:${scheduledFor}`
|
|
11026
|
+
});
|
|
11027
|
+
await markWorkflowScheduleRan(schedule.id, now, addCadence(scheduledFor, schedule.cadence));
|
|
11028
|
+
if (run.status !== "queued") {
|
|
11029
|
+
results.push({ scheduleId: schedule.id, runId: run.id, status: run.status });
|
|
11030
|
+
continue;
|
|
11031
|
+
}
|
|
11032
|
+
try {
|
|
11033
|
+
const executed = await executeWorkflowRun({
|
|
11034
|
+
runId: run.id,
|
|
11035
|
+
user,
|
|
11036
|
+
workflowId: schedule.workflow_id,
|
|
11037
|
+
workflowInput: schedule.input,
|
|
11038
|
+
apiUrl,
|
|
11039
|
+
scheduleId: schedule.id,
|
|
11040
|
+
webhookUrl: schedule.webhook_url
|
|
11041
|
+
});
|
|
11042
|
+
results.push({ scheduleId: schedule.id, runId: executed.run.id, status: executed.run.status });
|
|
11043
|
+
} catch (err) {
|
|
11044
|
+
results.push({ scheduleId: schedule.id, runId: run.id, status: "failed", error: err instanceof Error ? err.message : String(err) });
|
|
11045
|
+
}
|
|
11046
|
+
}
|
|
11047
|
+
return { dispatched: results.length, results };
|
|
11048
|
+
}
|
|
11049
|
+
workflowApp.post("/cron/dispatch", async (c) => {
|
|
11050
|
+
const secret2 = c.req.header("authorization");
|
|
11051
|
+
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
11052
|
+
return c.json({ error: "Unauthorized" }, 401);
|
|
11053
|
+
}
|
|
11054
|
+
return c.json(await dispatchDueWorkflowSchedules(originFromUrl(c.req.url)));
|
|
11055
|
+
});
|
|
11056
|
+
|
|
11057
|
+
// src/api/serp-intelligence-routes.ts
|
|
11058
|
+
import { Hono as Hono8 } from "hono";
|
|
10637
11059
|
|
|
10638
11060
|
// src/serp-intelligence/page-snapshot-extractor.ts
|
|
10639
11061
|
import { createHash } from "crypto";
|
|
@@ -10945,7 +11367,7 @@ async function capturePageSnapshots(targets, options = {}) {
|
|
|
10945
11367
|
}
|
|
10946
11368
|
|
|
10947
11369
|
// src/serp-intelligence/schemas.ts
|
|
10948
|
-
import { z as
|
|
11370
|
+
import { z as z17 } from "zod";
|
|
10949
11371
|
var SerpIntelligenceDeviceValues = ["desktop", "mobile"];
|
|
10950
11372
|
var SerpIntelligenceProxyModeValues = ["location", "configured", "none"];
|
|
10951
11373
|
var SerpIntelligenceAttemptOutcomeValues = [
|
|
@@ -11007,171 +11429,171 @@ function isPublicHttpUrl(value) {
|
|
|
11007
11429
|
return false;
|
|
11008
11430
|
}
|
|
11009
11431
|
}
|
|
11010
|
-
var SerpIntelligencePublicHttpUrlSchema =
|
|
11011
|
-
var SerpIntelligenceCaptureBodySchema =
|
|
11012
|
-
query:
|
|
11013
|
-
location:
|
|
11014
|
-
gl:
|
|
11015
|
-
hl:
|
|
11016
|
-
device:
|
|
11017
|
-
proxyMode:
|
|
11018
|
-
proxyZip:
|
|
11019
|
-
pages:
|
|
11020
|
-
debug:
|
|
11021
|
-
includePageSnapshots:
|
|
11022
|
-
pageSnapshotLimit:
|
|
11432
|
+
var SerpIntelligencePublicHttpUrlSchema = z17.string().url().refine(isPublicHttpUrl, "url must be a public HTTP or HTTPS URL");
|
|
11433
|
+
var SerpIntelligenceCaptureBodySchema = z17.object({
|
|
11434
|
+
query: z17.string().trim().min(1, "query is required"),
|
|
11435
|
+
location: z17.string().trim().min(1).optional(),
|
|
11436
|
+
gl: z17.string().trim().length(2).default("us"),
|
|
11437
|
+
hl: z17.string().trim().length(2).default("en"),
|
|
11438
|
+
device: z17.enum(SerpIntelligenceDeviceValues).default("desktop"),
|
|
11439
|
+
proxyMode: z17.enum(SerpIntelligenceProxyModeValues).default("location"),
|
|
11440
|
+
proxyZip: z17.string().regex(/^\d{5}$/).optional(),
|
|
11441
|
+
pages: z17.number().int().min(1).max(2).default(1),
|
|
11442
|
+
debug: z17.boolean().default(false),
|
|
11443
|
+
includePageSnapshots: z17.boolean().default(false),
|
|
11444
|
+
pageSnapshotLimit: z17.number().int().min(0).max(10).default(0)
|
|
11023
11445
|
}).strict();
|
|
11024
|
-
var SerpIntelligencePageSnapshotRequestSchema =
|
|
11446
|
+
var SerpIntelligencePageSnapshotRequestSchema = z17.object({
|
|
11025
11447
|
url: SerpIntelligencePublicHttpUrlSchema,
|
|
11026
|
-
sourceKind:
|
|
11027
|
-
sourcePosition:
|
|
11448
|
+
sourceKind: z17.enum(SerpPageSnapshotSourceKindValues).default("configured_target"),
|
|
11449
|
+
sourcePosition: z17.number().int().min(1).optional()
|
|
11028
11450
|
}).strict();
|
|
11029
|
-
var SerpIntelligencePageSnapshotsBodySchema =
|
|
11030
|
-
urls:
|
|
11031
|
-
targets:
|
|
11032
|
-
maxConcurrency:
|
|
11033
|
-
timeoutMs:
|
|
11034
|
-
debug:
|
|
11451
|
+
var SerpIntelligencePageSnapshotsBodySchema = z17.object({
|
|
11452
|
+
urls: z17.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25),
|
|
11453
|
+
targets: z17.array(SerpIntelligencePageSnapshotRequestSchema).min(1).max(25).optional(),
|
|
11454
|
+
maxConcurrency: z17.number().int().min(1).max(5).default(2),
|
|
11455
|
+
timeoutMs: z17.number().int().min(1e3).max(6e4).default(15e3),
|
|
11456
|
+
debug: z17.boolean().default(false)
|
|
11035
11457
|
}).strict();
|
|
11036
|
-
var SerpIntelligenceAICitationSchema =
|
|
11037
|
-
text:
|
|
11038
|
-
href:
|
|
11458
|
+
var SerpIntelligenceAICitationSchema = z17.object({
|
|
11459
|
+
text: z17.string(),
|
|
11460
|
+
href: z17.string()
|
|
11039
11461
|
}).strict();
|
|
11040
|
-
var SerpIntelligenceOrganicResultSchema =
|
|
11041
|
-
position:
|
|
11042
|
-
title:
|
|
11043
|
-
url:
|
|
11044
|
-
domain:
|
|
11045
|
-
cite:
|
|
11046
|
-
snippet:
|
|
11047
|
-
isRedditStyle:
|
|
11048
|
-
inlineRating:
|
|
11049
|
-
value:
|
|
11050
|
-
count:
|
|
11462
|
+
var SerpIntelligenceOrganicResultSchema = z17.object({
|
|
11463
|
+
position: z17.number().int().min(1),
|
|
11464
|
+
title: z17.string(),
|
|
11465
|
+
url: z17.string(),
|
|
11466
|
+
domain: z17.string(),
|
|
11467
|
+
cite: z17.string().nullable(),
|
|
11468
|
+
snippet: z17.string().nullable(),
|
|
11469
|
+
isRedditStyle: z17.boolean(),
|
|
11470
|
+
inlineRating: z17.object({
|
|
11471
|
+
value: z17.string(),
|
|
11472
|
+
count: z17.string()
|
|
11051
11473
|
}).strict().nullable()
|
|
11052
11474
|
}).strict();
|
|
11053
|
-
var SerpIntelligenceLocationEvidenceSchema =
|
|
11054
|
-
status:
|
|
11055
|
-
expected:
|
|
11056
|
-
city:
|
|
11057
|
-
regionCode:
|
|
11058
|
-
canonicalLocation:
|
|
11475
|
+
var SerpIntelligenceLocationEvidenceSchema = z17.object({
|
|
11476
|
+
status: z17.enum(SerpIntelligenceLocalizationStatusValues),
|
|
11477
|
+
expected: z17.object({
|
|
11478
|
+
city: z17.string(),
|
|
11479
|
+
regionCode: z17.string().nullable(),
|
|
11480
|
+
canonicalLocation: z17.string()
|
|
11059
11481
|
}).strict().nullable(),
|
|
11060
|
-
candidates:
|
|
11061
|
-
city:
|
|
11062
|
-
regionCode:
|
|
11063
|
-
count:
|
|
11064
|
-
examples:
|
|
11482
|
+
candidates: z17.array(z17.object({
|
|
11483
|
+
city: z17.string(),
|
|
11484
|
+
regionCode: z17.string(),
|
|
11485
|
+
count: z17.number().int().min(0),
|
|
11486
|
+
examples: z17.array(z17.string())
|
|
11065
11487
|
}).strict())
|
|
11066
11488
|
}).strict();
|
|
11067
|
-
var SerpIntelligenceHarvestResultSchema =
|
|
11068
|
-
seed:
|
|
11069
|
-
location:
|
|
11070
|
-
extractedAt:
|
|
11071
|
-
totalQuestions:
|
|
11072
|
-
surface:
|
|
11073
|
-
aiOverview:
|
|
11074
|
-
detected:
|
|
11075
|
-
text:
|
|
11076
|
-
citations:
|
|
11077
|
-
expanded:
|
|
11078
|
-
fullyExpanded:
|
|
11079
|
-
sections:
|
|
11489
|
+
var SerpIntelligenceHarvestResultSchema = z17.object({
|
|
11490
|
+
seed: z17.string(),
|
|
11491
|
+
location: z17.string().nullable(),
|
|
11492
|
+
extractedAt: z17.string(),
|
|
11493
|
+
totalQuestions: z17.number().int().min(0),
|
|
11494
|
+
surface: z17.enum(["web", "aim", "unknown"]),
|
|
11495
|
+
aiOverview: z17.object({
|
|
11496
|
+
detected: z17.boolean(),
|
|
11497
|
+
text: z17.string().nullable(),
|
|
11498
|
+
citations: z17.array(SerpIntelligenceAICitationSchema),
|
|
11499
|
+
expanded: z17.boolean().optional(),
|
|
11500
|
+
fullyExpanded: z17.boolean().optional(),
|
|
11501
|
+
sections: z17.array(z17.string()).optional()
|
|
11080
11502
|
}).strict(),
|
|
11081
|
-
aiMode:
|
|
11082
|
-
detected:
|
|
11083
|
-
text:
|
|
11084
|
-
citations:
|
|
11503
|
+
aiMode: z17.object({
|
|
11504
|
+
detected: z17.boolean(),
|
|
11505
|
+
text: z17.string().nullable(),
|
|
11506
|
+
citations: z17.array(SerpIntelligenceAICitationSchema)
|
|
11085
11507
|
}).strict(),
|
|
11086
|
-
tree:
|
|
11087
|
-
flat:
|
|
11088
|
-
videos:
|
|
11089
|
-
forums:
|
|
11090
|
-
organicResults:
|
|
11091
|
-
localPack:
|
|
11092
|
-
entityIds:
|
|
11093
|
-
entities:
|
|
11094
|
-
name:
|
|
11095
|
-
kgId:
|
|
11096
|
-
cid:
|
|
11097
|
-
gcid:
|
|
11508
|
+
tree: z17.array(z17.unknown()),
|
|
11509
|
+
flat: z17.array(z17.unknown()),
|
|
11510
|
+
videos: z17.array(z17.unknown()),
|
|
11511
|
+
forums: z17.array(z17.unknown()),
|
|
11512
|
+
organicResults: z17.array(SerpIntelligenceOrganicResultSchema),
|
|
11513
|
+
localPack: z17.array(z17.unknown()),
|
|
11514
|
+
entityIds: z17.object({
|
|
11515
|
+
entities: z17.array(z17.object({
|
|
11516
|
+
name: z17.string(),
|
|
11517
|
+
kgId: z17.string().nullable(),
|
|
11518
|
+
cid: z17.string().nullable(),
|
|
11519
|
+
gcid: z17.string().nullable()
|
|
11098
11520
|
}).strict()),
|
|
11099
|
-
kgIds:
|
|
11100
|
-
cids:
|
|
11101
|
-
gcids:
|
|
11521
|
+
kgIds: z17.array(z17.string()),
|
|
11522
|
+
cids: z17.array(z17.string()),
|
|
11523
|
+
gcids: z17.array(z17.string())
|
|
11102
11524
|
}).strict(),
|
|
11103
|
-
stats:
|
|
11104
|
-
seed:
|
|
11105
|
-
totalQuestions:
|
|
11106
|
-
maxDepthReached:
|
|
11107
|
-
durationMs:
|
|
11108
|
-
errorCount:
|
|
11525
|
+
stats: z17.object({
|
|
11526
|
+
seed: z17.string(),
|
|
11527
|
+
totalQuestions: z17.number().int().min(0),
|
|
11528
|
+
maxDepthReached: z17.number().int().min(0),
|
|
11529
|
+
durationMs: z17.number().min(0),
|
|
11530
|
+
errorCount: z17.number().int().min(0)
|
|
11109
11531
|
}).strict(),
|
|
11110
|
-
diagnostics:
|
|
11111
|
-
completionStatus:
|
|
11112
|
-
problem:
|
|
11113
|
-
warnings:
|
|
11114
|
-
debug:
|
|
11532
|
+
diagnostics: z17.object({
|
|
11533
|
+
completionStatus: z17.enum(["paa_found", "no_paa", "serp_only"]),
|
|
11534
|
+
problem: z17.null(),
|
|
11535
|
+
warnings: z17.array(z17.unknown()).optional(),
|
|
11536
|
+
debug: z17.object({
|
|
11115
11537
|
locationEvidence: SerpIntelligenceLocationEvidenceSchema.optional()
|
|
11116
11538
|
}).passthrough().optional()
|
|
11117
11539
|
}).passthrough(),
|
|
11118
|
-
whatPeopleSaying:
|
|
11540
|
+
whatPeopleSaying: z17.array(z17.unknown())
|
|
11119
11541
|
}).strict();
|
|
11120
|
-
var SerpIntelligenceCaptureAttemptSchema =
|
|
11121
|
-
attemptNumber:
|
|
11122
|
-
outcome:
|
|
11123
|
-
startedAt:
|
|
11124
|
-
completedAt:
|
|
11125
|
-
durationMs:
|
|
11126
|
-
problemCode:
|
|
11127
|
-
message:
|
|
11128
|
-
kernelSessionId:
|
|
11129
|
-
cleanupSucceeded:
|
|
11542
|
+
var SerpIntelligenceCaptureAttemptSchema = z17.object({
|
|
11543
|
+
attemptNumber: z17.number().int().min(1),
|
|
11544
|
+
outcome: z17.enum(SerpIntelligenceAttemptOutcomeValues),
|
|
11545
|
+
startedAt: z17.string().optional(),
|
|
11546
|
+
completedAt: z17.string().optional(),
|
|
11547
|
+
durationMs: z17.number().min(0).optional(),
|
|
11548
|
+
problemCode: z17.string().optional(),
|
|
11549
|
+
message: z17.string().optional(),
|
|
11550
|
+
kernelSessionId: z17.string().nullable().optional(),
|
|
11551
|
+
cleanupSucceeded: z17.boolean().nullable().optional()
|
|
11130
11552
|
}).strict();
|
|
11131
|
-
var SerpPageSnapshotCaptureSchema =
|
|
11553
|
+
var SerpPageSnapshotCaptureSchema = z17.object({
|
|
11132
11554
|
url: SerpIntelligencePublicHttpUrlSchema,
|
|
11133
11555
|
requestedUrl: SerpIntelligencePublicHttpUrlSchema,
|
|
11134
11556
|
finalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
|
|
11135
|
-
sourceKind:
|
|
11136
|
-
sourcePosition:
|
|
11137
|
-
status:
|
|
11138
|
-
fetchedVia:
|
|
11139
|
-
httpStatus:
|
|
11140
|
-
contentType:
|
|
11141
|
-
title:
|
|
11557
|
+
sourceKind: z17.enum(SerpPageSnapshotSourceKindValues),
|
|
11558
|
+
sourcePosition: z17.number().int().min(1).nullable(),
|
|
11559
|
+
status: z17.enum(SerpPageFetchStatusValues),
|
|
11560
|
+
fetchedVia: z17.enum(SerpPageFetchedViaValues).nullable(),
|
|
11561
|
+
httpStatus: z17.number().int().min(100).max(599).nullable(),
|
|
11562
|
+
contentType: z17.string().nullable(),
|
|
11563
|
+
title: z17.string().nullable(),
|
|
11142
11564
|
canonicalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
|
|
11143
|
-
metaDescription:
|
|
11144
|
-
headings:
|
|
11145
|
-
level:
|
|
11146
|
-
text:
|
|
11565
|
+
metaDescription: z17.string().nullable(),
|
|
11566
|
+
headings: z17.array(z17.object({
|
|
11567
|
+
level: z17.number().int().min(1).max(6),
|
|
11568
|
+
text: z17.string()
|
|
11147
11569
|
}).strict()).default([]),
|
|
11148
|
-
artifact:
|
|
11149
|
-
htmlBlobUrl:
|
|
11150
|
-
textBlobUrl:
|
|
11151
|
-
markdownBlobUrl:
|
|
11152
|
-
screenshotBlobUrl:
|
|
11153
|
-
contentSha256:
|
|
11154
|
-
capturedAt:
|
|
11570
|
+
artifact: z17.object({
|
|
11571
|
+
htmlBlobUrl: z17.string().url().nullable(),
|
|
11572
|
+
textBlobUrl: z17.string().url().nullable(),
|
|
11573
|
+
markdownBlobUrl: z17.string().url().nullable(),
|
|
11574
|
+
screenshotBlobUrl: z17.string().url().nullable(),
|
|
11575
|
+
contentSha256: z17.string().nullable(),
|
|
11576
|
+
capturedAt: z17.string().nullable()
|
|
11155
11577
|
}).strict(),
|
|
11156
|
-
error:
|
|
11157
|
-
code:
|
|
11158
|
-
message:
|
|
11578
|
+
error: z17.object({
|
|
11579
|
+
code: z17.string(),
|
|
11580
|
+
message: z17.string()
|
|
11159
11581
|
}).strict().nullable()
|
|
11160
11582
|
}).strict();
|
|
11161
|
-
var SerpIntelligenceCaptureResponseSchema =
|
|
11583
|
+
var SerpIntelligenceCaptureResponseSchema = z17.object({
|
|
11162
11584
|
harvestResult: SerpIntelligenceHarvestResultSchema,
|
|
11163
|
-
attempts:
|
|
11585
|
+
attempts: z17.array(SerpIntelligenceCaptureAttemptSchema),
|
|
11164
11586
|
locationEvidence: SerpIntelligenceLocationEvidenceSchema.nullable(),
|
|
11165
|
-
pageSnapshotArtifacts:
|
|
11166
|
-
billing:
|
|
11167
|
-
creditsUsed:
|
|
11168
|
-
requestId:
|
|
11169
|
-
jobId:
|
|
11587
|
+
pageSnapshotArtifacts: z17.array(SerpPageSnapshotCaptureSchema),
|
|
11588
|
+
billing: z17.object({
|
|
11589
|
+
creditsUsed: z17.number().min(0).optional(),
|
|
11590
|
+
requestId: z17.string().optional(),
|
|
11591
|
+
jobId: z17.string().optional()
|
|
11170
11592
|
}).strict().optional()
|
|
11171
11593
|
}).strict();
|
|
11172
|
-
var SerpIntelligencePageSnapshotsResponseSchema =
|
|
11173
|
-
pageSnapshotArtifacts:
|
|
11174
|
-
attempts:
|
|
11594
|
+
var SerpIntelligencePageSnapshotsResponseSchema = z17.object({
|
|
11595
|
+
pageSnapshotArtifacts: z17.array(SerpPageSnapshotCaptureSchema),
|
|
11596
|
+
attempts: z17.array(SerpIntelligenceCaptureAttemptSchema).default([])
|
|
11175
11597
|
}).strict();
|
|
11176
11598
|
|
|
11177
11599
|
// src/serp-intelligence/serp-capture-service.ts
|
|
@@ -11343,7 +11765,7 @@ var SERP_INTELLIGENCE_RATE_LIMIT = 60;
|
|
|
11343
11765
|
var SERP_INTELLIGENCE_RATE_WINDOW_SECONDS = 60;
|
|
11344
11766
|
var POST_CAPTURE_ROUTE_LABEL = "POST /capture";
|
|
11345
11767
|
var POST_PAGE_SNAPSHOTS_ROUTE_LABEL = "POST /page-snapshots";
|
|
11346
|
-
var serpIntelligenceApp = new
|
|
11768
|
+
var serpIntelligenceApp = new Hono8();
|
|
11347
11769
|
serpIntelligenceApp.use("*", createApiKeyAuth());
|
|
11348
11770
|
function structuredError(input) {
|
|
11349
11771
|
return {
|
|
@@ -11518,7 +11940,7 @@ serpIntelligenceApp.post("/page-snapshots", async (c) => {
|
|
|
11518
11940
|
});
|
|
11519
11941
|
|
|
11520
11942
|
// src/mcp/mcp-routes.ts
|
|
11521
|
-
import { Hono as
|
|
11943
|
+
import { Hono as Hono9 } from "hono";
|
|
11522
11944
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
11523
11945
|
configureReportSaving(false);
|
|
11524
11946
|
function mcpAuthError() {
|
|
@@ -11548,11 +11970,11 @@ async function requireMcpCallerKey(c) {
|
|
|
11548
11970
|
if (!user) return mcpAuthError();
|
|
11549
11971
|
return callerKey;
|
|
11550
11972
|
}
|
|
11551
|
-
var mcpApp = new
|
|
11973
|
+
var mcpApp = new Hono9();
|
|
11552
11974
|
function registerSerpIntelligenceCaptureTools(server, executor) {
|
|
11553
11975
|
server.registerTool("capture_serp_snapshot", {
|
|
11554
11976
|
title: "SERP Intelligence Snapshot",
|
|
11555
|
-
description: "Capture a structured SERP Intelligence Google snapshot through POST /serp-intelligence/capture, the same product capture path used by Phoenix. Split query from location, infer gl/hl, use proxyMode location for localized US residential evidence; location mode creates fresh proxy IDs across retries and
|
|
11977
|
+
description: "Capture a structured SERP Intelligence Google snapshot through POST /serp-intelligence/capture, the same product capture path used by Phoenix. Split query from location, infer gl/hl, use proxyMode location for localized US residential evidence; location mode creates fresh proxy IDs across retries, briefly waits for browser-service automatic CAPTCHA/challenge solving when Google blocks a page, and rotates to a new proxy/session if the challenge does not clear or location evidence is wrong. Use configured only for the static residential proxy, and none only for direct-network debugging. Set debug true when investigating location evidence, proxy behavior, CAPTCHA, or capture reliability.",
|
|
11556
11978
|
inputSchema: CaptureSerpSnapshotInputSchema,
|
|
11557
11979
|
annotations: liveWebToolAnnotations("SERP Intelligence Snapshot")
|
|
11558
11980
|
}, async (input) => executor.captureSerpSnapshot(input));
|
|
@@ -11584,7 +12006,7 @@ mcpApp.all("/", async (c) => {
|
|
|
11584
12006
|
});
|
|
11585
12007
|
|
|
11586
12008
|
// src/api/browser-agent-routes.ts
|
|
11587
|
-
import { Hono as
|
|
12009
|
+
import { Hono as Hono10 } from "hono";
|
|
11588
12010
|
|
|
11589
12011
|
// src/api/browser-agent-db.ts
|
|
11590
12012
|
import { randomUUID } from "crypto";
|
|
@@ -12105,7 +12527,7 @@ async function loadOpenSession(id, userId) {
|
|
|
12105
12527
|
return row;
|
|
12106
12528
|
}
|
|
12107
12529
|
function buildBrowserAgentRoutes() {
|
|
12108
|
-
const app2 = new
|
|
12530
|
+
const app2 = new Hono10();
|
|
12109
12531
|
app2.use("*", async (c, next) => {
|
|
12110
12532
|
await migrateBrowserAgent();
|
|
12111
12533
|
return next();
|
|
@@ -12620,9 +13042,9 @@ if (state.key) { refreshSessions(); if (state.current) selectSession(state.curre
|
|
|
12620
13042
|
|
|
12621
13043
|
// src/api/stripe-routes.ts
|
|
12622
13044
|
import Stripe from "stripe";
|
|
12623
|
-
import { Hono as
|
|
13045
|
+
import { Hono as Hono11 } from "hono";
|
|
12624
13046
|
var stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
|
|
12625
|
-
var stripeApp = new
|
|
13047
|
+
var stripeApp = new Hono11();
|
|
12626
13048
|
stripeApp.post("/webhooks", async (c) => {
|
|
12627
13049
|
const sig = c.req.header("stripe-signature");
|
|
12628
13050
|
const body = await c.req.text();
|
|
@@ -12717,27 +13139,27 @@ import { getCookie, setCookie, deleteCookie } from "hono/cookie";
|
|
|
12717
13139
|
import Stripe2 from "stripe";
|
|
12718
13140
|
|
|
12719
13141
|
// src/api/billing-schemas.ts
|
|
12720
|
-
import { z as
|
|
12721
|
-
var BillingCheckoutBodySchema =
|
|
12722
|
-
priceId:
|
|
12723
|
-
});
|
|
12724
|
-
var FreeCreditBreakdownSchema =
|
|
12725
|
-
signup_grant_mc:
|
|
12726
|
-
monthly_refresh_mc:
|
|
12727
|
-
total_free_mc:
|
|
12728
|
-
signup_grant_credits:
|
|
12729
|
-
monthly_refresh_credits:
|
|
12730
|
-
total_free_credits:
|
|
12731
|
-
});
|
|
12732
|
-
var BillingBalanceResponseSchema =
|
|
12733
|
-
balance_mc:
|
|
12734
|
-
balance_credits:
|
|
13142
|
+
import { z as z18 } from "zod";
|
|
13143
|
+
var BillingCheckoutBodySchema = z18.object({
|
|
13144
|
+
priceId: z18.string().min(1)
|
|
13145
|
+
});
|
|
13146
|
+
var FreeCreditBreakdownSchema = z18.object({
|
|
13147
|
+
signup_grant_mc: z18.number().int().nonnegative(),
|
|
13148
|
+
monthly_refresh_mc: z18.number().int().nonnegative(),
|
|
13149
|
+
total_free_mc: z18.number().int().nonnegative(),
|
|
13150
|
+
signup_grant_credits: z18.number().nonnegative(),
|
|
13151
|
+
monthly_refresh_credits: z18.number().nonnegative(),
|
|
13152
|
+
total_free_credits: z18.number().nonnegative()
|
|
13153
|
+
});
|
|
13154
|
+
var BillingBalanceResponseSchema = z18.object({
|
|
13155
|
+
balance_mc: z18.number().int().nonnegative(),
|
|
13156
|
+
balance_credits: z18.number().nonnegative(),
|
|
12735
13157
|
free_credits: FreeCreditBreakdownSchema,
|
|
12736
|
-
ledger:
|
|
13158
|
+
ledger: z18.array(z18.any())
|
|
12737
13159
|
});
|
|
12738
|
-
var MonthlyRefreshSweepResultSchema =
|
|
12739
|
-
usersRefreshed:
|
|
12740
|
-
totalMcGranted:
|
|
13160
|
+
var MonthlyRefreshSweepResultSchema = z18.object({
|
|
13161
|
+
usersRefreshed: z18.number().int().nonnegative(),
|
|
13162
|
+
totalMcGranted: z18.number().int().nonnegative()
|
|
12741
13163
|
});
|
|
12742
13164
|
|
|
12743
13165
|
// src/api/credit-operations.ts
|
|
@@ -12838,7 +13260,7 @@ async function getFreeCreditBreakdown(userId) {
|
|
|
12838
13260
|
}
|
|
12839
13261
|
|
|
12840
13262
|
// src/api/session.ts
|
|
12841
|
-
import { createHmac, timingSafeEqual } from "crypto";
|
|
13263
|
+
import { createHmac as createHmac2, timingSafeEqual } from "crypto";
|
|
12842
13264
|
var isProduction = () => process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
|
|
12843
13265
|
function getSessionSecret() {
|
|
12844
13266
|
const configured = process.env.SESSION_SECRET?.trim();
|
|
@@ -12857,7 +13279,7 @@ function safeEqualHex(a, b) {
|
|
|
12857
13279
|
}
|
|
12858
13280
|
function signSession(userId) {
|
|
12859
13281
|
const payload = String(userId);
|
|
12860
|
-
const sig =
|
|
13282
|
+
const sig = createHmac2("sha256", secret()).update(payload).digest("hex");
|
|
12861
13283
|
return `${payload}.${sig}`;
|
|
12862
13284
|
}
|
|
12863
13285
|
function verifySession(token) {
|
|
@@ -12865,7 +13287,7 @@ function verifySession(token) {
|
|
|
12865
13287
|
if (dot === -1) return null;
|
|
12866
13288
|
const payload = token.slice(0, dot);
|
|
12867
13289
|
const sig = token.slice(dot + 1);
|
|
12868
|
-
const expected =
|
|
13290
|
+
const expected = createHmac2("sha256", secret()).update(payload).digest("hex");
|
|
12869
13291
|
if (!safeEqualHex(sig, expected)) return null;
|
|
12870
13292
|
const id = parseInt(payload);
|
|
12871
13293
|
return isNaN(id) ? null : id;
|
|
@@ -12948,7 +13370,7 @@ var sessionAuth = createMiddleware3(async (c, next) => {
|
|
|
12948
13370
|
c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
|
|
12949
13371
|
return next();
|
|
12950
13372
|
});
|
|
12951
|
-
var app = new
|
|
13373
|
+
var app = new Hono12();
|
|
12952
13374
|
var STRIPE_API_VERSION = "2026-02-25.clover";
|
|
12953
13375
|
function requireStripeSecret() {
|
|
12954
13376
|
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
@@ -13594,13 +14016,14 @@ app.get("/cron/tick", async (c) => {
|
|
|
13594
14016
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
13595
14017
|
return c.json({ error: "Unauthorized" }, 401);
|
|
13596
14018
|
}
|
|
13597
|
-
const { drainQueue } = await import("./worker-
|
|
14019
|
+
const { drainQueue } = await import("./worker-TDJQ6TH3.js");
|
|
13598
14020
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
14021
|
+
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
13599
14022
|
const [results, sweepResult] = await Promise.all([
|
|
13600
14023
|
drainQueue(budget),
|
|
13601
14024
|
runMonthlyRefreshSweep()
|
|
13602
14025
|
]);
|
|
13603
|
-
return c.json({ drained: results.length, results, sweepResult });
|
|
14026
|
+
return c.json({ drained: results.length, results, sweepResult, workflowDispatch: workflowDispatchResult });
|
|
13604
14027
|
});
|
|
13605
14028
|
app.on(["GET", "POST", "PUT"], "/api/inngest", serveInngest({ client: inngest, functions: [siteAuditFn] }));
|
|
13606
14029
|
app.route("/api/internal/site-architecture-auditor", siteAuditApp);
|
|
@@ -13609,6 +14032,7 @@ app.route("/screenshot", screenshotApp);
|
|
|
13609
14032
|
app.route("/facebook", facebookAdApp);
|
|
13610
14033
|
app.route("/maps", mapsApp);
|
|
13611
14034
|
app.route("/directory", directoryApp);
|
|
14035
|
+
app.route("/workflows", workflowApp);
|
|
13612
14036
|
app.route("/serp-intelligence", serpIntelligenceApp);
|
|
13613
14037
|
app.route("/mcp", mcpApp);
|
|
13614
14038
|
app.route("/agent", buildBrowserAgentRoutes());
|
|
@@ -13720,4 +14144,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
13720
14144
|
export {
|
|
13721
14145
|
app
|
|
13722
14146
|
};
|
|
13723
|
-
//# sourceMappingURL=server-
|
|
14147
|
+
//# sourceMappingURL=server-BKJ7LULQ.js.map
|