leapfrog-mcp 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/adaptive-wait.d.ts +72 -0
  2. package/dist/adaptive-wait.js +695 -0
  3. package/dist/api-intelligence.d.ts +82 -0
  4. package/dist/api-intelligence.js +575 -0
  5. package/dist/captcha-solver.d.ts +26 -0
  6. package/dist/captcha-solver.js +547 -0
  7. package/dist/cdp-connector.d.ts +33 -0
  8. package/dist/cdp-connector.js +176 -0
  9. package/dist/consent-dismiss.d.ts +33 -0
  10. package/dist/consent-dismiss.js +358 -0
  11. package/dist/crash-recovery.d.ts +74 -0
  12. package/dist/crash-recovery.js +242 -0
  13. package/dist/domain-knowledge.d.ts +149 -0
  14. package/dist/domain-knowledge.js +449 -0
  15. package/dist/harness-intelligence.d.ts +65 -0
  16. package/dist/harness-intelligence.js +432 -0
  17. package/dist/humanize-fingerprint.d.ts +42 -0
  18. package/dist/humanize-fingerprint.js +161 -0
  19. package/dist/humanize-mouse.d.ts +95 -0
  20. package/dist/humanize-mouse.js +275 -0
  21. package/dist/humanize-pause.d.ts +48 -0
  22. package/dist/humanize-pause.js +111 -0
  23. package/dist/humanize-scroll.d.ts +67 -0
  24. package/dist/humanize-scroll.js +185 -0
  25. package/dist/humanize-typing.d.ts +60 -0
  26. package/dist/humanize-typing.js +258 -0
  27. package/dist/humanize-utils.d.ts +62 -0
  28. package/dist/humanize-utils.js +100 -0
  29. package/dist/intervention.d.ts +65 -0
  30. package/dist/intervention.js +591 -0
  31. package/dist/logger.d.ts +13 -0
  32. package/dist/logger.js +47 -0
  33. package/dist/network-intelligence.d.ts +70 -0
  34. package/dist/network-intelligence.js +424 -0
  35. package/dist/page-classifier.d.ts +33 -0
  36. package/dist/page-classifier.js +1000 -0
  37. package/dist/paginate.d.ts +42 -0
  38. package/dist/paginate.js +693 -0
  39. package/dist/recording.d.ts +72 -0
  40. package/dist/recording.js +934 -0
  41. package/dist/script-executor.d.ts +31 -0
  42. package/dist/script-executor.js +249 -0
  43. package/dist/session-hud.d.ts +20 -0
  44. package/dist/session-hud.js +134 -0
  45. package/dist/snapshot-differ.d.ts +26 -0
  46. package/dist/snapshot-differ.js +225 -0
  47. package/dist/ssrf.d.ts +28 -0
  48. package/dist/ssrf.js +290 -0
  49. package/dist/stealth-audit.d.ts +27 -0
  50. package/dist/stealth-audit.js +719 -0
  51. package/dist/stealth.d.ts +195 -0
  52. package/dist/stealth.js +1157 -0
  53. package/dist/tab-manager.d.ts +14 -0
  54. package/dist/tab-manager.js +306 -0
  55. package/dist/tiles-coordinator.d.ts +106 -0
  56. package/dist/tiles-coordinator.js +358 -0
  57. package/package.json +1 -1
package/dist/logger.js ADDED
@@ -0,0 +1,47 @@
1
+ // ─── Structured JSON Logger ───────────────────────────────────────────────
2
+ //
3
+ // Zero-dependency structured logger that writes JSON to stderr.
4
+ // MCP servers use stderr for logging (stdout is reserved for the protocol).
5
+ //
6
+ // Control verbosity via LEAP_LOG_LEVEL env var: debug | info | warn | error
7
+ // Default: info
8
+ const LOG_LEVELS = {
9
+ debug: 0,
10
+ info: 1,
11
+ warn: 2,
12
+ error: 3,
13
+ };
14
+ class Logger {
15
+ minLevel;
16
+ constructor() {
17
+ const envLevel = process.env.LEAP_LOG_LEVEL;
18
+ this.minLevel =
19
+ envLevel && envLevel in LOG_LEVELS ? envLevel : 'info';
20
+ }
21
+ log(level, event, data) {
22
+ if (LOG_LEVELS[level] < LOG_LEVELS[this.minLevel])
23
+ return;
24
+ const entry = {
25
+ ts: new Date().toISOString(),
26
+ level,
27
+ event,
28
+ pid: process.pid,
29
+ ...data,
30
+ };
31
+ process.stderr.write(JSON.stringify(entry) + '\n');
32
+ }
33
+ debug(event, data) {
34
+ this.log('debug', event, data);
35
+ }
36
+ info(event, data) {
37
+ this.log('info', event, data);
38
+ }
39
+ warn(event, data) {
40
+ this.log('warn', event, data);
41
+ }
42
+ error(event, data) {
43
+ this.log('error', event, data);
44
+ }
45
+ }
46
+ export const logger = new Logger();
47
+ export default logger;
@@ -0,0 +1,70 @@
1
+ import type { Page } from "playwright-core";
2
+ import type { Session, NetworkInterceptRule } from "./types.js";
3
+ export type ResponseCallback = (session: Session, url: string, method: string, status: number, headers: Record<string, string>, body: Buffer | null, duration: number, resourceType: string, requestHeaders?: Record<string, string>, requestBody?: string) => void;
4
+ export interface NetworkFilter {
5
+ urlPattern?: string;
6
+ method?: string;
7
+ statusMin?: number;
8
+ statusMax?: number;
9
+ contentType?: string;
10
+ }
11
+ export interface ConsoleFilter {
12
+ level?: string;
13
+ }
14
+ export declare class NetworkIntelligence {
15
+ /**
16
+ * Tracks active route handlers per session so we can unroute them.
17
+ * Key = session.id, Value = Map of ruleId -> { urlPattern, handler }
18
+ */
19
+ private routeHandlers;
20
+ /** Registered response callbacks — invoked for every captured response */
21
+ private responseCallbacks;
22
+ /**
23
+ * Register a callback that fires for every network response.
24
+ * Used by ApiIntelligence to capture API traffic.
25
+ */
26
+ onResponse(callback: ResponseCallback): void;
27
+ /**
28
+ * Wire up network and console capture on a page.
29
+ * Call once when a session is created. Initializes the ring buffers
30
+ * on the session and installs Playwright event listeners.
31
+ */
32
+ attachToPage(page: Page, session: Session): void;
33
+ /**
34
+ * Returns network log entries as compact formatted text.
35
+ * Supports filtering by URL pattern (regex or substring), HTTP method,
36
+ * status code range, and content-type.
37
+ */
38
+ getNetworkLog(session: Session, filter?: NetworkFilter): string;
39
+ /**
40
+ * Returns console log entries as compact formatted text.
41
+ * Supports filtering by log level.
42
+ */
43
+ getConsoleLog(session: Session, filter?: ConsoleFilter): string;
44
+ /**
45
+ * Add a network intercept rule to a page.
46
+ * - `block`: Aborts matching requests
47
+ * - `log`: Lets requests through (captured by the response listener)
48
+ * - `mock`: Returns a custom response with specified status, body, and content-type
49
+ *
50
+ * If a rule with the same ID already exists, it is replaced.
51
+ */
52
+ addIntercept(page: Page, session: Session, rule: NetworkInterceptRule): Promise<void>;
53
+ /**
54
+ * Remove a previously added intercept rule by ID.
55
+ * Calls page.unroute() to deregister the Playwright route handler.
56
+ */
57
+ removeIntercept(page: Page, session: Session, ruleId: string): Promise<void>;
58
+ /**
59
+ * Clear captured logs.
60
+ * @param type - Which logs to clear: 'network', 'console', or 'all' (default).
61
+ */
62
+ clearLogs(session: Session, type?: "network" | "console" | "all"): void;
63
+ /**
64
+ * Remove tracked route handlers for a destroyed session.
65
+ * Call this from your session destroy logic.
66
+ */
67
+ cleanupSession(sessionId: string): void;
68
+ }
69
+ export declare const networkIntelligence: NetworkIntelligence;
70
+ export default NetworkIntelligence;
@@ -0,0 +1,424 @@
1
+ // ─── Constants ────────────────────────────────────────────────────────────
2
+ const MAX_NETWORK_ENTRIES = 200;
3
+ const MAX_CONSOLE_ENTRIES = 100;
4
+ const MAX_BODY_CAPTURE_BYTES = 10 * 1024; // 10KB
5
+ /** Content-types eligible for body capture */
6
+ const CAPTURABLE_CONTENT_TYPES = [
7
+ "application/json",
8
+ "text/plain",
9
+ "text/html",
10
+ "text/xml",
11
+ "application/xml",
12
+ "text/csv",
13
+ "application/javascript",
14
+ "text/javascript",
15
+ ];
16
+ // ─── Helpers ──────────────────────────────────────────────────────────────
17
+ function isCapturableContentType(contentType) {
18
+ const lower = contentType.toLowerCase();
19
+ return CAPTURABLE_CONTENT_TYPES.some((ct) => lower.startsWith(ct));
20
+ }
21
+ function formatBytes(bytes) {
22
+ if (bytes < 1024)
23
+ return `${bytes}B`;
24
+ const kb = bytes / 1024;
25
+ if (kb < 1024)
26
+ return `${kb.toFixed(1)}KB`;
27
+ const mb = kb / 1024;
28
+ return `${mb.toFixed(1)}MB`;
29
+ }
30
+ function formatTimestamp(ts) {
31
+ const d = new Date(ts);
32
+ return (String(d.getHours()).padStart(2, "0") +
33
+ ":" +
34
+ String(d.getMinutes()).padStart(2, "0") +
35
+ ":" +
36
+ String(d.getSeconds()).padStart(2, "0"));
37
+ }
38
+ function pushToRingBuffer(buffer, entry, maxSize) {
39
+ if (buffer.length >= maxSize) {
40
+ buffer.shift();
41
+ }
42
+ buffer.push(entry);
43
+ }
44
+ /**
45
+ * Ensure session has initialized arrays for network intelligence.
46
+ * Called defensively before any read/write to these fields since
47
+ * session-manager.ts does not initialize them.
48
+ */
49
+ function ensureSessionArrays(session) {
50
+ if (!session.networkLog)
51
+ session.networkLog = [];
52
+ if (!session.consoleLog)
53
+ session.consoleLog = [];
54
+ if (!session.interceptRules)
55
+ session.interceptRules = [];
56
+ }
57
+ const CONSOLE_LEVEL_MAP = {
58
+ log: "log",
59
+ warning: "warn",
60
+ error: "error",
61
+ info: "info",
62
+ debug: "debug",
63
+ };
64
+ // ─── NetworkIntelligence ─────────────────────────────────────────────────
65
+ export class NetworkIntelligence {
66
+ /**
67
+ * Tracks active route handlers per session so we can unroute them.
68
+ * Key = session.id, Value = Map of ruleId -> { urlPattern, handler }
69
+ */
70
+ routeHandlers = new Map();
71
+ /** Registered response callbacks — invoked for every captured response */
72
+ responseCallbacks = [];
73
+ /**
74
+ * Register a callback that fires for every network response.
75
+ * Used by ApiIntelligence to capture API traffic.
76
+ */
77
+ onResponse(callback) {
78
+ this.responseCallbacks.push(callback);
79
+ }
80
+ // ── Attach listeners to a page ───────────────────────────────────
81
+ /**
82
+ * Wire up network and console capture on a page.
83
+ * Call once when a session is created. Initializes the ring buffers
84
+ * on the session and installs Playwright event listeners.
85
+ */
86
+ attachToPage(page, session) {
87
+ // Initialize arrays on the session
88
+ ensureSessionArrays(session);
89
+ // BUG-006: Track request start times via wall clock as a fallback
90
+ // for when Playwright timing() returns zeros.
91
+ const requestStartTimes = new Map();
92
+ page.on("request", (request) => {
93
+ requestStartTimes.set(request.url() + request.method(), Date.now());
94
+ });
95
+ // --- Network response listener ---
96
+ page.on("response", async (response) => {
97
+ try {
98
+ const request = response.request();
99
+ const timing = request.timing();
100
+ // BUG-006: Duration calculation with multiple fallbacks.
101
+ // 1. Prefer Playwright's built-in timing (responseEnd - requestStart)
102
+ // 2. Fall back to (responseEnd - startTime) if requestStart is missing
103
+ // 3. Final fallback: wall-clock delta from our request listener
104
+ let duration = 0;
105
+ if (timing.responseEnd > 0 && timing.requestStart > 0) {
106
+ duration = Math.round(timing.responseEnd - timing.requestStart);
107
+ }
108
+ else if (timing.responseEnd > 0 && timing.startTime > 0) {
109
+ duration = Math.round(timing.responseEnd - timing.startTime);
110
+ }
111
+ else {
112
+ // Wall-clock fallback
113
+ const key = request.url() + request.method();
114
+ const wallStart = requestStartTimes.get(key);
115
+ if (wallStart) {
116
+ duration = Date.now() - wallStart;
117
+ requestStartTimes.delete(key);
118
+ }
119
+ }
120
+ const url = response.url();
121
+ const status = response.status();
122
+ const headers = response.headers();
123
+ const contentType = headers["content-type"] ?? "";
124
+ const contentLength = headers["content-length"];
125
+ // Determine response size — prefer content-length header, fallback to body length
126
+ let responseSize = contentLength ? parseInt(contentLength, 10) : 0;
127
+ if (isNaN(responseSize))
128
+ responseSize = 0;
129
+ // Optionally capture body for small JSON/text responses
130
+ let responseBody;
131
+ if (isCapturableContentType(contentType)) {
132
+ // Check size hint before attempting body read
133
+ const sizeHint = responseSize || MAX_BODY_CAPTURE_BYTES + 1;
134
+ if (sizeHint <= MAX_BODY_CAPTURE_BYTES) {
135
+ try {
136
+ const body = await response.text();
137
+ if (body.length <= MAX_BODY_CAPTURE_BYTES) {
138
+ responseBody = body;
139
+ }
140
+ // Update size from actual body if content-length was missing
141
+ if (!responseSize) {
142
+ responseSize = body.length;
143
+ }
144
+ }
145
+ catch {
146
+ // Body may be unavailable (redirects, aborted) — skip silently
147
+ }
148
+ }
149
+ }
150
+ // If we still don't have a size, try reading the body just for size
151
+ if (!responseSize && !responseBody) {
152
+ try {
153
+ const bodyBuf = await response.body();
154
+ responseSize = bodyBuf.length;
155
+ }
156
+ catch {
157
+ // Non-critical — leave as 0
158
+ }
159
+ }
160
+ const entry = {
161
+ timestamp: Date.now(),
162
+ method: request.method(),
163
+ url,
164
+ status,
165
+ contentType: contentType.split(";")[0].trim(),
166
+ responseSize,
167
+ duration,
168
+ ...(responseBody !== undefined ? { responseBody } : {}),
169
+ };
170
+ // Defensive: ensure array exists (session may have been created before attach)
171
+ if (!session.networkLog)
172
+ session.networkLog = [];
173
+ pushToRingBuffer(session.networkLog, entry, MAX_NETWORK_ENTRIES);
174
+ // Invoke response callbacks (e.g. ApiIntelligence)
175
+ if (this.responseCallbacks.length > 0) {
176
+ const request = response.request();
177
+ let bodyBuffer = null;
178
+ try {
179
+ bodyBuffer = await response.body();
180
+ }
181
+ catch { /* body may be unavailable */ }
182
+ let reqHeaders;
183
+ try {
184
+ reqHeaders = await request.allHeaders();
185
+ }
186
+ catch { /* */ }
187
+ let reqBody;
188
+ try {
189
+ reqBody = request.postData() ?? undefined;
190
+ }
191
+ catch { /* */ }
192
+ for (const cb of this.responseCallbacks) {
193
+ try {
194
+ cb(session, url, request.method(), status, headers, bodyBuffer, duration, request.resourceType(), reqHeaders, reqBody);
195
+ }
196
+ catch { /* callbacks must not crash the server */ }
197
+ }
198
+ }
199
+ }
200
+ catch {
201
+ // Network capture must never crash the server — swallow all errors
202
+ }
203
+ });
204
+ // --- Console message listener ---
205
+ page.on("console", (msg) => {
206
+ try {
207
+ const rawType = msg.type();
208
+ const level = CONSOLE_LEVEL_MAP[rawType] ?? "log";
209
+ const entry = {
210
+ timestamp: Date.now(),
211
+ level,
212
+ text: msg.text(),
213
+ };
214
+ // Defensive: ensure array exists
215
+ if (!session.consoleLog)
216
+ session.consoleLog = [];
217
+ pushToRingBuffer(session.consoleLog, entry, MAX_CONSOLE_ENTRIES);
218
+ }
219
+ catch {
220
+ // Console capture must never crash the server
221
+ }
222
+ });
223
+ }
224
+ // ── Get network log (filtered, formatted) ────────────────────────
225
+ /**
226
+ * Returns network log entries as compact formatted text.
227
+ * Supports filtering by URL pattern (regex or substring), HTTP method,
228
+ * status code range, and content-type.
229
+ */
230
+ getNetworkLog(session, filter) {
231
+ const entries = session.networkLog ?? [];
232
+ let filtered = entries;
233
+ if (filter) {
234
+ filtered = entries.filter((e) => {
235
+ if (filter.urlPattern) {
236
+ try {
237
+ const regex = new RegExp(filter.urlPattern, "i");
238
+ if (!regex.test(e.url))
239
+ return false;
240
+ }
241
+ catch {
242
+ // Invalid regex — fall back to substring match
243
+ if (!e.url.toLowerCase().includes(filter.urlPattern.toLowerCase()))
244
+ return false;
245
+ }
246
+ }
247
+ if (filter.method && e.method.toUpperCase() !== filter.method.toUpperCase()) {
248
+ return false;
249
+ }
250
+ if (filter.statusMin !== undefined && e.status < filter.statusMin) {
251
+ return false;
252
+ }
253
+ if (filter.statusMax !== undefined && e.status > filter.statusMax) {
254
+ return false;
255
+ }
256
+ if (filter.contentType) {
257
+ if (!e.contentType
258
+ .toLowerCase()
259
+ .includes(filter.contentType.toLowerCase())) {
260
+ return false;
261
+ }
262
+ }
263
+ return true;
264
+ });
265
+ }
266
+ if (filtered.length === 0) {
267
+ return "(no matching network entries)";
268
+ }
269
+ return filtered
270
+ .map((e) => {
271
+ const parts = [
272
+ e.method,
273
+ String(e.status),
274
+ e.url,
275
+ `(${formatBytes(e.responseSize)})`,
276
+ `${e.duration}ms`,
277
+ ];
278
+ if (e.responseBody !== undefined) {
279
+ parts.push("[body captured]");
280
+ }
281
+ return parts.join(" ");
282
+ })
283
+ .join("\n");
284
+ }
285
+ // ── Get console log (filtered, formatted) ────────────────────────
286
+ /**
287
+ * Returns console log entries as compact formatted text.
288
+ * Supports filtering by log level.
289
+ */
290
+ getConsoleLog(session, filter) {
291
+ const entries = session.consoleLog ?? [];
292
+ let filtered = entries;
293
+ if (filter?.level) {
294
+ const targetLevel = filter.level.toLowerCase();
295
+ filtered = entries.filter((e) => e.level === targetLevel);
296
+ }
297
+ if (filtered.length === 0) {
298
+ return "(no matching console entries)";
299
+ }
300
+ // Pad level labels to align output
301
+ const maxLevelLen = 5; // "error" is longest
302
+ return filtered
303
+ .map((e) => {
304
+ const levelStr = `[${e.level}]`.padEnd(maxLevelLen + 2);
305
+ const timeStr = formatTimestamp(e.timestamp);
306
+ return `${levelStr} ${timeStr} ${e.text}`;
307
+ })
308
+ .join("\n");
309
+ }
310
+ // ── Add intercept rule ───────────────────────────────────────────
311
+ /**
312
+ * Add a network intercept rule to a page.
313
+ * - `block`: Aborts matching requests
314
+ * - `log`: Lets requests through (captured by the response listener)
315
+ * - `mock`: Returns a custom response with specified status, body, and content-type
316
+ *
317
+ * If a rule with the same ID already exists, it is replaced.
318
+ */
319
+ async addIntercept(page, session, rule) {
320
+ ensureSessionArrays(session);
321
+ // Remove existing rule with same ID if present
322
+ const rules = session.interceptRules;
323
+ if (rules.some((r) => r.id === rule.id)) {
324
+ await this.removeIntercept(page, session, rule.id);
325
+ }
326
+ const handler = async (route) => {
327
+ try {
328
+ switch (rule.action) {
329
+ case "block":
330
+ await route.abort("blockedbyclient");
331
+ break;
332
+ case "log":
333
+ // Let the request through — the response listener will capture it
334
+ await route.continue();
335
+ break;
336
+ case "mock": {
337
+ if (!rule.mockResponse) {
338
+ // No mock config — fall through
339
+ await route.continue();
340
+ break;
341
+ }
342
+ await route.fulfill({
343
+ status: rule.mockResponse.status,
344
+ contentType: rule.mockResponse.contentType,
345
+ body: rule.mockResponse.body,
346
+ });
347
+ break;
348
+ }
349
+ }
350
+ }
351
+ catch {
352
+ // Route may already be handled or page navigated away — ignore
353
+ try {
354
+ await route.continue();
355
+ }
356
+ catch {
357
+ // Truly dead route — nothing to do
358
+ }
359
+ }
360
+ };
361
+ // Register the route with Playwright
362
+ await page.route(rule.urlPattern, handler);
363
+ // Track the handler so we can unroute later
364
+ if (!this.routeHandlers.has(session.id)) {
365
+ this.routeHandlers.set(session.id, new Map());
366
+ }
367
+ this.routeHandlers
368
+ .get(session.id)
369
+ .set(rule.id, { urlPattern: rule.urlPattern, handler });
370
+ // Store the rule on the session
371
+ session.interceptRules.push(rule);
372
+ }
373
+ // ── Remove intercept rule ────────────────────────────────────────
374
+ /**
375
+ * Remove a previously added intercept rule by ID.
376
+ * Calls page.unroute() to deregister the Playwright route handler.
377
+ */
378
+ async removeIntercept(page, session, ruleId) {
379
+ const sessionHandlers = this.routeHandlers.get(session.id);
380
+ const entry = sessionHandlers?.get(ruleId);
381
+ if (entry) {
382
+ try {
383
+ await page.unroute(entry.urlPattern, entry.handler);
384
+ }
385
+ catch {
386
+ // Route may have already been removed — ignore
387
+ }
388
+ sessionHandlers.delete(ruleId);
389
+ if (sessionHandlers.size === 0) {
390
+ this.routeHandlers.delete(session.id);
391
+ }
392
+ }
393
+ if (session.interceptRules) {
394
+ session.interceptRules = session.interceptRules.filter((r) => r.id !== ruleId);
395
+ }
396
+ }
397
+ // ── Clear logs ───────────────────────────────────────────────────
398
+ /**
399
+ * Clear captured logs.
400
+ * @param type - Which logs to clear: 'network', 'console', or 'all' (default).
401
+ */
402
+ clearLogs(session, type) {
403
+ const target = type ?? "all";
404
+ if (target === "network" || target === "all") {
405
+ if (session.networkLog)
406
+ session.networkLog.length = 0;
407
+ }
408
+ if (target === "console" || target === "all") {
409
+ if (session.consoleLog)
410
+ session.consoleLog.length = 0;
411
+ }
412
+ }
413
+ // ── Cleanup when a session is destroyed ──────────────────────────
414
+ /**
415
+ * Remove tracked route handlers for a destroyed session.
416
+ * Call this from your session destroy logic.
417
+ */
418
+ cleanupSession(sessionId) {
419
+ this.routeHandlers.delete(sessionId);
420
+ }
421
+ }
422
+ // ─── Export singleton + class ─────────────────────────────────────────────
423
+ export const networkIntelligence = new NetworkIntelligence();
424
+ export default NetworkIntelligence;
@@ -0,0 +1,33 @@
1
+ export type PageType = "login" | "search-results" | "product" | "product-list" | "checkout" | "article" | "dashboard" | "form" | "error" | "challenge" | "landing" | "documentation" | "profile" | "media" | "feed" | "qa" | "ecommerce" | "unknown";
2
+ export interface ClassificationResult {
3
+ type: PageType;
4
+ confidence: number;
5
+ signals: string[];
6
+ allScores: Record<PageType, number>;
7
+ metadata?: {
8
+ formFields?: number;
9
+ interactiveElements?: number;
10
+ hasPassword?: boolean;
11
+ hasPrice?: boolean;
12
+ jsonLdType?: string;
13
+ ogType?: string;
14
+ };
15
+ }
16
+ export interface ClassificationInput {
17
+ url: string;
18
+ status?: number;
19
+ snapshotText: string;
20
+ meta?: {
21
+ ogType?: string;
22
+ jsonLdType?: string;
23
+ robots?: string;
24
+ description?: string;
25
+ };
26
+ }
27
+ export declare class PageClassifier {
28
+ /** Classify a page based on URL, snapshot, and optional meta */
29
+ static classify(input: ClassificationInput): ClassificationResult;
30
+ /** Quick classify from URL only (Layer 1 only, lower confidence) */
31
+ static classifyUrl(url: string): ClassificationResult;
32
+ }
33
+ export default PageClassifier;