mcp-scraper 0.4.4 → 0.4.6
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/dist/bin/api-server.cjs +579 -40
- 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 +197 -3
- 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.map +1 -1
- package/dist/bin/paa-harvest.js +2 -2
- package/dist/chunk-5BB6Y3BB.js +7 -0
- package/dist/chunk-5BB6Y3BB.js.map +1 -0
- package/dist/{chunk-NJK5BTUK.js → chunk-5GAD2AN2.js} +2 -2
- package/dist/{chunk-FUVQR6GK.js → chunk-GL4BW4CP.js} +49 -1
- package/dist/chunk-GL4BW4CP.js.map +1 -0
- package/dist/{chunk-3UH3BEIZ.js → chunk-HE2LQPJ2.js} +2 -2
- package/dist/{chunk-Z34NGK64.js → chunk-IFXACD4K.js} +198 -4
- package/dist/chunk-IFXACD4K.js.map +1 -0
- package/dist/{chunk-5LO6EHEH.js → chunk-IVQYNY45.js} +14 -3
- package/dist/chunk-IVQYNY45.js.map +1 -0
- package/dist/{chunk-4ZZWFI6L.js → chunk-NYAWN7CZ.js} +3 -3
- package/dist/{db-H3S3M6KK.js → db-LIOTIWVN.js} +6 -2
- package/dist/{extract-bundle-UKE273TV.js → extract-bundle-COS56ZDO.js} +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{server-IKT3QVFB.js → server-R3KVQNOT.js} +316 -35
- package/dist/server-R3KVQNOT.js.map +1 -0
- package/dist/{site-extract-repository-THBVEXMP.js → site-extract-repository-LI2S3S6E.js} +4 -4
- package/dist/{worker-YHKUJR5P.js → worker-VASAJ7FZ.js} +5 -5
- package/package.json +2 -1
- package/dist/chunk-5LO6EHEH.js.map +0 -1
- package/dist/chunk-FUVQR6GK.js.map +0 -1
- package/dist/chunk-TZRPP45I.js +0 -7
- package/dist/chunk-TZRPP45I.js.map +0 -1
- package/dist/chunk-Z34NGK64.js.map +0 -1
- package/dist/server-IKT3QVFB.js.map +0 -1
- /package/dist/{chunk-NJK5BTUK.js.map → chunk-5GAD2AN2.js.map} +0 -0
- /package/dist/{chunk-3UH3BEIZ.js.map → chunk-HE2LQPJ2.js.map} +0 -0
- /package/dist/{chunk-4ZZWFI6L.js.map → chunk-NYAWN7CZ.js.map} +0 -0
- /package/dist/{db-H3S3M6KK.js.map → db-LIOTIWVN.js.map} +0 -0
- /package/dist/{extract-bundle-UKE273TV.js.map → extract-bundle-COS56ZDO.js.map} +0 -0
- /package/dist/{site-extract-repository-THBVEXMP.js.map → site-extract-repository-LI2S3S6E.js.map} +0 -0
- /package/dist/{worker-YHKUJR5P.js.map → worker-VASAJ7FZ.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
harvest
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5GAD2AN2.js";
|
|
4
4
|
import "./chunk-HPV4VOQX.js";
|
|
5
5
|
import "./chunk-K443GQY5.js";
|
|
6
6
|
import "./chunk-XGIPATLV.js";
|
|
7
7
|
import "./chunk-M2S27J6Z.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-GL4BW4CP.js";
|
|
9
9
|
|
|
10
10
|
// src/video/VideoGenerator.ts
|
|
11
11
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
createHarvestAttemptRecorder,
|
|
4
4
|
harvestProblemResponse,
|
|
5
5
|
serializeHarvestProblem
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-HE2LQPJ2.js";
|
|
7
7
|
import {
|
|
8
8
|
BrowserDriver,
|
|
9
9
|
MapsSelectors,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
recordVendorUsage,
|
|
17
17
|
resolveKernelProxyId,
|
|
18
18
|
runWithCostContext
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-5GAD2AN2.js";
|
|
20
20
|
import {
|
|
21
21
|
HttpMcpToolExecutor,
|
|
22
22
|
MemoryMcpToolExecutor,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
registerSerpIntelligenceCaptureTools,
|
|
32
32
|
sanitizeAttempts,
|
|
33
33
|
sanitizeHarvestResult
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-IFXACD4K.js";
|
|
35
35
|
import {
|
|
36
36
|
auditImages,
|
|
37
37
|
getBlobStore
|
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
RawMapsOverviewSchema,
|
|
66
66
|
RawMapsReviewStatsSchema
|
|
67
67
|
} from "./chunk-XGIPATLV.js";
|
|
68
|
-
import "./chunk-
|
|
68
|
+
import "./chunk-5BB6Y3BB.js";
|
|
69
69
|
import {
|
|
70
70
|
completeExtractJob,
|
|
71
71
|
countSuccessfulPages,
|
|
@@ -76,7 +76,7 @@ import {
|
|
|
76
76
|
saveExtractPages,
|
|
77
77
|
setExtractJobTotal,
|
|
78
78
|
settleExtractJob
|
|
79
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-NYAWN7CZ.js";
|
|
80
80
|
import {
|
|
81
81
|
BROWSER_OPEN_MIN_BALANCE_MC,
|
|
82
82
|
CONCURRENCY_PRICE_ID,
|
|
@@ -95,7 +95,7 @@ import {
|
|
|
95
95
|
browserActiveCostMc,
|
|
96
96
|
concurrencySlotBillingInfo,
|
|
97
97
|
insufficientBalanceResponse
|
|
98
|
-
} from "./chunk-
|
|
98
|
+
} from "./chunk-IVQYNY45.js";
|
|
99
99
|
import {
|
|
100
100
|
CaptchaError,
|
|
101
101
|
RECAPTCHA_INSTRUCTIONS,
|
|
@@ -131,6 +131,7 @@ import {
|
|
|
131
131
|
getLedger,
|
|
132
132
|
getMemoryPlan,
|
|
133
133
|
getMemoryProvisioned,
|
|
134
|
+
getPageSnapshot,
|
|
134
135
|
getRefresh,
|
|
135
136
|
getUserByApiKey,
|
|
136
137
|
getUserByEmail,
|
|
@@ -175,8 +176,9 @@ import {
|
|
|
175
176
|
setStripeCustomerId,
|
|
176
177
|
setSubscriptionTier,
|
|
177
178
|
setUserMemoryCreds,
|
|
179
|
+
upsertPageSnapshot,
|
|
178
180
|
verifyPassword
|
|
179
|
-
} from "./chunk-
|
|
181
|
+
} from "./chunk-GL4BW4CP.js";
|
|
180
182
|
|
|
181
183
|
// src/blog/registry.ts
|
|
182
184
|
var posts = [
|
|
@@ -7529,7 +7531,7 @@ var siteExtractFn = inngest.createFunction(
|
|
|
7529
7531
|
return auditImages(pages, { concurrency: 12, timeoutMs: 12e3 });
|
|
7530
7532
|
}) : null;
|
|
7531
7533
|
const artifacts = await step.run("finalize", async () => {
|
|
7532
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
7534
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-COS56ZDO.js");
|
|
7533
7535
|
const stored = await assembleExtractArtifacts(job, { branding, imageAudit });
|
|
7534
7536
|
await completeExtractJob(jobId, stored);
|
|
7535
7537
|
return stored;
|
|
@@ -8935,6 +8937,11 @@ var ExtractUrlBodySchema = z11.object({
|
|
|
8935
8937
|
depositToVault: z11.boolean().optional(),
|
|
8936
8938
|
vaultName: z11.string().trim().min(1).max(120).optional()
|
|
8937
8939
|
});
|
|
8940
|
+
var DiffPageBodySchema = z11.object({
|
|
8941
|
+
url: z11.string().min(1, "url is required"),
|
|
8942
|
+
allowLocal: z11.boolean().optional(),
|
|
8943
|
+
resetBaseline: z11.boolean().optional()
|
|
8944
|
+
});
|
|
8938
8945
|
var MapUrlsBodySchema = z11.object({
|
|
8939
8946
|
url: z11.string().min(1, "url is required"),
|
|
8940
8947
|
maxUrls: z11.number().int().min(1).max(2e3).optional(),
|
|
@@ -11935,23 +11942,8 @@ async function provisionMemoryForUser(user) {
|
|
|
11935
11942
|
} catch {
|
|
11936
11943
|
}
|
|
11937
11944
|
}
|
|
11938
|
-
function memoryPlanForUser(user) {
|
|
11939
|
-
switch (user.subscription_tier) {
|
|
11940
|
-
case "starter":
|
|
11941
|
-
return "pro";
|
|
11942
|
-
case "growth":
|
|
11943
|
-
return "team";
|
|
11944
|
-
case "scale":
|
|
11945
|
-
return "enterprise";
|
|
11946
|
-
default:
|
|
11947
|
-
return "free";
|
|
11948
|
-
}
|
|
11949
|
-
}
|
|
11950
|
-
var MEMORY_PLAN_RANK = { free: 0, pro: 1, team: 2, enterprise: 3 };
|
|
11951
11945
|
function resolveEffectiveMemoryPlan(user) {
|
|
11952
|
-
|
|
11953
|
-
const memoryProPlan = user.memory_plan === "pro" || user.memory_plan === "team" ? user.memory_plan : "free";
|
|
11954
|
-
return MEMORY_PLAN_RANK[tierPlan] >= MEMORY_PLAN_RANK[memoryProPlan] ? tierPlan : memoryProPlan;
|
|
11946
|
+
return user.memory_plan === "pro" || user.memory_plan === "team" ? user.memory_plan : "free";
|
|
11955
11947
|
}
|
|
11956
11948
|
async function getOrCreateUserMemoryKey(user) {
|
|
11957
11949
|
const creds = await getUserMemoryCreds(user.id);
|
|
@@ -15958,6 +15950,7 @@ mcpApp.all("/", async (c) => {
|
|
|
15958
15950
|
});
|
|
15959
15951
|
|
|
15960
15952
|
// src/api/browser-agent-routes.ts
|
|
15953
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
15961
15954
|
import { Hono as Hono17 } from "hono";
|
|
15962
15955
|
|
|
15963
15956
|
// src/api/browser-agent-db.ts
|
|
@@ -16046,8 +16039,58 @@ async function migrateBrowserAgent() {
|
|
|
16046
16039
|
} catch {
|
|
16047
16040
|
}
|
|
16048
16041
|
await db.execute(`CREATE INDEX IF NOT EXISTS browser_auth_connections_profile ON browser_auth_connections(profile)`);
|
|
16042
|
+
await db.execute(`
|
|
16043
|
+
CREATE TABLE IF NOT EXISTS browser_agent_extensions (
|
|
16044
|
+
id TEXT PRIMARY KEY,
|
|
16045
|
+
user_id INTEGER NOT NULL,
|
|
16046
|
+
name TEXT NOT NULL,
|
|
16047
|
+
backend_id TEXT NOT NULL,
|
|
16048
|
+
backend_name TEXT NOT NULL,
|
|
16049
|
+
source TEXT NOT NULL DEFAULT 'store',
|
|
16050
|
+
source_url TEXT,
|
|
16051
|
+
size_bytes INTEGER,
|
|
16052
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
16053
|
+
)
|
|
16054
|
+
`);
|
|
16055
|
+
await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS browser_agent_extensions_user_name ON browser_agent_extensions(user_id, name)`);
|
|
16056
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS browser_agent_extensions_user ON browser_agent_extensions(user_id)`);
|
|
16049
16057
|
_ready = true;
|
|
16050
16058
|
}
|
|
16059
|
+
async function createExtensionRow(input) {
|
|
16060
|
+
const db = getDb();
|
|
16061
|
+
const id = `bext_${randomUUID2().replace(/-/g, "").slice(0, 20)}`;
|
|
16062
|
+
await db.execute({
|
|
16063
|
+
sql: `INSERT INTO browser_agent_extensions (id, user_id, name, backend_id, backend_name, source, source_url, size_bytes)
|
|
16064
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
16065
|
+
args: [id, input.userId, input.name, input.backendId, input.backendName, input.source, input.sourceUrl, input.sizeBytes]
|
|
16066
|
+
});
|
|
16067
|
+
const row = await getExtensionRow(input.userId, input.name);
|
|
16068
|
+
if (!row) throw new Error("extension insert failed");
|
|
16069
|
+
return row;
|
|
16070
|
+
}
|
|
16071
|
+
async function getExtensionRow(userId, name) {
|
|
16072
|
+
const db = getDb();
|
|
16073
|
+
const res = await db.execute({
|
|
16074
|
+
sql: `SELECT * FROM browser_agent_extensions WHERE user_id = ? AND name = ?`,
|
|
16075
|
+
args: [userId, name]
|
|
16076
|
+
});
|
|
16077
|
+
return res.rows[0] ?? null;
|
|
16078
|
+
}
|
|
16079
|
+
async function listExtensionRows(userId) {
|
|
16080
|
+
const db = getDb();
|
|
16081
|
+
const res = await db.execute({
|
|
16082
|
+
sql: `SELECT * FROM browser_agent_extensions WHERE user_id = ? ORDER BY created_at DESC`,
|
|
16083
|
+
args: [userId]
|
|
16084
|
+
});
|
|
16085
|
+
return res.rows;
|
|
16086
|
+
}
|
|
16087
|
+
async function deleteExtensionRow(userId, name) {
|
|
16088
|
+
const db = getDb();
|
|
16089
|
+
await db.execute({
|
|
16090
|
+
sql: `DELETE FROM browser_agent_extensions WHERE user_id = ? AND name = ?`,
|
|
16091
|
+
args: [userId, name]
|
|
16092
|
+
});
|
|
16093
|
+
}
|
|
16051
16094
|
async function createAuthConnectionRow(input) {
|
|
16052
16095
|
const db = getDb();
|
|
16053
16096
|
const connectionId = `authc_${randomUUID2().replace(/-/g, "").slice(0, 20)}`;
|
|
@@ -16904,6 +16947,28 @@ function isProfileConflict(err) {
|
|
|
16904
16947
|
const message = err instanceof Error ? err.message : String(err);
|
|
16905
16948
|
return /\b409\b|conflict|already exists/i.test(message);
|
|
16906
16949
|
}
|
|
16950
|
+
function isNotFound(err) {
|
|
16951
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
16952
|
+
return /\b404\b|not found/i.test(message);
|
|
16953
|
+
}
|
|
16954
|
+
async function importExtensionFromStore(storeUrl, backendName) {
|
|
16955
|
+
const k = client();
|
|
16956
|
+
const archive = await k.extensions.downloadFromChromeStore({ url: storeUrl });
|
|
16957
|
+
const uploaded = await k.extensions.upload({ file: archive, name: backendName });
|
|
16958
|
+
return {
|
|
16959
|
+
backendId: uploaded.id,
|
|
16960
|
+
backendName: uploaded.name ?? backendName,
|
|
16961
|
+
sizeBytes: typeof uploaded.size_bytes === "number" ? uploaded.size_bytes : null
|
|
16962
|
+
};
|
|
16963
|
+
}
|
|
16964
|
+
async function deleteExtensionBackend(backendName) {
|
|
16965
|
+
const k = client();
|
|
16966
|
+
try {
|
|
16967
|
+
await k.extensions.delete(backendName);
|
|
16968
|
+
} catch (err) {
|
|
16969
|
+
if (!isNotFound(err)) throw err;
|
|
16970
|
+
}
|
|
16971
|
+
}
|
|
16907
16972
|
async function ensureProfile(k, name) {
|
|
16908
16973
|
try {
|
|
16909
16974
|
await k.profiles.create({ name });
|
|
@@ -16922,6 +16987,7 @@ async function createSession(opts = {}) {
|
|
|
16922
16987
|
const resolvedSaveProfileChanges = opts.saveProfileChanges ?? browserServiceProfileSaveChanges();
|
|
16923
16988
|
const disableDefaultProxy = opts.disableDefaultProxy ?? !resolvedProxyId;
|
|
16924
16989
|
const startUrl = opts.startUrl?.trim();
|
|
16990
|
+
const extensionNames = (opts.extensionNames ?? []).map((name) => name.trim()).filter(Boolean);
|
|
16925
16991
|
if (resolvedProfileName && resolvedSaveProfileChanges === true) {
|
|
16926
16992
|
await ensureProfile(k, resolvedProfileName);
|
|
16927
16993
|
}
|
|
@@ -16935,7 +17001,8 @@ async function createSession(opts = {}) {
|
|
|
16935
17001
|
name: resolvedProfileName,
|
|
16936
17002
|
...typeof resolvedSaveProfileChanges === "boolean" ? { save_changes: resolvedSaveProfileChanges } : {}
|
|
16937
17003
|
}
|
|
16938
|
-
} : {}
|
|
17004
|
+
} : {},
|
|
17005
|
+
...extensionNames.length ? { extensions: extensionNames.map((name) => ({ name })) } : {}
|
|
16939
17006
|
};
|
|
16940
17007
|
const browser = await k.browsers.create(createPayload);
|
|
16941
17008
|
const runtimeSessionId = browser.session_id;
|
|
@@ -17323,6 +17390,16 @@ function parseAgentDate(value) {
|
|
|
17323
17390
|
const legacyParsed = Date.parse(`${value.replace(" ", "T")}Z`);
|
|
17324
17391
|
return Number.isFinite(legacyParsed) ? legacyParsed : null;
|
|
17325
17392
|
}
|
|
17393
|
+
var EXTENSION_NAME_RE = /^[\w .-]{1,64}$/;
|
|
17394
|
+
function publicExtension(row) {
|
|
17395
|
+
return {
|
|
17396
|
+
name: row.name,
|
|
17397
|
+
source: row.source,
|
|
17398
|
+
source_url: row.source_url,
|
|
17399
|
+
size_bytes: row.size_bytes,
|
|
17400
|
+
created_at: row.created_at
|
|
17401
|
+
};
|
|
17402
|
+
}
|
|
17326
17403
|
function replayClock(row) {
|
|
17327
17404
|
if (!row) return null;
|
|
17328
17405
|
const startedAtMs = parseAgentDate(row.started_at);
|
|
@@ -17469,6 +17546,15 @@ function buildBrowserAgentRoutes() {
|
|
|
17469
17546
|
const body = await c.req.json().catch(() => ({}));
|
|
17470
17547
|
const timeoutSeconds = typeof body.timeout_seconds === "number" ? body.timeout_seconds : void 0;
|
|
17471
17548
|
const startUrl = typeof body.url === "string" ? body.url : void 0;
|
|
17549
|
+
const extensionNames = Array.isArray(body.extension_names) ? body.extension_names.filter((v) => typeof v === "string") : void 0;
|
|
17550
|
+
let resolvedExtensionBackendNames;
|
|
17551
|
+
if (extensionNames && extensionNames.length) {
|
|
17552
|
+
const names = extensionNames;
|
|
17553
|
+
const rows = await Promise.all(names.map((name) => getExtensionRow(user.id, name)));
|
|
17554
|
+
const missing = names.filter((_, i) => !rows[i]);
|
|
17555
|
+
if (missing.length) return c.json({ error: `extension(s) not found: ${missing.join(", ")}` }, 400);
|
|
17556
|
+
resolvedExtensionBackendNames = rows.map((row) => row.backend_name);
|
|
17557
|
+
}
|
|
17472
17558
|
const gate = await acquireConcurrencyGate(user, "browser_agent_session", {
|
|
17473
17559
|
ttlSeconds: browserSessionLockTtlSeconds(timeoutSeconds),
|
|
17474
17560
|
metadata: { label: typeof body.label === "string" ? body.label : null }
|
|
@@ -17483,7 +17569,8 @@ function buildBrowserAgentRoutes() {
|
|
|
17483
17569
|
saveProfileChanges: typeof body.save_profile_changes === "boolean" ? body.save_profile_changes : void 0,
|
|
17484
17570
|
startUrl,
|
|
17485
17571
|
disableDefaultProxy: typeof body.disable_default_proxy === "boolean" ? body.disable_default_proxy : void 0,
|
|
17486
|
-
viewport: body.viewport && typeof body.viewport === "object" ? body.viewport : void 0
|
|
17572
|
+
viewport: body.viewport && typeof body.viewport === "object" ? body.viewport : void 0,
|
|
17573
|
+
extensionNames: resolvedExtensionBackendNames
|
|
17487
17574
|
});
|
|
17488
17575
|
runtimeSessionId = created.runtimeSessionId;
|
|
17489
17576
|
const row = await createSessionRow({
|
|
@@ -17786,6 +17873,52 @@ function buildBrowserAgentRoutes() {
|
|
|
17786
17873
|
}))
|
|
17787
17874
|
});
|
|
17788
17875
|
});
|
|
17876
|
+
app2.post("/extensions/import", async (c) => {
|
|
17877
|
+
const user = c.get("user");
|
|
17878
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17879
|
+
const storeUrl = typeof body.store_url === "string" ? body.store_url.trim() : "";
|
|
17880
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
17881
|
+
if (!storeUrl) return c.json({ error: "store_url is required" }, 400);
|
|
17882
|
+
if (!name || !EXTENSION_NAME_RE.test(name)) {
|
|
17883
|
+
return c.json({ error: "name is required (1-64 characters: letters, numbers, spaces, dots, underscores, hyphens)" }, 400);
|
|
17884
|
+
}
|
|
17885
|
+
const existing = await getExtensionRow(user.id, name);
|
|
17886
|
+
if (existing) return c.json({ error: `an extension named "${name}" already exists \u2014 delete it first or pick another name` }, 409);
|
|
17887
|
+
const backendName = `u${user.id}_${randomUUID3().replace(/-/g, "")}`;
|
|
17888
|
+
try {
|
|
17889
|
+
const imported = await importExtensionFromStore(storeUrl, backendName);
|
|
17890
|
+
const row = await createExtensionRow({
|
|
17891
|
+
userId: user.id,
|
|
17892
|
+
name,
|
|
17893
|
+
backendId: imported.backendId,
|
|
17894
|
+
backendName: imported.backendName,
|
|
17895
|
+
source: "store",
|
|
17896
|
+
sourceUrl: storeUrl,
|
|
17897
|
+
sizeBytes: imported.sizeBytes
|
|
17898
|
+
});
|
|
17899
|
+
return c.json(publicExtension(row));
|
|
17900
|
+
} catch (err) {
|
|
17901
|
+
return c.json(failure(err), 502);
|
|
17902
|
+
}
|
|
17903
|
+
});
|
|
17904
|
+
app2.get("/extensions", async (c) => {
|
|
17905
|
+
const user = c.get("user");
|
|
17906
|
+
const rows = await listExtensionRows(user.id);
|
|
17907
|
+
return c.json({ extensions: rows.map(publicExtension) });
|
|
17908
|
+
});
|
|
17909
|
+
app2.delete("/extensions/:name", async (c) => {
|
|
17910
|
+
const user = c.get("user");
|
|
17911
|
+
const name = c.req.param("name");
|
|
17912
|
+
const row = await getExtensionRow(user.id, name);
|
|
17913
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
17914
|
+
try {
|
|
17915
|
+
await deleteExtensionBackend(row.backend_name);
|
|
17916
|
+
} catch (err) {
|
|
17917
|
+
return c.json(failure(err), 502);
|
|
17918
|
+
}
|
|
17919
|
+
await deleteExtensionRow(user.id, name);
|
|
17920
|
+
return c.json({ ok: true });
|
|
17921
|
+
});
|
|
17789
17922
|
app2.get("/sessions/:id/replays/:replayId/download", async (c) => {
|
|
17790
17923
|
const user = c.get("user");
|
|
17791
17924
|
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
@@ -18141,7 +18274,6 @@ stripeApp.post("/webhooks", async (c) => {
|
|
|
18141
18274
|
const live = sub.status === "active" || sub.status === "trialing";
|
|
18142
18275
|
const newTier = live ? tier.tier : null;
|
|
18143
18276
|
await setSubscriptionTier(user.id, newTier, live ? tier.concurrency : 0, live ? sub.id : null);
|
|
18144
|
-
await syncMemoryKeyPlan(user, resolveEffectiveMemoryPlan({ ...user, subscription_tier: newTier }));
|
|
18145
18277
|
} else if (priceId === CONCURRENCY_PRICE_ID && event.type === "customer.subscription.created") {
|
|
18146
18278
|
await setExtraConcurrencySlots(user.id, user.extra_concurrency_slots + 1);
|
|
18147
18279
|
await setConcurrencySubId(user.id, sub.id);
|
|
@@ -18167,7 +18299,6 @@ stripeApp.post("/webhooks", async (c) => {
|
|
|
18167
18299
|
if (!user) return c.json({ received: true });
|
|
18168
18300
|
if (priceId && priceId in SUBSCRIPTION_TIERS) {
|
|
18169
18301
|
await setSubscriptionTier(user.id, null, 0, null);
|
|
18170
|
-
await syncMemoryKeyPlan(user, resolveEffectiveMemoryPlan({ ...user, subscription_tier: null }));
|
|
18171
18302
|
} else if (priceId === CONCURRENCY_PRICE_ID) {
|
|
18172
18303
|
await setExtraConcurrencySlots(user.id, Math.max(0, user.extra_concurrency_slots - 1));
|
|
18173
18304
|
await setConcurrencySubId(user.id, null);
|
|
@@ -18191,7 +18322,7 @@ async function resolveUser(customerId, emailFallback) {
|
|
|
18191
18322
|
// src/api/oauth-routes.ts
|
|
18192
18323
|
import { Hono as Hono19 } from "hono";
|
|
18193
18324
|
import { getCookie, setCookie } from "hono/cookie";
|
|
18194
|
-
import { createHash as createHash3, randomBytes as randomBytes2, randomUUID as
|
|
18325
|
+
import { createHash as createHash3, randomBytes as randomBytes2, randomUUID as randomUUID4 } from "crypto";
|
|
18195
18326
|
import { importPKCS8, exportJWK, SignJWT } from "jose";
|
|
18196
18327
|
var ISSUER = "https://mcpscraper.dev";
|
|
18197
18328
|
var RESOURCE = () => process.env.MCP_MEMORY_RESOURCE_URL ?? "https://memory.mcpscraper.dev/mcp";
|
|
@@ -18539,7 +18670,7 @@ function pkceMatches(verifier, challenge) {
|
|
|
18539
18670
|
}
|
|
18540
18671
|
async function mintAccessToken(identity, scope, plan, audience) {
|
|
18541
18672
|
const { privateKey, kid } = await getKeys();
|
|
18542
|
-
return new SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti(
|
|
18673
|
+
return new SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti(randomUUID4()).setExpirationTime(`${ACCESS_TTL_SECONDS}s`).sign(privateKey);
|
|
18543
18674
|
}
|
|
18544
18675
|
function tokenErrorResponse(c, error, description, status) {
|
|
18545
18676
|
return c.json({ error, error_description: description }, status);
|
|
@@ -19645,6 +19776,71 @@ import { createMiddleware as createMiddleware3 } from "hono/factory";
|
|
|
19645
19776
|
import { getCookie as getCookie2, setCookie as setCookie2, deleteCookie } from "hono/cookie";
|
|
19646
19777
|
import Stripe2 from "stripe";
|
|
19647
19778
|
|
|
19779
|
+
// src/api/page-diff.ts
|
|
19780
|
+
import { createHash as createHash4 } from "crypto";
|
|
19781
|
+
import { diffLines } from "diff";
|
|
19782
|
+
var MAX_SNAPSHOT_CONTENT_CHARS = 25e4;
|
|
19783
|
+
var MAX_DIFF_HUNKS = 200;
|
|
19784
|
+
var MAX_DIFF_LINES_PER_RESPONSE = 2e3;
|
|
19785
|
+
function sha256Hex(value) {
|
|
19786
|
+
return createHash4("sha256").update(value).digest("hex");
|
|
19787
|
+
}
|
|
19788
|
+
function truncateForStorage(value, maxChars = MAX_SNAPSHOT_CONTENT_CHARS) {
|
|
19789
|
+
if (value.length <= maxChars) return { value, truncated: false };
|
|
19790
|
+
return { value: value.slice(0, maxChars), truncated: true };
|
|
19791
|
+
}
|
|
19792
|
+
function changeLines(change) {
|
|
19793
|
+
const lines = change.value.split("\n");
|
|
19794
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
19795
|
+
return lines;
|
|
19796
|
+
}
|
|
19797
|
+
function diffPageContent(oldContent, newContent) {
|
|
19798
|
+
const changes = diffLines(oldContent, newContent);
|
|
19799
|
+
const rawHunks = [];
|
|
19800
|
+
let linesAdded = 0;
|
|
19801
|
+
let linesRemoved = 0;
|
|
19802
|
+
let totalChangedLineCount = 0;
|
|
19803
|
+
for (const change of changes) {
|
|
19804
|
+
if (!change.added && !change.removed) continue;
|
|
19805
|
+
const lines = changeLines(change);
|
|
19806
|
+
totalChangedLineCount += lines.length;
|
|
19807
|
+
if (change.added) linesAdded += lines.length;
|
|
19808
|
+
if (change.removed) linesRemoved += lines.length;
|
|
19809
|
+
rawHunks.push({ type: change.added ? "added" : "removed", lines });
|
|
19810
|
+
}
|
|
19811
|
+
let hunksTruncated = false;
|
|
19812
|
+
const hunksWithinCount = rawHunks.length > MAX_DIFF_HUNKS ? rawHunks.slice(0, MAX_DIFF_HUNKS) : rawHunks;
|
|
19813
|
+
if (rawHunks.length > MAX_DIFF_HUNKS) hunksTruncated = true;
|
|
19814
|
+
const hunks = [];
|
|
19815
|
+
let emittedLines = 0;
|
|
19816
|
+
for (const hunk of hunksWithinCount) {
|
|
19817
|
+
if (emittedLines >= MAX_DIFF_LINES_PER_RESPONSE) {
|
|
19818
|
+
hunksTruncated = true;
|
|
19819
|
+
break;
|
|
19820
|
+
}
|
|
19821
|
+
const remaining = MAX_DIFF_LINES_PER_RESPONSE - emittedLines;
|
|
19822
|
+
if (hunk.lines.length > remaining) {
|
|
19823
|
+
hunks.push({ type: hunk.type, lines: hunk.lines.slice(0, remaining) });
|
|
19824
|
+
emittedLines += remaining;
|
|
19825
|
+
hunksTruncated = true;
|
|
19826
|
+
break;
|
|
19827
|
+
}
|
|
19828
|
+
hunks.push(hunk);
|
|
19829
|
+
emittedLines += hunk.lines.length;
|
|
19830
|
+
}
|
|
19831
|
+
const denominator = Math.max(oldContent.split("\n").length, newContent.split("\n").length, 1);
|
|
19832
|
+
const percentChanged = totalChangedLineCount > 0 ? Math.min(100, Math.round(totalChangedLineCount / denominator * 1e3) / 10) : 0;
|
|
19833
|
+
return {
|
|
19834
|
+
hunks,
|
|
19835
|
+
linesAdded,
|
|
19836
|
+
linesRemoved,
|
|
19837
|
+
percentChanged,
|
|
19838
|
+
hunksTruncated,
|
|
19839
|
+
hunksTruncatedReason: hunksTruncated ? `Showing ${emittedLines} of ${totalChangedLineCount} changed lines (capped at ${MAX_DIFF_HUNKS} hunks / ${MAX_DIFF_LINES_PER_RESPONSE} lines). Use contentHash/previousContentHash to confirm the full scope of change.` : null,
|
|
19840
|
+
totalChangedLineCount
|
|
19841
|
+
};
|
|
19842
|
+
}
|
|
19843
|
+
|
|
19648
19844
|
// src/api/scrape-vault-sink.ts
|
|
19649
19845
|
var MAX_DEPOSIT_CHARS = 5e6;
|
|
19650
19846
|
var SCRAPE_FALLBACK_PREFIX = "scrape-fallback/";
|
|
@@ -20974,6 +21170,91 @@ app.post("/extract-url", auth2, async (c) => {
|
|
|
20974
21170
|
await releaseConcurrencyGate(gate.lockId);
|
|
20975
21171
|
}
|
|
20976
21172
|
});
|
|
21173
|
+
app.post("/diff-page", auth2, async (c) => {
|
|
21174
|
+
const raw = await c.req.json().catch(() => ({}));
|
|
21175
|
+
const bodyResult = DiffPageBodySchema.safeParse(raw);
|
|
21176
|
+
if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
|
|
21177
|
+
const { url, allowLocal, resetBaseline } = bodyResult.data;
|
|
21178
|
+
if (!allowLocal) {
|
|
21179
|
+
const checked = await validatePublicHttpUrl(url, { field: "URL" });
|
|
21180
|
+
if (checked.error || !checked.parsed) return c.json({ error: checked.error ?? "Invalid URL" }, 400);
|
|
21181
|
+
} else {
|
|
21182
|
+
try {
|
|
21183
|
+
new URL(url);
|
|
21184
|
+
} catch {
|
|
21185
|
+
return c.json({ error: "Invalid URL" }, 400);
|
|
21186
|
+
}
|
|
21187
|
+
}
|
|
21188
|
+
const canonicalUrl = (() => {
|
|
21189
|
+
try {
|
|
21190
|
+
return new URL(url).href;
|
|
21191
|
+
} catch {
|
|
21192
|
+
return url;
|
|
21193
|
+
}
|
|
21194
|
+
})();
|
|
21195
|
+
const user = c.get("user");
|
|
21196
|
+
const gate = await acquireConcurrencyGate(user, "diff_page", {
|
|
21197
|
+
reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"),
|
|
21198
|
+
metadata: { url: canonicalUrl }
|
|
21199
|
+
});
|
|
21200
|
+
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
21201
|
+
let debited = false;
|
|
21202
|
+
try {
|
|
21203
|
+
const { ok: dpOk, balance_mc: dpBal } = await debitMc(user.id, MC_COSTS.diff_page, LedgerOperation.DIFF_PAGE, new URL(canonicalUrl).hostname);
|
|
21204
|
+
if (!dpOk) return c.json(insufficientBalanceResponse(dpBal, MC_COSTS.diff_page), 402);
|
|
21205
|
+
debited = true;
|
|
21206
|
+
const existingSnapshot = await getPageSnapshot(user.id, canonicalUrl);
|
|
21207
|
+
const previous = resetBaseline ? null : existingSnapshot;
|
|
21208
|
+
const kernelApiKey = browserServiceApiKey();
|
|
21209
|
+
const result = await extractKpo({ url: canonicalUrl, kernelApiKey });
|
|
21210
|
+
const currentContent = result.bodyMarkdown ?? "";
|
|
21211
|
+
const currentHash = sha256Hex(currentContent);
|
|
21212
|
+
const { value: storedContent, truncated: contentTruncated } = truncateForStorage(currentContent);
|
|
21213
|
+
const currentCheckedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
21214
|
+
await upsertPageSnapshot(user.id, canonicalUrl, {
|
|
21215
|
+
contentHash: currentHash,
|
|
21216
|
+
content: storedContent,
|
|
21217
|
+
title: result.title ?? null,
|
|
21218
|
+
contentBytes: Buffer.byteLength(currentContent, "utf8"),
|
|
21219
|
+
truncated: contentTruncated
|
|
21220
|
+
});
|
|
21221
|
+
let status;
|
|
21222
|
+
let diff = { hunks: [], linesAdded: 0, linesRemoved: 0, percentChanged: 0, hunksTruncated: false, hunksTruncatedReason: null, totalChangedLineCount: 0 };
|
|
21223
|
+
if (!previous) {
|
|
21224
|
+
status = "baseline";
|
|
21225
|
+
diff.percentChanged = null;
|
|
21226
|
+
} else if (previous.contentHash === currentHash) {
|
|
21227
|
+
status = "unchanged";
|
|
21228
|
+
} else {
|
|
21229
|
+
status = "changed";
|
|
21230
|
+
diff = diffPageContent(previous.content, storedContent);
|
|
21231
|
+
}
|
|
21232
|
+
await logRequestEvent({ userId: user.id, source: "diff_page", status: "done", query: canonicalUrl, result: { status } });
|
|
21233
|
+
return c.json({
|
|
21234
|
+
url: canonicalUrl,
|
|
21235
|
+
title: result.title ?? null,
|
|
21236
|
+
status,
|
|
21237
|
+
isReset: !!resetBaseline && !!existingSnapshot,
|
|
21238
|
+
previousCheckedAt: previous?.checkedAt ?? null,
|
|
21239
|
+
currentCheckedAt,
|
|
21240
|
+
contentHash: currentHash,
|
|
21241
|
+
previousContentHash: previous?.contentHash ?? null,
|
|
21242
|
+
summary: { linesAdded: diff.linesAdded, linesRemoved: diff.linesRemoved, percentChanged: diff.percentChanged },
|
|
21243
|
+
hunks: diff.hunks,
|
|
21244
|
+
contentTruncated,
|
|
21245
|
+
hunksTruncated: diff.hunksTruncated,
|
|
21246
|
+
hunksTruncatedReason: diff.hunksTruncatedReason,
|
|
21247
|
+
totalChangedLineCount: diff.totalChangedLineCount
|
|
21248
|
+
});
|
|
21249
|
+
} catch (err) {
|
|
21250
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
21251
|
+
if (debited) await creditMc(user.id, MC_COSTS.diff_page, LedgerOperation.DIFF_PAGE, "failed call");
|
|
21252
|
+
await logRequestEvent({ userId: user.id, source: "diff_page", status: "failed", query: canonicalUrl, error: msg });
|
|
21253
|
+
return c.json({ error: msg }, 500);
|
|
21254
|
+
} finally {
|
|
21255
|
+
await releaseConcurrencyGate(gate.lockId);
|
|
21256
|
+
}
|
|
21257
|
+
});
|
|
20977
21258
|
app.post("/map-urls", auth2, async (c) => {
|
|
20978
21259
|
const raw = await c.req.json().catch(() => ({}));
|
|
20979
21260
|
const bodyResult = MapUrlsBodySchema.safeParse(raw);
|
|
@@ -21400,7 +21681,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
21400
21681
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
21401
21682
|
return c.json({ error: "Unauthorized" }, 401);
|
|
21402
21683
|
}
|
|
21403
|
-
const { drainQueue } = await import("./worker-
|
|
21684
|
+
const { drainQueue } = await import("./worker-VASAJ7FZ.js");
|
|
21404
21685
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
21405
21686
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
21406
21687
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup] = await Promise.all([
|
|
@@ -21418,15 +21699,15 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
|
|
|
21418
21699
|
return c.json({ error: "Unauthorized" }, 401);
|
|
21419
21700
|
}
|
|
21420
21701
|
const jobId = c.req.param("id");
|
|
21421
|
-
const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-
|
|
21422
|
-
const { assembleExtractArtifacts } = await import("./extract-bundle-
|
|
21702
|
+
const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-LI2S3S6E.js");
|
|
21703
|
+
const { assembleExtractArtifacts } = await import("./extract-bundle-COS56ZDO.js");
|
|
21423
21704
|
const job = await getExtractJob2(jobId);
|
|
21424
21705
|
if (!job) return c.json({ error: "job not found" }, 404);
|
|
21425
21706
|
const stored = await assembleExtractArtifacts(job, {});
|
|
21426
21707
|
await completeExtractJob2(jobId, stored);
|
|
21427
21708
|
let settlement = "already_settled_or_refunded";
|
|
21428
21709
|
if (job.billedMc == null && job.userId != null) {
|
|
21429
|
-
const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-
|
|
21710
|
+
const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-LI2S3S6E.js");
|
|
21430
21711
|
const heldMc = Number(job.options.heldMc ?? 0);
|
|
21431
21712
|
const successful = await countSuccessfulPages2(jobId);
|
|
21432
21713
|
const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
|
|
@@ -21590,4 +21871,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
21590
21871
|
export {
|
|
21591
21872
|
app
|
|
21592
21873
|
};
|
|
21593
|
-
//# sourceMappingURL=server-
|
|
21874
|
+
//# sourceMappingURL=server-R3KVQNOT.js.map
|