mcp-scraper 0.21.3 → 0.21.4
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 +728 -181
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- 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 +97 -32
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +5 -5
- package/dist/bin/paa-harvest.cjs +21 -0
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +3 -3
- package/dist/{chunk-JJ5RMEGX.js → chunk-E5J4HJBO.js} +2 -2
- package/dist/{chunk-H7APH5UG.js → chunk-ICT7DDHL.js} +366 -50
- package/dist/chunk-ICT7DDHL.js.map +1 -0
- package/dist/{chunk-OBNKOYOE.js → chunk-JHI373VB.js} +7 -5
- package/dist/chunk-JHI373VB.js.map +1 -0
- package/dist/{chunk-5IU5ZFVO.js → chunk-MTSBI7ZH.js} +23 -6
- package/dist/chunk-MTSBI7ZH.js.map +1 -0
- package/dist/{chunk-7LO6KLKD.js → chunk-N4JHXPW6.js} +89 -27
- package/dist/chunk-N4JHXPW6.js.map +1 -0
- package/dist/{chunk-XDRHBGJR.js → chunk-PNBYYAEK.js} +3 -3
- package/dist/{chunk-2P5VFIJM.js → chunk-SDHPQTFM.js} +2 -2
- package/dist/chunk-WQI7JIIN.js +7 -0
- package/dist/chunk-WQI7JIIN.js.map +1 -0
- package/dist/{db-Z35S224E.js → db-WRILVOFY.js} +4 -2
- package/dist/{extract-bundle-4RO3YU4Q.js → extract-bundle-NFFVMUMT.js} +2 -2
- package/dist/index.cjs +21 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/{server-PBBRSLBP.js → server-OLOOFN5W.js} +246 -100
- package/dist/server-OLOOFN5W.js.map +1 -0
- package/dist/{site-extract-repository-FDH6ZXKT.js → site-extract-repository-2QQSL2QC.js} +4 -4
- package/dist/{worker-BXJQ7J4N.js → worker-RV2TYXE3.js} +9 -8
- package/dist/worker-RV2TYXE3.js.map +1 -0
- package/docs/mcp-tool-manifest.generated.json +90 -11
- package/docs/specs/oauth-mcp-spec.md +2 -2
- package/package.json +1 -1
- package/dist/chunk-5IU5ZFVO.js.map +0 -1
- package/dist/chunk-7LO6KLKD.js.map +0 -1
- package/dist/chunk-H7APH5UG.js.map +0 -1
- package/dist/chunk-OBNKOYOE.js.map +0 -1
- package/dist/chunk-VYUWWIVR.js +0 -7
- package/dist/chunk-VYUWWIVR.js.map +0 -1
- package/dist/server-PBBRSLBP.js.map +0 -1
- package/dist/worker-BXJQ7J4N.js.map +0 -1
- /package/dist/{chunk-JJ5RMEGX.js.map → chunk-E5J4HJBO.js.map} +0 -0
- /package/dist/{chunk-XDRHBGJR.js.map → chunk-PNBYYAEK.js.map} +0 -0
- /package/dist/{chunk-2P5VFIJM.js.map → chunk-SDHPQTFM.js.map} +0 -0
- /package/dist/{db-Z35S224E.js.map → db-WRILVOFY.js.map} +0 -0
- /package/dist/{extract-bundle-4RO3YU4Q.js.map → extract-bundle-NFFVMUMT.js.map} +0 -0
- /package/dist/{site-extract-repository-FDH6ZXKT.js.map → site-extract-repository-2QQSL2QC.js.map} +0 -0
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
createHarvestAttemptRecorder,
|
|
7
7
|
harvestProblemResponse,
|
|
8
8
|
serializeHarvestProblem
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-SDHPQTFM.js";
|
|
10
10
|
import {
|
|
11
11
|
BrowserDriver,
|
|
12
12
|
MapsSelectors,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
deleteKernelProxyId,
|
|
15
15
|
harvest,
|
|
16
16
|
resolveKernelProxyId
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-E5J4HJBO.js";
|
|
18
18
|
import {
|
|
19
19
|
HttpMcpToolExecutor,
|
|
20
20
|
MemoryMcpToolExecutor,
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
sanitizeAttempts,
|
|
36
36
|
sanitizeHarvestResult,
|
|
37
37
|
transcribeMediaUrl
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-N4JHXPW6.js";
|
|
39
39
|
import {
|
|
40
40
|
auditImages,
|
|
41
41
|
buildLinkReport,
|
|
@@ -49,11 +49,10 @@ import {
|
|
|
49
49
|
browserServiceProxyId,
|
|
50
50
|
currentCostContext,
|
|
51
51
|
kernelCostUsd,
|
|
52
|
-
migrateCostTelemetry,
|
|
53
52
|
recordKernelSession,
|
|
54
53
|
recordVendorUsage,
|
|
55
54
|
runWithCostContext
|
|
56
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-MTSBI7ZH.js";
|
|
57
56
|
import {
|
|
58
57
|
csvRecords,
|
|
59
58
|
listWorkflowDefinitions,
|
|
@@ -77,7 +76,7 @@ import {
|
|
|
77
76
|
RawMapsOverviewSchema,
|
|
78
77
|
RawMapsReviewStatsSchema
|
|
79
78
|
} from "./chunk-XGIPATLV.js";
|
|
80
|
-
import "./chunk-
|
|
79
|
+
import "./chunk-WQI7JIIN.js";
|
|
81
80
|
import {
|
|
82
81
|
completeExtractJob,
|
|
83
82
|
countSuccessfulPages,
|
|
@@ -88,12 +87,11 @@ import {
|
|
|
88
87
|
saveExtractPages,
|
|
89
88
|
setExtractJobTotal,
|
|
90
89
|
settleExtractJob
|
|
91
|
-
} from "./chunk-
|
|
90
|
+
} from "./chunk-PNBYYAEK.js";
|
|
92
91
|
import {
|
|
93
92
|
BROWSER_OPEN_MIN_BALANCE_MC,
|
|
94
93
|
CONCURRENCY_PRICE_ID,
|
|
95
94
|
CREDIT_COST_CATALOG,
|
|
96
|
-
FREE_SIGNUP_MC,
|
|
97
95
|
LedgerOperation,
|
|
98
96
|
MC_COSTS,
|
|
99
97
|
MC_PER_CREDIT,
|
|
@@ -117,8 +115,9 @@ import {
|
|
|
117
115
|
concurrencySlotBillingInfo,
|
|
118
116
|
getPublicRatesPayload,
|
|
119
117
|
insufficientBalanceResponse,
|
|
118
|
+
serpActualCostMc,
|
|
120
119
|
vendorCostUsdToMc
|
|
121
|
-
} from "./chunk-
|
|
120
|
+
} from "./chunk-JHI373VB.js";
|
|
122
121
|
import {
|
|
123
122
|
CaptchaError,
|
|
124
123
|
RECAPTCHA_INSTRUCTIONS,
|
|
@@ -200,9 +199,10 @@ import {
|
|
|
200
199
|
setStripeCustomerId,
|
|
201
200
|
setSubscriptionTier,
|
|
202
201
|
setUserMemoryCreds,
|
|
202
|
+
settleDebitMcIdempotent,
|
|
203
203
|
upsertPageSnapshot,
|
|
204
204
|
verifyPassword
|
|
205
|
-
} from "./chunk-
|
|
205
|
+
} from "./chunk-ICT7DDHL.js";
|
|
206
206
|
|
|
207
207
|
// src/blog/registry.ts
|
|
208
208
|
var posts = [
|
|
@@ -7360,7 +7360,7 @@ var siteExtractFn = inngest.createFunction(
|
|
|
7360
7360
|
return buildSeoAudit(pages, job.startUrl);
|
|
7361
7361
|
}) : null;
|
|
7362
7362
|
const artifacts = await step.run("finalize", async () => {
|
|
7363
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
7363
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-NFFVMUMT.js");
|
|
7364
7364
|
const stored = await assembleExtractArtifacts(job, { branding, imageAudit, seoAudit });
|
|
7365
7365
|
await completeExtractJob(jobId, stored);
|
|
7366
7366
|
return stored;
|
|
@@ -11004,6 +11004,15 @@ function buildPageIntelUrl(body, country) {
|
|
|
11004
11004
|
if (body.pageId?.trim()) return `https://www.facebook.com/ads/library/?active_status=all&ad_type=all&country=${country}&is_targeted_country=false&media_type=all&search_type=page&view_all_page_id=${body.pageId.trim()}`;
|
|
11005
11005
|
return `https://www.facebook.com/ads/library/?active_status=all&ad_type=all&country=${country}&q=${encodeURIComponent(body.query.trim())}&search_type=keyword_unordered`;
|
|
11006
11006
|
}
|
|
11007
|
+
function pageIntelRequestContext(body) {
|
|
11008
|
+
const country = body.country?.trim().toUpperCase() ?? "US";
|
|
11009
|
+
return {
|
|
11010
|
+
country,
|
|
11011
|
+
maxAds: Math.min(200, Math.max(1, body.maxAds ?? 50)),
|
|
11012
|
+
listingUrl: buildPageIntelUrl(body, country),
|
|
11013
|
+
subject: body.pageId ?? body.query ?? body.libraryId ?? ""
|
|
11014
|
+
};
|
|
11015
|
+
}
|
|
11007
11016
|
function normalizeAdvertiserName(value) {
|
|
11008
11017
|
return value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, " ").trim().replace(/\s+/g, " ");
|
|
11009
11018
|
}
|
|
@@ -11117,16 +11126,14 @@ facebookAdApp.post("/page-intel", createApiKeyAuth(), async (c) => {
|
|
|
11117
11126
|
const parsed = FacebookPageIntelBodySchema.safeParse(raw);
|
|
11118
11127
|
if (!parsed.success) return c.json(invalidRequest(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
|
|
11119
11128
|
const body = parsed.data;
|
|
11120
|
-
const
|
|
11121
|
-
const country = body.country?.trim().toUpperCase() ?? "US";
|
|
11122
|
-
const listingUrl = buildPageIntelUrl(body, country);
|
|
11129
|
+
const { country, maxAds, listingUrl, subject } = pageIntelRequestContext(body);
|
|
11123
11130
|
const fbUser = c.get("user");
|
|
11124
11131
|
const gate = await acquireConcurrencyGate(fbUser, "facebook_page_intel", { reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"), metadata: { pageId: body.pageId, query: body.query, libraryId: body.libraryId, country } });
|
|
11125
11132
|
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
11126
11133
|
const driver = new BrowserDriver();
|
|
11127
11134
|
let refunded = false, debited = false;
|
|
11128
11135
|
try {
|
|
11129
|
-
const { ok: fbOk, balance_mc: fbBal } = await debitMc(fbUser.id, MC_COSTS.fb_ad, LedgerOperation.FB_AD,
|
|
11136
|
+
const { ok: fbOk, balance_mc: fbBal } = await debitMc(fbUser.id, MC_COSTS.fb_ad, LedgerOperation.FB_AD, subject);
|
|
11130
11137
|
if (!fbOk) return c.json(insufficientBalanceResponse(fbBal, MC_COSTS.fb_ad), 402);
|
|
11131
11138
|
debited = true;
|
|
11132
11139
|
await driver.launch(await kernelLaunchOptsResidential());
|
|
@@ -11136,17 +11143,17 @@ facebookAdApp.post("/page-intel", createApiKeyAuth(), async (c) => {
|
|
|
11136
11143
|
if (result.ads.length === 0 && await detectSoftBlock(driver)) {
|
|
11137
11144
|
await creditMc(fbUser.id, MC_COSTS.fb_ad, LedgerOperation.FB_AD_REFUND, "soft-block empty result");
|
|
11138
11145
|
refunded = true;
|
|
11139
|
-
await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "failed", query:
|
|
11146
|
+
await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "failed", query: subject, error: "soft-block: empty result refunded" });
|
|
11140
11147
|
return c.json({ error: "soft-block: no ads returned (refunded)" }, 503);
|
|
11141
11148
|
}
|
|
11142
11149
|
const match = assessPageIntelMatch(body, result);
|
|
11143
11150
|
const output = { ...result, ...match };
|
|
11144
|
-
await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "done", query:
|
|
11151
|
+
await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "done", query: subject, resultCount: result.ads.length, result: output });
|
|
11145
11152
|
return c.json(output);
|
|
11146
11153
|
} catch (err) {
|
|
11147
11154
|
const msg = err instanceof Error ? err.message : String(err);
|
|
11148
11155
|
if (debited && !refunded) await creditMc(fbUser.id, MC_COSTS.fb_ad, LedgerOperation.FB_AD_REFUND, "failed call");
|
|
11149
|
-
await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "failed", query:
|
|
11156
|
+
await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "failed", query: subject, error: msg });
|
|
11150
11157
|
if (msg.toLowerCase().includes("blocked") || msg.toLowerCase().includes("captcha")) {
|
|
11151
11158
|
return c.json({ error: msg }, 503);
|
|
11152
11159
|
}
|
|
@@ -15084,6 +15091,7 @@ workflowApp.post("/cron/dispatch", async (c) => {
|
|
|
15084
15091
|
|
|
15085
15092
|
// src/api/serp-intelligence-routes.ts
|
|
15086
15093
|
import { Hono as Hono16 } from "hono";
|
|
15094
|
+
import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
|
|
15087
15095
|
|
|
15088
15096
|
// src/serp-intelligence/page-snapshot-extractor.ts
|
|
15089
15097
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -15477,12 +15485,22 @@ var SerpIntelligencePageSnapshotRequestSchema = z24.object({
|
|
|
15477
15485
|
sourcePosition: z24.number().int().min(1).optional()
|
|
15478
15486
|
}).strict();
|
|
15479
15487
|
var SerpIntelligencePageSnapshotsBodySchema = z24.object({
|
|
15480
|
-
urls: z24.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25),
|
|
15488
|
+
urls: z24.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25).optional(),
|
|
15481
15489
|
targets: z24.array(SerpIntelligencePageSnapshotRequestSchema).min(1).max(25).optional(),
|
|
15482
15490
|
maxConcurrency: z24.number().int().min(1).max(5).default(2),
|
|
15483
15491
|
timeoutMs: z24.number().int().min(1e3).max(6e4).default(15e3),
|
|
15484
15492
|
debug: z24.boolean().default(false)
|
|
15485
|
-
}).strict()
|
|
15493
|
+
}).strict().superRefine((body, ctx) => {
|
|
15494
|
+
const hasUrls = Boolean(body.urls?.length);
|
|
15495
|
+
const hasTargets = Boolean(body.targets?.length);
|
|
15496
|
+
if (hasUrls === hasTargets) {
|
|
15497
|
+
ctx.addIssue({
|
|
15498
|
+
code: z24.ZodIssueCode.custom,
|
|
15499
|
+
message: "provide exactly one of urls or targets",
|
|
15500
|
+
path: hasUrls ? ["targets"] : ["urls"]
|
|
15501
|
+
});
|
|
15502
|
+
}
|
|
15503
|
+
});
|
|
15486
15504
|
var SerpIntelligenceAICitationSchema = z24.object({
|
|
15487
15505
|
text: z24.string(),
|
|
15488
15506
|
href: z24.string()
|
|
@@ -15769,14 +15787,24 @@ async function captureSerpIntelligenceSnapshot(rawInput, runtimeOptions = {}) {
|
|
|
15769
15787
|
});
|
|
15770
15788
|
const pageSnapshotLimit = normalizePageSnapshotLimit(parsedInput);
|
|
15771
15789
|
const pageSnapshotTargets = collectPageSnapshotTargets(harvestResult, pageSnapshotLimit);
|
|
15772
|
-
const pageSnapshotArtifacts = pageSnapshotTargets.length > 0 ? (await
|
|
15773
|
-
|
|
15774
|
-
|
|
15775
|
-
|
|
15776
|
-
|
|
15777
|
-
|
|
15778
|
-
|
|
15779
|
-
|
|
15790
|
+
const pageSnapshotArtifacts = pageSnapshotTargets.length > 0 ? (await runWithCostContext(
|
|
15791
|
+
{
|
|
15792
|
+
...currentCostContext() ?? { op: "serp" },
|
|
15793
|
+
subOp: "page_snapshot",
|
|
15794
|
+
// Page snapshot launches have their own fixed product rate. They
|
|
15795
|
+
// must not overwrite the headless/headful mode used to settle the
|
|
15796
|
+
// preceding SERP harvest.
|
|
15797
|
+
headlessSentOut: void 0
|
|
15798
|
+
},
|
|
15799
|
+
() => capturePageSnapshotsFn(pageSnapshotTargets, {
|
|
15800
|
+
kernelApiKey: runtimeOptions.kernelApiKey ?? browserServiceApiKey(),
|
|
15801
|
+
timeoutMs: runtimeOptions.pageSnapshotTimeoutMs,
|
|
15802
|
+
maxConcurrency: runtimeOptions.pageSnapshotMaxConcurrency,
|
|
15803
|
+
debug,
|
|
15804
|
+
includeContent: runtimeOptions.includePageSnapshotContent,
|
|
15805
|
+
maxContentChars: runtimeOptions.maxPageSnapshotContentChars
|
|
15806
|
+
})
|
|
15807
|
+
)).pageSnapshotArtifacts.map(stripPageSnapshotArtifact) : [];
|
|
15780
15808
|
return normalizeHarvestResultForSerpIntelligence({
|
|
15781
15809
|
harvestResult,
|
|
15782
15810
|
attempts,
|
|
@@ -15793,6 +15821,7 @@ var SERP_INTELLIGENCE_RATE_LIMIT = 60;
|
|
|
15793
15821
|
var SERP_INTELLIGENCE_RATE_WINDOW_SECONDS = 60;
|
|
15794
15822
|
var POST_CAPTURE_ROUTE_LABEL = "POST /capture";
|
|
15795
15823
|
var POST_PAGE_SNAPSHOTS_ROUTE_LABEL = "POST /page-snapshots";
|
|
15824
|
+
var SERP_CAPTURE_BILLING_SOURCE = "serp_intelligence_capture";
|
|
15796
15825
|
var serpIntelligenceApp = new Hono16();
|
|
15797
15826
|
serpIntelligenceApp.use("*", createApiKeyAuth());
|
|
15798
15827
|
function structuredError(input) {
|
|
@@ -15832,18 +15861,46 @@ async function enforceSerpIntelligenceRateLimit(c, userId) {
|
|
|
15832
15861
|
);
|
|
15833
15862
|
}
|
|
15834
15863
|
function pageSnapshotTargetsFromBody(body) {
|
|
15835
|
-
return body.targets?.length ? body.targets : body.urls.map((url) => ({
|
|
15864
|
+
return body.targets?.length ? body.targets : (body.urls ?? []).map((url) => ({
|
|
15836
15865
|
url,
|
|
15837
15866
|
sourceKind: "configured_target",
|
|
15838
15867
|
sourcePosition: null
|
|
15839
15868
|
}));
|
|
15840
15869
|
}
|
|
15870
|
+
async function logRequestEventBestEffort(input) {
|
|
15871
|
+
try {
|
|
15872
|
+
await logRequestEvent(input);
|
|
15873
|
+
} catch (error) {
|
|
15874
|
+
console.warn("[serp-intelligence] request audit log failed:", error instanceof Error ? error.message : String(error));
|
|
15875
|
+
}
|
|
15876
|
+
}
|
|
15877
|
+
function captureBillingKeys(userId, suppliedKey, body) {
|
|
15878
|
+
const responseKey = suppliedKey?.trim() || randomUUID3();
|
|
15879
|
+
const requestFingerprint = createHash4("sha256").update(JSON.stringify(body)).digest("hex");
|
|
15880
|
+
const keyDigest = createHash4("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
|
|
15881
|
+
return {
|
|
15882
|
+
responseKey,
|
|
15883
|
+
debitKey: `serp-capture:${userId}:${keyDigest}`,
|
|
15884
|
+
debitDescription: `${body.query} [request:${requestFingerprint}]`
|
|
15885
|
+
};
|
|
15886
|
+
}
|
|
15841
15887
|
serpIntelligenceApp.post("/capture", async (c) => {
|
|
15842
15888
|
void POST_CAPTURE_ROUTE_LABEL;
|
|
15843
15889
|
const user = c.get("user");
|
|
15844
15890
|
const raw = await c.req.json().catch(() => ({}));
|
|
15845
15891
|
const parsed = SerpIntelligenceCaptureBodySchema.safeParse(raw);
|
|
15846
15892
|
if (!parsed.success) return c.json(formatZodError(parsed.error), 400);
|
|
15893
|
+
const suppliedIdempotencyKey = c.req.header("idempotency-key") ?? c.req.header("x-idempotency-key");
|
|
15894
|
+
if (suppliedIdempotencyKey && suppliedIdempotencyKey.trim().length > 500) {
|
|
15895
|
+
return c.json(structuredError({
|
|
15896
|
+
error_code: "invalid_request",
|
|
15897
|
+
error_type: "validation_error",
|
|
15898
|
+
message: "Idempotency-Key must be 500 characters or fewer.",
|
|
15899
|
+
retryable: false
|
|
15900
|
+
}), 400);
|
|
15901
|
+
}
|
|
15902
|
+
const billingKeys = captureBillingKeys(user.id, suppliedIdempotencyKey, parsed.data);
|
|
15903
|
+
c.header("Idempotency-Key", billingKeys.responseKey);
|
|
15847
15904
|
const limited = await enforceSerpIntelligenceRateLimit(c, user.id);
|
|
15848
15905
|
if (limited) return limited;
|
|
15849
15906
|
const gate = await acquireConcurrencyGate(user, "serp_intelligence_capture", {
|
|
@@ -15851,51 +15908,130 @@ serpIntelligenceApp.post("/capture", async (c) => {
|
|
|
15851
15908
|
metadata: { query: parsed.data.query, location: parsed.data.location }
|
|
15852
15909
|
});
|
|
15853
15910
|
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
15854
|
-
const
|
|
15855
|
-
|
|
15911
|
+
const pageSnapshotHold = parsed.data.includePageSnapshots ? parsed.data.pageSnapshotLimit * MC_COSTS.page_scrape : 0;
|
|
15912
|
+
const cost = MC_COSTS.serp_headful + pageSnapshotHold;
|
|
15913
|
+
const headlessSentOut = { value: null };
|
|
15856
15914
|
try {
|
|
15857
|
-
const { ok, balance_mc } = await
|
|
15915
|
+
const { ok, balance_mc } = await debitMcIdempotent(
|
|
15858
15916
|
user.id,
|
|
15859
15917
|
cost,
|
|
15860
15918
|
LedgerOperation.SERP,
|
|
15861
|
-
|
|
15919
|
+
billingKeys.debitDescription,
|
|
15920
|
+
billingKeys.debitKey
|
|
15862
15921
|
);
|
|
15863
|
-
if (!ok)
|
|
15864
|
-
|
|
15865
|
-
|
|
15866
|
-
|
|
15867
|
-
|
|
15868
|
-
|
|
15869
|
-
|
|
15922
|
+
if (!ok) {
|
|
15923
|
+
await releaseConcurrencyGate(gate.lockId);
|
|
15924
|
+
return c.json(insufficientBalanceResponse(balance_mc, cost), 402);
|
|
15925
|
+
}
|
|
15926
|
+
} catch (error) {
|
|
15927
|
+
const idempotencyConflict = error instanceof Error && error.message === "idempotent debit key conflicts with another debit";
|
|
15928
|
+
const body = structuredError({
|
|
15929
|
+
error_code: idempotencyConflict ? "idempotency_conflict" : "capture_failed",
|
|
15930
|
+
error_type: idempotencyConflict ? "validation_error" : "capture_error",
|
|
15931
|
+
message: error instanceof Error ? error.message : String(error),
|
|
15932
|
+
retryable: !idempotencyConflict
|
|
15870
15933
|
});
|
|
15871
|
-
await
|
|
15934
|
+
await logRequestEventBestEffort({
|
|
15872
15935
|
userId: user.id,
|
|
15873
|
-
source:
|
|
15874
|
-
status: "
|
|
15936
|
+
source: SERP_CAPTURE_BILLING_SOURCE,
|
|
15937
|
+
status: "failed",
|
|
15875
15938
|
query: parsed.data.query,
|
|
15876
15939
|
location: parsed.data.location,
|
|
15877
|
-
|
|
15878
|
-
result
|
|
15940
|
+
error: body.message
|
|
15879
15941
|
});
|
|
15880
|
-
|
|
15942
|
+
await releaseConcurrencyGate(gate.lockId);
|
|
15943
|
+
return c.json(body, idempotencyConflict ? 409 : 500);
|
|
15944
|
+
}
|
|
15945
|
+
let result;
|
|
15946
|
+
try {
|
|
15947
|
+
const parentCostContext = currentCostContext();
|
|
15948
|
+
result = await runWithCostContext(
|
|
15949
|
+
{
|
|
15950
|
+
...parentCostContext ?? {},
|
|
15951
|
+
op: "serp",
|
|
15952
|
+
userId: Number(user.id),
|
|
15953
|
+
headlessSentOut
|
|
15954
|
+
},
|
|
15955
|
+
() => captureSerpIntelligenceSnapshot(parsed.data, {
|
|
15956
|
+
kernelApiKey: browserServiceApiKey(),
|
|
15957
|
+
kernelProxyId: browserServiceProxyId(),
|
|
15958
|
+
signal: c.req.raw.signal
|
|
15959
|
+
})
|
|
15960
|
+
);
|
|
15881
15961
|
} catch (error) {
|
|
15882
|
-
|
|
15962
|
+
try {
|
|
15963
|
+
await settleDebitMcIdempotent(
|
|
15964
|
+
user.id,
|
|
15965
|
+
billingKeys.debitKey,
|
|
15966
|
+
0,
|
|
15967
|
+
LedgerOperation.SERP_REFUND,
|
|
15968
|
+
"failed serp intelligence capture",
|
|
15969
|
+
SERP_CAPTURE_BILLING_SOURCE
|
|
15970
|
+
);
|
|
15971
|
+
} catch (settlementError) {
|
|
15972
|
+
console.warn(
|
|
15973
|
+
"[serp-intelligence] failed capture refund remains recoverable:",
|
|
15974
|
+
settlementError instanceof Error ? settlementError.message : String(settlementError)
|
|
15975
|
+
);
|
|
15976
|
+
}
|
|
15883
15977
|
const body = error instanceof SerpIntelligenceCaptureError ? error.toJSON() : structuredError({
|
|
15884
15978
|
error_code: "capture_failed",
|
|
15885
15979
|
error_type: "capture_error",
|
|
15886
15980
|
message: error instanceof Error ? error.message : String(error),
|
|
15887
15981
|
retryable: true
|
|
15888
15982
|
});
|
|
15889
|
-
await
|
|
15983
|
+
await logRequestEventBestEffort({
|
|
15890
15984
|
userId: user.id,
|
|
15891
|
-
source:
|
|
15985
|
+
source: SERP_CAPTURE_BILLING_SOURCE,
|
|
15892
15986
|
status: "failed",
|
|
15893
15987
|
query: parsed.data.query,
|
|
15894
15988
|
location: parsed.data.location,
|
|
15895
15989
|
error: "message" in body && typeof body.message === "string" ? body.message : "SERP Intelligence capture failed"
|
|
15896
15990
|
});
|
|
15897
15991
|
const status = error instanceof SerpIntelligenceCaptureError ? error.httpStatus : 500;
|
|
15992
|
+
await releaseConcurrencyGate(gate.lockId);
|
|
15898
15993
|
return c.json(body, status);
|
|
15994
|
+
}
|
|
15995
|
+
try {
|
|
15996
|
+
const requestedActualCost = serpActualCostMc(headlessSentOut.value) + result.pageSnapshotArtifacts.length * MC_COSTS.page_scrape;
|
|
15997
|
+
const settlement = await settleDebitMcIdempotent(
|
|
15998
|
+
user.id,
|
|
15999
|
+
billingKeys.debitKey,
|
|
16000
|
+
requestedActualCost,
|
|
16001
|
+
LedgerOperation.SERP_REFUND,
|
|
16002
|
+
"SERP capture pricing settle",
|
|
16003
|
+
SERP_CAPTURE_BILLING_SOURCE
|
|
16004
|
+
);
|
|
16005
|
+
const settledResult = {
|
|
16006
|
+
...result,
|
|
16007
|
+
billing: { creditsUsed: settlement.final_amount_mc / MC_PER_CREDIT }
|
|
16008
|
+
};
|
|
16009
|
+
await logRequestEventBestEffort({
|
|
16010
|
+
userId: user.id,
|
|
16011
|
+
source: SERP_CAPTURE_BILLING_SOURCE,
|
|
16012
|
+
status: "done",
|
|
16013
|
+
query: parsed.data.query,
|
|
16014
|
+
location: parsed.data.location,
|
|
16015
|
+
resultCount: settledResult.harvestResult.organicResults.length,
|
|
16016
|
+
result: settledResult
|
|
16017
|
+
});
|
|
16018
|
+
return c.json(settledResult);
|
|
16019
|
+
} catch (error) {
|
|
16020
|
+
const body = structuredError({
|
|
16021
|
+
error_code: "capture_failed",
|
|
16022
|
+
error_type: "capture_error",
|
|
16023
|
+
message: error instanceof Error ? error.message : String(error),
|
|
16024
|
+
retryable: true
|
|
16025
|
+
});
|
|
16026
|
+
await logRequestEventBestEffort({
|
|
16027
|
+
userId: user.id,
|
|
16028
|
+
source: SERP_CAPTURE_BILLING_SOURCE,
|
|
16029
|
+
status: "failed",
|
|
16030
|
+
query: parsed.data.query,
|
|
16031
|
+
location: parsed.data.location,
|
|
16032
|
+
error: body.message
|
|
16033
|
+
});
|
|
16034
|
+
return c.json(body, 500);
|
|
15899
16035
|
} finally {
|
|
15900
16036
|
await releaseConcurrencyGate(gate.lockId);
|
|
15901
16037
|
}
|
|
@@ -15931,7 +16067,7 @@ serpIntelligenceApp.post("/page-snapshots", async (c) => {
|
|
|
15931
16067
|
maxConcurrency: parsed.data.maxConcurrency,
|
|
15932
16068
|
debug: parsed.data.debug
|
|
15933
16069
|
});
|
|
15934
|
-
await
|
|
16070
|
+
await logRequestEventBestEffort({
|
|
15935
16071
|
userId: user.id,
|
|
15936
16072
|
source: "serp_intelligence_page_snapshots",
|
|
15937
16073
|
status: "done",
|
|
@@ -15948,7 +16084,7 @@ serpIntelligenceApp.post("/page-snapshots", async (c) => {
|
|
|
15948
16084
|
message: error instanceof Error ? error.message : String(error),
|
|
15949
16085
|
retryable: true
|
|
15950
16086
|
});
|
|
15951
|
-
await
|
|
16087
|
+
await logRequestEventBestEffort({
|
|
15952
16088
|
userId: user.id,
|
|
15953
16089
|
source: "serp_intelligence_page_snapshots",
|
|
15954
16090
|
status: "failed",
|
|
@@ -16593,15 +16729,42 @@ mcpApp.all("/", async (c) => {
|
|
|
16593
16729
|
});
|
|
16594
16730
|
|
|
16595
16731
|
// src/api/browser-agent-routes.ts
|
|
16596
|
-
import { randomUUID as
|
|
16732
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
16597
16733
|
import { Hono as Hono18 } from "hono";
|
|
16598
16734
|
|
|
16599
16735
|
// src/api/browser-agent-db.ts
|
|
16600
|
-
import { randomUUID as
|
|
16736
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
16601
16737
|
var _ready = false;
|
|
16738
|
+
var _migrationPromise = null;
|
|
16602
16739
|
async function migrateBrowserAgent() {
|
|
16603
16740
|
if (_ready) return;
|
|
16741
|
+
if (_migrationPromise) return _migrationPromise;
|
|
16742
|
+
_migrationPromise = runBrowserAgentMigration().finally(() => {
|
|
16743
|
+
_migrationPromise = null;
|
|
16744
|
+
});
|
|
16745
|
+
return _migrationPromise;
|
|
16746
|
+
}
|
|
16747
|
+
async function runBrowserAgentMigration() {
|
|
16604
16748
|
const db = getDb();
|
|
16749
|
+
const existing = await db.execute(`
|
|
16750
|
+
SELECT
|
|
16751
|
+
(SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name IN (
|
|
16752
|
+
'browser_agent_sessions',
|
|
16753
|
+
'browser_agent_actions',
|
|
16754
|
+
'browser_agent_replays',
|
|
16755
|
+
'browser_auth_connections',
|
|
16756
|
+
'browser_agent_extensions'
|
|
16757
|
+
)) = 5
|
|
16758
|
+
AND (SELECT COUNT(*) FROM pragma_table_info('browser_agent_sessions') WHERE name = 'concurrency_lock_id') = 1
|
|
16759
|
+
AND (SELECT COUNT(*) FROM pragma_table_info('browser_auth_connections') WHERE name IN (
|
|
16760
|
+
'account_email', 'note', 'updated_at', 'status', 'browser_agent_session_id'
|
|
16761
|
+
)) = 5
|
|
16762
|
+
AS ready
|
|
16763
|
+
`);
|
|
16764
|
+
if (Number(existing.rows[0]?.ready ?? 0) === 1) {
|
|
16765
|
+
_ready = true;
|
|
16766
|
+
return;
|
|
16767
|
+
}
|
|
16605
16768
|
await db.execute(`
|
|
16606
16769
|
CREATE TABLE IF NOT EXISTS browser_agent_sessions (
|
|
16607
16770
|
id TEXT PRIMARY KEY,
|
|
@@ -16701,7 +16864,7 @@ async function migrateBrowserAgent() {
|
|
|
16701
16864
|
}
|
|
16702
16865
|
async function createExtensionRow(input) {
|
|
16703
16866
|
const db = getDb();
|
|
16704
|
-
const id = `bext_${
|
|
16867
|
+
const id = `bext_${randomUUID4().replace(/-/g, "").slice(0, 20)}`;
|
|
16705
16868
|
await db.execute({
|
|
16706
16869
|
sql: `INSERT INTO browser_agent_extensions (id, user_id, name, backend_id, backend_name, source, source_url, size_bytes)
|
|
16707
16870
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -16736,7 +16899,7 @@ async function deleteExtensionRow(userId, name) {
|
|
|
16736
16899
|
}
|
|
16737
16900
|
async function createAuthConnectionRow(input) {
|
|
16738
16901
|
const db = getDb();
|
|
16739
|
-
const connectionId = `authc_${
|
|
16902
|
+
const connectionId = `authc_${randomUUID4().replace(/-/g, "").slice(0, 20)}`;
|
|
16740
16903
|
await db.execute({
|
|
16741
16904
|
sql: `INSERT INTO browser_auth_connections (connection_id, domain, profile, account_email, note, status, browser_agent_session_id)
|
|
16742
16905
|
VALUES (?, ?, ?, ?, ?, 'NEEDS_AUTH', ?)`,
|
|
@@ -16770,7 +16933,7 @@ async function listAuthConnectionsByProfile(profile) {
|
|
|
16770
16933
|
}
|
|
16771
16934
|
async function createSessionRow(input) {
|
|
16772
16935
|
const db = getDb();
|
|
16773
|
-
const id = `bas_${
|
|
16936
|
+
const id = `bas_${randomUUID4().replace(/-/g, "").slice(0, 20)}`;
|
|
16774
16937
|
await db.execute({
|
|
16775
16938
|
sql: `INSERT INTO browser_agent_sessions (id, runtime_session_id, live_view_url, cdp_ws_url, status, label, user_id, concurrency_lock_id, last_action_at)
|
|
16776
16939
|
VALUES (?, ?, ?, ?, 'open', ?, ?, ?, datetime('now'))`,
|
|
@@ -16839,7 +17002,7 @@ async function recordAction(input) {
|
|
|
16839
17002
|
sql: `INSERT INTO browser_agent_actions (id, session_id, type, params_json, ok, error)
|
|
16840
17003
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
16841
17004
|
args: [
|
|
16842
|
-
`baa_${
|
|
17005
|
+
`baa_${randomUUID4().replace(/-/g, "").slice(0, 20)}`,
|
|
16843
17006
|
input.sessionId,
|
|
16844
17007
|
input.type,
|
|
16845
17008
|
input.params == null ? null : JSON.stringify(input.params),
|
|
@@ -18527,7 +18690,7 @@ function buildBrowserAgentRoutes() {
|
|
|
18527
18690
|
}
|
|
18528
18691
|
const existing = await getExtensionRow(user.id, name);
|
|
18529
18692
|
if (existing) return c.json({ error: `an extension named "${name}" already exists \u2014 delete it first or pick another name` }, 409);
|
|
18530
|
-
const backendName = `u${user.id}_${
|
|
18693
|
+
const backendName = `u${user.id}_${randomUUID5().replace(/-/g, "")}`;
|
|
18531
18694
|
try {
|
|
18532
18695
|
const imported = await importExtensionFromStore(storeUrl, backendName);
|
|
18533
18696
|
const row = await createExtensionRow({
|
|
@@ -18957,7 +19120,7 @@ async function resolveUser(customerId, emailFallback) {
|
|
|
18957
19120
|
// src/api/oauth-routes.ts
|
|
18958
19121
|
import { Hono as Hono20 } from "hono";
|
|
18959
19122
|
import { getCookie, setCookie } from "hono/cookie";
|
|
18960
|
-
import { createHash as
|
|
19123
|
+
import { createHash as createHash5, randomBytes as randomBytes2, randomUUID as randomUUID6 } from "crypto";
|
|
18961
19124
|
import { importPKCS8, exportJWK, SignJWT } from "jose";
|
|
18962
19125
|
var ISSUER = "https://mcpscraper.dev";
|
|
18963
19126
|
var RESOURCE = () => process.env.MCP_MEMORY_RESOURCE_URL ?? "https://memory.mcpscraper.dev/mcp";
|
|
@@ -18990,7 +19153,7 @@ async function getKeys() {
|
|
|
18990
19153
|
const privateKey = await importPKCS8(pem, "RS256", { extractable: true });
|
|
18991
19154
|
const full = await exportJWK(privateKey);
|
|
18992
19155
|
const publicJwk = { kty: full.kty, n: full.n, e: full.e };
|
|
18993
|
-
const kid =
|
|
19156
|
+
const kid = createHash5("sha256").update(JSON.stringify({ e: publicJwk.e, kty: publicJwk.kty, n: publicJwk.n })).digest("base64url").slice(0, 16);
|
|
18994
19157
|
publicJwk.kid = kid;
|
|
18995
19158
|
publicJwk.alg = "RS256";
|
|
18996
19159
|
publicJwk.use = "sig";
|
|
@@ -19300,12 +19463,12 @@ oauthApp.post("/oauth/authorize", async (c) => {
|
|
|
19300
19463
|
});
|
|
19301
19464
|
function pkceMatches(verifier, challenge) {
|
|
19302
19465
|
if (!verifier) return false;
|
|
19303
|
-
const computed =
|
|
19466
|
+
const computed = createHash5("sha256").update(verifier).digest("base64url");
|
|
19304
19467
|
return computed === challenge;
|
|
19305
19468
|
}
|
|
19306
19469
|
async function mintAccessToken(identity, scope, plan, audience) {
|
|
19307
19470
|
const { privateKey, kid } = await getKeys();
|
|
19308
|
-
return new SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti(
|
|
19471
|
+
return new SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti(randomUUID6()).setExpirationTime(`${ACCESS_TTL_SECONDS}s`).sign(privateKey);
|
|
19309
19472
|
}
|
|
19310
19473
|
function tokenErrorResponse(c, error, description, status) {
|
|
19311
19474
|
return c.json({ error, error_description: description }, status);
|
|
@@ -20651,13 +20814,13 @@ import { getCookie as getCookie2, setCookie as setCookie2, deleteCookie } from "
|
|
|
20651
20814
|
import Stripe2 from "stripe";
|
|
20652
20815
|
|
|
20653
20816
|
// src/api/page-diff.ts
|
|
20654
|
-
import { createHash as
|
|
20817
|
+
import { createHash as createHash6 } from "crypto";
|
|
20655
20818
|
import { diffLines } from "diff";
|
|
20656
20819
|
var MAX_SNAPSHOT_CONTENT_CHARS = 25e4;
|
|
20657
20820
|
var MAX_DIFF_HUNKS = 200;
|
|
20658
20821
|
var MAX_DIFF_LINES_PER_RESPONSE = 2e3;
|
|
20659
20822
|
function sha256Hex(value) {
|
|
20660
|
-
return
|
|
20823
|
+
return createHash6("sha256").update(value).digest("hex");
|
|
20661
20824
|
}
|
|
20662
20825
|
function truncateForStorage(value, maxChars = MAX_SNAPSHOT_CONTENT_CHARS) {
|
|
20663
20826
|
if (value.length <= maxChars) return { value, truncated: false };
|
|
@@ -20764,7 +20927,7 @@ async function persistScrapeBody(user, opts) {
|
|
|
20764
20927
|
}
|
|
20765
20928
|
|
|
20766
20929
|
// src/api/connection-memory-import.ts
|
|
20767
|
-
import { createHash as
|
|
20930
|
+
import { createHash as createHash7 } from "crypto";
|
|
20768
20931
|
var CONNECTION_MEMORY_IMPORT_MAX_ARGS_BYTES = 64 * 1024;
|
|
20769
20932
|
var CONNECTION_MEMORY_IMPORT_MAX_RESULT_BYTES = 1e6;
|
|
20770
20933
|
var CONNECTION_MEMORY_IMPORT_MAX_STRING_CHARS = 256e3;
|
|
@@ -20794,7 +20957,7 @@ function canonicalJson(value) {
|
|
|
20794
20957
|
return JSON.stringify(value);
|
|
20795
20958
|
}
|
|
20796
20959
|
function sha2562(value) {
|
|
20797
|
-
return
|
|
20960
|
+
return createHash7("sha256").update(value).digest("hex");
|
|
20798
20961
|
}
|
|
20799
20962
|
function sensitiveKey(key) {
|
|
20800
20963
|
const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
@@ -21063,7 +21226,7 @@ async function cleanupExpiredScrapeBlobs(maxAgeMs = SCRAPE_BLOB_TTL_MS) {
|
|
|
21063
21226
|
}
|
|
21064
21227
|
|
|
21065
21228
|
// src/api/connected-data-export.ts
|
|
21066
|
-
import { randomUUID as
|
|
21229
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
21067
21230
|
var CONNECTED_DATA_INLINE_BUDGET_BYTES = Number(
|
|
21068
21231
|
process.env.MCP_SCRAPER_CONNECTED_DATA_INLINE_BUDGET_BYTES ?? 5e4
|
|
21069
21232
|
);
|
|
@@ -21188,7 +21351,7 @@ function finitePositive(value, fallback) {
|
|
|
21188
21351
|
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
21189
21352
|
}
|
|
21190
21353
|
async function collectConnectedDataExport(args) {
|
|
21191
|
-
const exportId =
|
|
21354
|
+
const exportId = randomUUID7();
|
|
21192
21355
|
const now = args.now ?? Date.now;
|
|
21193
21356
|
const startedAt = now();
|
|
21194
21357
|
const budgetMs = finitePositive(CONNECTED_DATA_EXPORT_BUDGET_MS, 24e4);
|
|
@@ -21300,12 +21463,6 @@ ${lines.length ? `${lines.join("\n")}
|
|
|
21300
21463
|
}
|
|
21301
21464
|
|
|
21302
21465
|
// src/api/credit-operations.ts
|
|
21303
|
-
async function grantSignupCredit(userId) {
|
|
21304
|
-
if (!FREE_SIGNUP_MC || FREE_SIGNUP_MC <= 0) return;
|
|
21305
|
-
const already = await ledgerExistsForOperation(userId, LedgerOperation.SIGNUP_GRANT);
|
|
21306
|
-
if (already) return;
|
|
21307
|
-
await creditMc(userId, FREE_SIGNUP_MC, LedgerOperation.SIGNUP_GRANT, "Welcome credits");
|
|
21308
|
-
}
|
|
21309
21466
|
async function applyMonthlyFreeRefresh(user) {
|
|
21310
21467
|
return user;
|
|
21311
21468
|
}
|
|
@@ -21517,7 +21674,7 @@ async function dbUsage(identity, plan) {
|
|
|
21517
21674
|
}
|
|
21518
21675
|
|
|
21519
21676
|
// src/api/nango-control.ts
|
|
21520
|
-
import { createHash as
|
|
21677
|
+
import { createHash as createHash8 } from "crypto";
|
|
21521
21678
|
var DEFAULT_NANGO_CONTROL_URL = "https://mcp-scraper-scheduler.vercel.app";
|
|
21522
21679
|
var DISABLED_NANGO_TOOLS = {
|
|
21523
21680
|
"google-mail": /* @__PURE__ */ new Set(["list-filters"]),
|
|
@@ -22116,7 +22273,7 @@ function canonicalJson2(value) {
|
|
|
22116
22273
|
return JSON.stringify(value);
|
|
22117
22274
|
}
|
|
22118
22275
|
function projectedToolSchemaHash(tool) {
|
|
22119
|
-
return
|
|
22276
|
+
return createHash8("sha256").update(canonicalJson2(tool)).digest("hex");
|
|
22120
22277
|
}
|
|
22121
22278
|
async function describeNangoTool(identity, connectionId, tool, fresh) {
|
|
22122
22279
|
const body = await controlRequest("/api/internal/nango/connections/actions/describe", {
|
|
@@ -22717,7 +22874,6 @@ app.use("*", (c, next) => {
|
|
|
22717
22874
|
const forceHeadless = c.req.header("x-kernel-headless") === "true";
|
|
22718
22875
|
return runWithCostContext({ op, probeRunId, forceHeadless }, () => next());
|
|
22719
22876
|
});
|
|
22720
|
-
void migrateCostTelemetry();
|
|
22721
22877
|
app.post("/auth/register", requireAllowedOrigin, async (c) => {
|
|
22722
22878
|
const { email, password } = await c.req.json();
|
|
22723
22879
|
const normalizedEmail = email?.trim().toLowerCase();
|
|
@@ -22748,7 +22904,6 @@ app.post("/auth/register", requireAllowedOrigin, async (c) => {
|
|
|
22748
22904
|
}
|
|
22749
22905
|
}
|
|
22750
22906
|
const newKey = await rotateApiKey(user.id);
|
|
22751
|
-
await grantSignupCredit(user.id);
|
|
22752
22907
|
void (async () => {
|
|
22753
22908
|
try {
|
|
22754
22909
|
const full = await getUserById(user.id);
|
|
@@ -23997,7 +24152,7 @@ app.post("/harvest/sync", auth2, async (c) => {
|
|
|
23997
24152
|
if (diff > 0) await creditMc(user.id, diff, LedgerOperation.PAA_REFUND, "overestimate refund");
|
|
23998
24153
|
else if (diff < 0) await debitMc(user.id, -diff, LedgerOperation.PAA, options.query);
|
|
23999
24154
|
} else {
|
|
24000
|
-
const actualCost = headlessSentOut.value
|
|
24155
|
+
const actualCost = serpActualCostMc(headlessSentOut.value);
|
|
24001
24156
|
const diff = syncCost - actualCost;
|
|
24002
24157
|
if (diff > 0) await creditMc(user.id, diff, LedgerOperation.SERP_REFUND, "headless-mode pricing settle");
|
|
24003
24158
|
else if (diff < 0) await debitMc(user.id, -diff, LedgerOperation.SERP, options.query);
|
|
@@ -24076,22 +24231,13 @@ app.delete("/admin/users/:id", adminAuth, async (c) => {
|
|
|
24076
24231
|
});
|
|
24077
24232
|
app.post("/admin/backfill-signup-credits", adminAuth, async (c) => {
|
|
24078
24233
|
const users = await listUsers();
|
|
24079
|
-
|
|
24080
|
-
|
|
24081
|
-
|
|
24082
|
-
|
|
24083
|
-
|
|
24084
|
-
|
|
24085
|
-
|
|
24086
|
-
if (alreadyHasGrant) {
|
|
24087
|
-
skipped++;
|
|
24088
|
-
} else {
|
|
24089
|
-
await grantSignupCredit(user.id);
|
|
24090
|
-
credited++;
|
|
24091
|
-
users_credited.push({ id: user.id, email: user.email });
|
|
24092
|
-
}
|
|
24093
|
-
}
|
|
24094
|
-
return c.json({ processed, credited, skipped, users_credited });
|
|
24234
|
+
return c.json({
|
|
24235
|
+
processed: users.length,
|
|
24236
|
+
credited: 0,
|
|
24237
|
+
skipped: users.length,
|
|
24238
|
+
users_credited: [],
|
|
24239
|
+
retired: true
|
|
24240
|
+
});
|
|
24095
24241
|
});
|
|
24096
24242
|
app.post("/extract-url", auth2, async (c) => {
|
|
24097
24243
|
const raw = await c.req.json().catch(() => ({}));
|
|
@@ -24672,7 +24818,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
24672
24818
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
24673
24819
|
return c.json({ error: "Unauthorized" }, 401);
|
|
24674
24820
|
}
|
|
24675
|
-
const { drainQueue } = await import("./worker-
|
|
24821
|
+
const { drainQueue } = await import("./worker-RV2TYXE3.js");
|
|
24676
24822
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
24677
24823
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
24678
24824
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup] = await Promise.all([
|
|
@@ -24700,15 +24846,15 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
24700
24846
|
return c.json({ error: "Unauthorized" }, 401);
|
|
24701
24847
|
}
|
|
24702
24848
|
const jobId = c.req.param("id");
|
|
24703
|
-
const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-
|
|
24704
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
24849
|
+
const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-2QQSL2QC.js");
|
|
24850
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-NFFVMUMT.js");
|
|
24705
24851
|
const job = await getExtractJob2(jobId);
|
|
24706
24852
|
if (!job) return c.json({ error: "job not found" }, 404);
|
|
24707
24853
|
const stored = await assembleExtractArtifacts(job, {});
|
|
24708
24854
|
await completeExtractJob2(jobId, stored);
|
|
24709
24855
|
let settlement = "already_settled_or_refunded";
|
|
24710
24856
|
if (job.billedMc == null && job.userId != null) {
|
|
24711
|
-
const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-
|
|
24857
|
+
const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-2QQSL2QC.js");
|
|
24712
24858
|
const heldMc = Number(job.options.heldMc ?? 0);
|
|
24713
24859
|
const successful = await countSuccessfulPages2(jobId);
|
|
24714
24860
|
const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
|
|
@@ -24874,4 +25020,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
24874
25020
|
export {
|
|
24875
25021
|
app
|
|
24876
25022
|
};
|
|
24877
|
-
//# sourceMappingURL=server-
|
|
25023
|
+
//# sourceMappingURL=server-OLOOFN5W.js.map
|