@automatelab/ai-seo-mcp 0.1.3 → 0.3.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/LICENSE +21 -21
- package/README.md +14 -1
- package/action.yml +70 -0
- package/dist/cli/audit-cli.d.ts +3 -0
- package/dist/cli/audit-cli.d.ts.map +1 -0
- package/dist/cli/audit-cli.js +136 -0
- package/dist/cli/audit-cli.js.map +1 -0
- package/dist/index.js +246 -5
- package/dist/index.js.map +1 -1
- package/dist/lib/cache.d.ts +12 -0
- package/dist/lib/cache.d.ts.map +1 -0
- package/dist/lib/cache.js +51 -0
- package/dist/lib/cache.js.map +1 -0
- package/dist/lib/config.js +1 -1
- package/dist/lib/fetch.d.ts +5 -0
- package/dist/lib/fetch.d.ts.map +1 -1
- package/dist/lib/fetch.js +42 -1
- package/dist/lib/fetch.js.map +1 -1
- package/dist/lib/headless.d.ts +13 -0
- package/dist/lib/headless.d.ts.map +1 -0
- package/dist/lib/headless.js +73 -0
- package/dist/lib/headless.js.map +1 -0
- package/dist/lib/scorecard-html.js +94 -94
- package/dist/tools/audit-page.d.ts +11 -0
- package/dist/tools/audit-page.d.ts.map +1 -1
- package/dist/tools/audit-page.js +49 -2
- package/dist/tools/audit-page.js.map +1 -1
- package/dist/tools/audit-schema.d.ts +2 -1
- package/dist/tools/audit-schema.d.ts.map +1 -1
- package/dist/tools/audit-schema.js +2 -1
- package/dist/tools/audit-schema.js.map +1 -1
- package/dist/tools/audit-site.d.ts +41 -0
- package/dist/tools/audit-site.d.ts.map +1 -0
- package/dist/tools/audit-site.js +113 -0
- package/dist/tools/audit-site.js.map +1 -0
- package/dist/tools/audit-sitemap.d.ts +66 -0
- package/dist/tools/audit-sitemap.d.ts.map +1 -0
- package/dist/tools/audit-sitemap.js +264 -0
- package/dist/tools/audit-sitemap.js.map +1 -0
- package/dist/tools/check-technical.d.ts +2 -1
- package/dist/tools/check-technical.d.ts.map +1 -1
- package/dist/tools/check-technical.js +2 -1
- package/dist/tools/check-technical.js.map +1 -1
- package/dist/tools/diff-pages.js +2 -2
- package/dist/tools/diff-pages.js.map +1 -1
- package/dist/tools/extract-entities.d.ts +8 -1
- package/dist/tools/extract-entities.d.ts.map +1 -1
- package/dist/tools/extract-entities.js +110 -3
- package/dist/tools/extract-entities.js.map +1 -1
- package/dist/tools/rewrite-for-aeo.js +28 -28
- package/dist/tools/rewrite-for-geo.js +28 -28
- package/dist/tools/save-audit-report.d.ts +22 -0
- package/dist/tools/save-audit-report.d.ts.map +1 -0
- package/dist/tools/save-audit-report.js +133 -0
- package/dist/tools/save-audit-report.js.map +1 -0
- package/dist/tools/test-citation.d.ts +51 -0
- package/dist/tools/test-citation.d.ts.map +1 -0
- package/dist/tools/test-citation.js +159 -0
- package/dist/tools/test-citation.js.map +1 -0
- package/package.json +10 -2
- package/src/lib/crawlers.json +6 -6
- package/src/lib/schema-types.json +56 -56
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FetchResult } from "./fetch.js";
|
|
2
|
+
export declare const FETCH_CACHE_CONFIG: {
|
|
3
|
+
readonly DISABLED: boolean;
|
|
4
|
+
readonly MAX_ENTRIES: number;
|
|
5
|
+
readonly TTL_MS: number;
|
|
6
|
+
};
|
|
7
|
+
export type RenderMode = "static" | "headless";
|
|
8
|
+
export declare function cacheGet(url: string, mode?: RenderMode): FetchResult | null;
|
|
9
|
+
export declare function cacheSet(url: string, result: FetchResult, mode?: RenderMode): void;
|
|
10
|
+
export declare function cacheClear(): void;
|
|
11
|
+
export declare function cacheSize(): number;
|
|
12
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/lib/cache.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,eAAO,MAAM,kBAAkB;;;;CAIrB,CAAC;AAUX,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAM/C,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,UAAqB,GAAG,WAAW,GAAG,IAAI,CAarF;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,GAAE,UAAqB,GAAG,IAAI,CAY5F;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// In-memory LRU cache for politeFetch results.
|
|
2
|
+
// Keyed on URL (rendering mode reserved for headless support; static-only for now).
|
|
3
|
+
// Bypass via DISABLE_CACHE=true; default size 50 entries; TTL 5 minutes.
|
|
4
|
+
export const FETCH_CACHE_CONFIG = {
|
|
5
|
+
DISABLED: process.env["DISABLE_CACHE"] === "true",
|
|
6
|
+
MAX_ENTRIES: Number(process.env["FETCH_CACHE_MAX_ENTRIES"] ?? 50),
|
|
7
|
+
TTL_MS: Number(process.env["FETCH_CACHE_TTL_MS"] ?? 5 * 60 * 1000),
|
|
8
|
+
};
|
|
9
|
+
// Insertion-order Map gives us LRU semantics: delete + re-set bumps to most-recent.
|
|
10
|
+
const cache = new Map();
|
|
11
|
+
function makeKey(url, mode) {
|
|
12
|
+
return `${mode}::${url}`;
|
|
13
|
+
}
|
|
14
|
+
export function cacheGet(url, mode = "static") {
|
|
15
|
+
if (FETCH_CACHE_CONFIG.DISABLED)
|
|
16
|
+
return null;
|
|
17
|
+
const key = makeKey(url, mode);
|
|
18
|
+
const entry = cache.get(key);
|
|
19
|
+
if (!entry)
|
|
20
|
+
return null;
|
|
21
|
+
if (Date.now() > entry.expiresAt) {
|
|
22
|
+
cache.delete(key);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
// Bump to most-recent.
|
|
26
|
+
cache.delete(key);
|
|
27
|
+
cache.set(key, entry);
|
|
28
|
+
return entry.result;
|
|
29
|
+
}
|
|
30
|
+
export function cacheSet(url, result, mode = "static") {
|
|
31
|
+
if (FETCH_CACHE_CONFIG.DISABLED)
|
|
32
|
+
return;
|
|
33
|
+
const key = makeKey(url, mode);
|
|
34
|
+
cache.set(key, {
|
|
35
|
+
result,
|
|
36
|
+
expiresAt: Date.now() + FETCH_CACHE_CONFIG.TTL_MS,
|
|
37
|
+
});
|
|
38
|
+
while (cache.size > FETCH_CACHE_CONFIG.MAX_ENTRIES) {
|
|
39
|
+
const oldest = cache.keys().next().value;
|
|
40
|
+
if (oldest === undefined)
|
|
41
|
+
break;
|
|
42
|
+
cache.delete(oldest);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function cacheClear() {
|
|
46
|
+
cache.clear();
|
|
47
|
+
}
|
|
48
|
+
export function cacheSize() {
|
|
49
|
+
return cache.size;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/lib/cache.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,oFAAoF;AACpF,yEAAyE;AAIzE,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,KAAK,MAAM;IACjD,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,IAAI,EAAE,CAAC;IACjE,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;CAC1D,CAAC;AAOX,oFAAoF;AACpF,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;AAI5C,SAAS,OAAO,CAAC,GAAW,EAAE,IAAgB;IAC5C,OAAO,GAAG,IAAI,KAAK,GAAG,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,OAAmB,QAAQ;IAC/D,IAAI,kBAAkB,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;QACjC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,uBAAuB;IACvB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAClB,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACtB,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,MAAmB,EAAE,OAAmB,QAAQ;IACpF,IAAI,kBAAkB,CAAC,QAAQ;QAAE,OAAO;IACxC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC/B,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;QACb,MAAM;QACN,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,kBAAkB,CAAC,MAAM;KAClD,CAAC,CAAC;IACH,OAAO,KAAK,CAAC,IAAI,GAAG,kBAAkB,CAAC,WAAW,EAAE,CAAC;QACnD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QACzC,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM;QAChC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,KAAK,CAAC,IAAI,CAAC;AACpB,CAAC"}
|
package/dist/lib/config.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// All values are read once at server startup.
|
|
3
3
|
export const POLITE_FETCH = {
|
|
4
4
|
USER_AGENT: process.env["USER_AGENT"] ??
|
|
5
|
-
"automatelab-ai-seo-mcp/0.
|
|
5
|
+
"automatelab-ai-seo-mcp/0.3.4 (+https://github.com/AutomateLab-tech/ai-seo-mcp)",
|
|
6
6
|
TIMEOUT_MS: Number(process.env["FETCH_TIMEOUT_MS"] ?? 15_000),
|
|
7
7
|
MAX_BYTES: Number(process.env["MAX_BYTES"] ?? 5 * 1024 * 1024), // 5MB
|
|
8
8
|
INTER_REQUEST_DELAY_MS: Number(process.env["INTER_REQUEST_DELAY_MS"] ?? 1_500),
|
package/dist/lib/fetch.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type RenderMode } from "./cache.js";
|
|
1
2
|
import type { ToolError } from "../types.js";
|
|
2
3
|
export type HostDelayMap = Map<string, number>;
|
|
3
4
|
export interface FetchOptions {
|
|
@@ -9,6 +10,10 @@ export interface FetchOptions {
|
|
|
9
10
|
robotsCache?: Map<string, string>;
|
|
10
11
|
/** Allow redirects up to this many hops. Default 5 (handled by fetch internally). */
|
|
11
12
|
maxRedirects?: number;
|
|
13
|
+
/** Render mode for cache keying. Default "static". */
|
|
14
|
+
renderMode?: RenderMode;
|
|
15
|
+
/** Skip the in-memory fetch cache for this call. Robots/delays still apply. */
|
|
16
|
+
noCache?: boolean;
|
|
12
17
|
}
|
|
13
18
|
export interface FetchResult {
|
|
14
19
|
body: string;
|
package/dist/lib/fetch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/lib/fetch.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,kEAAkE;IAClE,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/lib/fetch.ts"],"names":[],"mappings":"AAMA,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAEjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,kEAAkE;IAClE,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,qBAAa,cAAe,SAAQ,KAAK;aACX,SAAS,EAAE,SAAS;gBAApB,SAAS,EAAE,SAAS;CAOjD;AAeD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,WAAW,CAAC,CAgLtB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6BxB"}
|
package/dist/lib/fetch.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { fetch as undiciFetch } from "undici";
|
|
4
4
|
import { POLITE_FETCH } from "./config.js";
|
|
5
5
|
import { checkRobotsAllowed, fetchRobotsTxt } from "./robots.js";
|
|
6
|
+
import { cacheGet, cacheSet } from "./cache.js";
|
|
7
|
+
import { renderHeadless, HeadlessUnavailableError } from "./headless.js";
|
|
6
8
|
export class ToolFetchError extends Error {
|
|
7
9
|
toolError;
|
|
8
10
|
constructor(toolError) {
|
|
@@ -42,6 +44,15 @@ export async function politeFetch(url, opts = {}) {
|
|
|
42
44
|
}
|
|
43
45
|
const hostname = parsedUrl.hostname;
|
|
44
46
|
const respectRobots = opts.respectRobots !== undefined ? opts.respectRobots : POLITE_FETCH.RESPECT_ROBOTS;
|
|
47
|
+
const renderMode = opts.renderMode ?? "static";
|
|
48
|
+
// Cache lookup. Cached entries bypass robots check, rate limiting, and the network call.
|
|
49
|
+
if (!opts.noCache) {
|
|
50
|
+
const hit = cacheGet(url, renderMode);
|
|
51
|
+
if (hit) {
|
|
52
|
+
console.error(`[fetch] cache hit ${url}`);
|
|
53
|
+
return hit;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
45
56
|
// Check inter-request delay
|
|
46
57
|
if (opts.hostDelays) {
|
|
47
58
|
const lastRequest = opts.hostDelays.get(hostname);
|
|
@@ -84,6 +95,32 @@ export async function politeFetch(url, opts = {}) {
|
|
|
84
95
|
opts.hostDelays.set(hostname, Date.now());
|
|
85
96
|
}
|
|
86
97
|
const startTime = Date.now();
|
|
98
|
+
// Headless rendering branch: hand off to Playwright, skip undici entirely.
|
|
99
|
+
if (renderMode === "headless") {
|
|
100
|
+
console.error(`[fetch] GET (headless) ${url}`);
|
|
101
|
+
try {
|
|
102
|
+
const rendered = await renderHeadless(url);
|
|
103
|
+
const duration = Date.now() - startTime;
|
|
104
|
+
console.error(`[fetch] ${rendered.statusCode} (headless) ${url} (${duration}ms)`);
|
|
105
|
+
const headlessResult = {
|
|
106
|
+
body: rendered.body,
|
|
107
|
+
finalUrl: rendered.finalUrl,
|
|
108
|
+
statusCode: rendered.statusCode,
|
|
109
|
+
headers: rendered.headers,
|
|
110
|
+
redirected: rendered.redirected,
|
|
111
|
+
};
|
|
112
|
+
if (!opts.noCache)
|
|
113
|
+
cacheSet(url, headlessResult, renderMode);
|
|
114
|
+
return headlessResult;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
if (err instanceof HeadlessUnavailableError) {
|
|
118
|
+
throw new ToolFetchError({ type: "fetch_error", url, message: err.message });
|
|
119
|
+
}
|
|
120
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
121
|
+
throw new ToolFetchError({ type: "fetch_error", url, message: `headless render failed: ${msg}` });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
87
124
|
console.error(`[fetch] GET ${url}`);
|
|
88
125
|
try {
|
|
89
126
|
const response = await undiciFetch(url, {
|
|
@@ -127,13 +164,17 @@ export async function politeFetch(url, opts = {}) {
|
|
|
127
164
|
response.headers.forEach((value, key) => {
|
|
128
165
|
headers[key] = value;
|
|
129
166
|
});
|
|
130
|
-
|
|
167
|
+
const fetchResult = {
|
|
131
168
|
body,
|
|
132
169
|
finalUrl: response.url || url,
|
|
133
170
|
statusCode: response.status,
|
|
134
171
|
headers,
|
|
135
172
|
redirected: response.redirected,
|
|
136
173
|
};
|
|
174
|
+
if (!opts.noCache) {
|
|
175
|
+
cacheSet(url, fetchResult, renderMode);
|
|
176
|
+
}
|
|
177
|
+
return fetchResult;
|
|
137
178
|
}
|
|
138
179
|
catch (err) {
|
|
139
180
|
if (err instanceof ToolFetchError)
|
package/dist/lib/fetch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/lib/fetch.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,uFAAuF;AAEvF,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/lib/fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface HeadlessResult {
|
|
2
|
+
body: string;
|
|
3
|
+
finalUrl: string;
|
|
4
|
+
statusCode: number;
|
|
5
|
+
headers: Record<string, string>;
|
|
6
|
+
redirected: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare class HeadlessUnavailableError extends Error {
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
export declare function renderHeadless(url: string): Promise<HeadlessResult>;
|
|
12
|
+
export declare function closeHeadlessBrowser(): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=headless.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headless.d.ts","sourceRoot":"","sources":["../../src/lib/headless.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,EAAE,MAAM;CAI5B;AAuBD,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA8CzE;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAS1D"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Headless browser rendering via playwright-core. Optional dep, lazy-imported.
|
|
2
|
+
// Adds 3-10s per request and a ~50MB install; opt in via render: "headless".
|
|
3
|
+
import { POLITE_FETCH } from "./config.js";
|
|
4
|
+
export class HeadlessUnavailableError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "HeadlessUnavailableError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
let browserPromise = null;
|
|
11
|
+
async function getBrowser() {
|
|
12
|
+
if (browserPromise)
|
|
13
|
+
return browserPromise;
|
|
14
|
+
browserPromise = (async () => {
|
|
15
|
+
let pw;
|
|
16
|
+
try {
|
|
17
|
+
// Indirect specifier prevents TypeScript from requiring playwright-core types at compile time;
|
|
18
|
+
// the dep is an optional peer dependency installed only when users opt into headless mode.
|
|
19
|
+
const specifier = "playwright-core";
|
|
20
|
+
pw = (await import(specifier));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new HeadlessUnavailableError("playwright-core is not installed. Install it with `npm install playwright-core` then run `npx playwright install chromium` once.");
|
|
24
|
+
}
|
|
25
|
+
return pw.chromium.launch({ headless: true });
|
|
26
|
+
})();
|
|
27
|
+
return browserPromise;
|
|
28
|
+
}
|
|
29
|
+
export async function renderHeadless(url) {
|
|
30
|
+
const browser = (await getBrowser());
|
|
31
|
+
const context = await browser.newContext({ userAgent: POLITE_FETCH.USER_AGENT });
|
|
32
|
+
const page = await context.newPage();
|
|
33
|
+
try {
|
|
34
|
+
const response = await page.goto(url, {
|
|
35
|
+
waitUntil: "networkidle",
|
|
36
|
+
timeout: POLITE_FETCH.TIMEOUT_MS,
|
|
37
|
+
});
|
|
38
|
+
if (!response) {
|
|
39
|
+
throw new Error(`No response from ${url}`);
|
|
40
|
+
}
|
|
41
|
+
const status = response.status();
|
|
42
|
+
if (status >= 400) {
|
|
43
|
+
throw new Error(`HTTP ${status}`);
|
|
44
|
+
}
|
|
45
|
+
const body = await page.content();
|
|
46
|
+
const finalUrl = response.url();
|
|
47
|
+
const headers = response.headers();
|
|
48
|
+
return {
|
|
49
|
+
body,
|
|
50
|
+
finalUrl,
|
|
51
|
+
statusCode: status,
|
|
52
|
+
headers,
|
|
53
|
+
redirected: finalUrl !== url,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await page.close();
|
|
58
|
+
await context.close();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export async function closeHeadlessBrowser() {
|
|
62
|
+
if (!browserPromise)
|
|
63
|
+
return;
|
|
64
|
+
try {
|
|
65
|
+
const browser = (await browserPromise);
|
|
66
|
+
await browser.close();
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// ignore
|
|
70
|
+
}
|
|
71
|
+
browserPromise = null;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=headless.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headless.js","sourceRoot":"","sources":["../../src/lib/headless.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,6EAA6E;AAE7E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAU3C,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACjD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAED,IAAI,cAAc,GAA4B,IAAI,CAAC;AAEnD,KAAK,UAAU,UAAU;IACvB,IAAI,cAAc;QAAE,OAAO,cAAc,CAAC;IAC1C,cAAc,GAAG,CAAC,KAAK,IAAI,EAAE;QAC3B,IAAI,EAAgE,CAAC;QACrE,IAAI,CAAC;YACH,+FAA+F;YAC/F,2FAA2F;YAC3F,MAAM,SAAS,GAAG,iBAAiB,CAAC;YACpC,EAAE,GAAG,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,CAAyB,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,wBAAwB,CAChC,kIAAkI,CACnI,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC,CAAC,EAAE,CAAC;IACL,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW;IAC9C,MAAM,OAAO,GAAG,CAAC,MAAM,UAAU,EAAE,CAgBlC,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,UAAU,EAAE,CAAC,CAAC;IACjF,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;YACpC,SAAS,EAAE,aAAa;YACxB,OAAO,EAAE,YAAY,CAAC,UAAU;SACjC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,oBAAoB,GAAG,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QACjC,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnC,OAAO;YACL,IAAI;YACJ,QAAQ;YACR,UAAU,EAAE,MAAM;YAClB,OAAO;YACP,UAAU,EAAE,QAAQ,KAAK,GAAG;SAC7B,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,IAAI,CAAC,cAAc;QAAE,OAAO;IAC5B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,CAAC,MAAM,cAAc,CAAmC,CAAC;QACzE,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;IACD,cAAc,GAAG,IAAI,CAAC;AACxB,CAAC"}
|
|
@@ -54,13 +54,13 @@ export function renderScorecardHtml(input) {
|
|
|
54
54
|
.map(([key, val]) => {
|
|
55
55
|
const label = DIMENSION_LABELS[key] ?? key;
|
|
56
56
|
const color = barColor(val);
|
|
57
|
-
return `
|
|
58
|
-
<div class="dim-row">
|
|
59
|
-
<span class="dim-label">${escHtml(label)}</span>
|
|
60
|
-
<div class="bar-track">
|
|
61
|
-
<div class="bar-fill" style="width:${val}%;background:${color};"></div>
|
|
62
|
-
</div>
|
|
63
|
-
<span class="dim-score">${val}</span>
|
|
57
|
+
return `
|
|
58
|
+
<div class="dim-row">
|
|
59
|
+
<span class="dim-label">${escHtml(label)}</span>
|
|
60
|
+
<div class="bar-track">
|
|
61
|
+
<div class="bar-fill" style="width:${val}%;background:${color};"></div>
|
|
62
|
+
</div>
|
|
63
|
+
<span class="dim-score">${val}</span>
|
|
64
64
|
</div>`;
|
|
65
65
|
})
|
|
66
66
|
.join("");
|
|
@@ -69,99 +69,99 @@ export function renderScorecardHtml(input) {
|
|
|
69
69
|
const impactBadge = b.estimated_impact
|
|
70
70
|
? `<span class="badge badge-${b.estimated_impact}">${escHtml(b.estimated_impact)}</span>`
|
|
71
71
|
: "";
|
|
72
|
-
return `
|
|
73
|
-
<div class="blocker">
|
|
74
|
-
<div class="blocker-header">
|
|
75
|
-
<span class="blocker-num">${i + 1}</span>
|
|
76
|
-
<span class="blocker-cat">${escHtml(b.category)}</span>
|
|
77
|
-
${impactBadge}
|
|
78
|
-
</div>
|
|
79
|
-
<p class="blocker-msg">${escHtml(b.message)}</p>
|
|
80
|
-
<p class="blocker-fix"><strong>Fix:</strong> ${escHtml(b.fix)}</p>
|
|
72
|
+
return `
|
|
73
|
+
<div class="blocker">
|
|
74
|
+
<div class="blocker-header">
|
|
75
|
+
<span class="blocker-num">${i + 1}</span>
|
|
76
|
+
<span class="blocker-cat">${escHtml(b.category)}</span>
|
|
77
|
+
${impactBadge}
|
|
78
|
+
</div>
|
|
79
|
+
<p class="blocker-msg">${escHtml(b.message)}</p>
|
|
80
|
+
<p class="blocker-fix"><strong>Fix:</strong> ${escHtml(b.fix)}</p>
|
|
81
81
|
</div>`;
|
|
82
82
|
})
|
|
83
83
|
.join("");
|
|
84
84
|
const noBlockers = citation_verdict.top_3_blockers.length === 0
|
|
85
85
|
? `<p class="no-blockers">No critical blockers found.</p>`
|
|
86
86
|
: "";
|
|
87
|
-
return `<!DOCTYPE html>
|
|
88
|
-
<html lang="en">
|
|
89
|
-
<head>
|
|
90
|
-
<meta charset="UTF-8">
|
|
91
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
92
|
-
<title>AI-SEO Scorecard - ${escHtml(url)}</title>
|
|
93
|
-
<style>
|
|
94
|
-
*{box-sizing:border-box;margin:0;padding:0}
|
|
95
|
-
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;padding:2rem 1rem}
|
|
96
|
-
.card{max-width:700px;margin:0 auto;background:#1e293b;border-radius:12px;overflow:hidden;box-shadow:0 4px 32px rgba(0,0,0,.4)}
|
|
97
|
-
.header{padding:2rem;border-bottom:1px solid #334155}
|
|
98
|
-
.header-url{font-size:.85rem;color:#94a3b8;word-break:break-all;margin-bottom:.5rem}
|
|
99
|
-
.header-ts{font-size:.78rem;color:#64748b}
|
|
100
|
-
.grade-row{display:flex;align-items:center;gap:1.5rem;margin-top:1.25rem}
|
|
101
|
-
.grade-badge{font-size:3rem;font-weight:800;line-height:1;color:${gradeColor(grade)}}
|
|
102
|
-
.score-num{font-size:1.75rem;font-weight:700;color:#f1f5f9}
|
|
103
|
-
.score-label{font-size:.8rem;color:#94a3b8;margin-top:.2rem}
|
|
104
|
-
.verdict-pill{display:inline-block;padding:.35rem .9rem;border-radius:999px;font-size:.85rem;font-weight:600;background:${verdictColor(citation_verdict.will_ai_cite)}22;color:${verdictColor(citation_verdict.will_ai_cite)};border:1px solid ${verdictColor(citation_verdict.will_ai_cite)}44;margin-top:.5rem}
|
|
105
|
-
.summary{font-size:.9rem;color:#cbd5e1;margin-top:.75rem;line-height:1.5}
|
|
106
|
-
.section{padding:1.5rem 2rem}
|
|
107
|
-
.section + .section{border-top:1px solid #334155}
|
|
108
|
-
.section-title{font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#64748b;margin-bottom:1rem}
|
|
109
|
-
.dim-row{display:grid;grid-template-columns:110px 1fr 36px;align-items:center;gap:.75rem;margin-bottom:.6rem}
|
|
110
|
-
.dim-label{font-size:.82rem;color:#94a3b8;text-align:right}
|
|
111
|
-
.bar-track{height:8px;background:#0f172a;border-radius:4px;overflow:hidden}
|
|
112
|
-
.bar-fill{height:100%;border-radius:4px;transition:width .3s}
|
|
113
|
-
.dim-score{font-size:.82rem;font-weight:600;color:#e2e8f0;text-align:right}
|
|
114
|
-
.blocker{background:#0f172a;border-radius:8px;padding:1rem;margin-bottom:.75rem;border-left:3px solid #ef4444}
|
|
115
|
-
.blocker:last-child{margin-bottom:0}
|
|
116
|
-
.blocker-header{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}
|
|
117
|
-
.blocker-num{width:22px;height:22px;border-radius:50%;background:#ef4444;color:#fff;font-size:.75rem;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
|
118
|
-
.blocker-cat{font-size:.8rem;font-weight:600;color:#cbd5e1;text-transform:capitalize}
|
|
119
|
-
.badge{padding:.15rem .5rem;border-radius:4px;font-size:.7rem;font-weight:600;text-transform:uppercase}
|
|
120
|
-
.badge-high{background:#ef444422;color:#ef4444}
|
|
121
|
-
.badge-medium{background:#f59e0b22;color:#f59e0b}
|
|
122
|
-
.badge-low{background:#22c55e22;color:#22c55e}
|
|
123
|
-
.blocker-msg{font-size:.82rem;color:#94a3b8;margin-bottom:.35rem;line-height:1.4}
|
|
124
|
-
.blocker-fix{font-size:.82rem;color:#64748b;line-height:1.4}
|
|
125
|
-
.blocker-fix strong{color:#94a3b8}
|
|
126
|
-
.no-blockers{font-size:.9rem;color:#22c55e}
|
|
127
|
-
.footer{padding:1rem 2rem;text-align:center;font-size:.75rem;color:#475569;border-top:1px solid #334155}
|
|
128
|
-
.footer a{color:#6366f1;text-decoration:none}
|
|
129
|
-
.footer a:hover{text-decoration:underline}
|
|
130
|
-
</style>
|
|
131
|
-
</head>
|
|
132
|
-
<body>
|
|
133
|
-
<div class="card">
|
|
134
|
-
<div class="header">
|
|
135
|
-
<div class="header-url">${escHtml(url)}</div>
|
|
136
|
-
<div class="header-ts">Audited ${escHtml(formattedDate)}</div>
|
|
137
|
-
<div class="grade-row">
|
|
138
|
-
<div>
|
|
139
|
-
<div class="grade-badge">${escHtml(grade)}</div>
|
|
140
|
-
</div>
|
|
141
|
-
<div>
|
|
142
|
-
<div class="score-num">${score}<span style="font-size:1rem;color:#64748b">/100</span></div>
|
|
143
|
-
<div class="score-label">AI Citation Score</div>
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
<div class="verdict-pill">${escHtml(verdictLabel)}</div>
|
|
147
|
-
<p class="summary">${escHtml(citation_verdict.one_line_summary)}</p>
|
|
148
|
-
</div>
|
|
149
|
-
|
|
150
|
-
<div class="section">
|
|
151
|
-
<div class="section-title">Dimension Scores</div>
|
|
152
|
-
${dimensionRows}
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
<div class="section">
|
|
156
|
-
<div class="section-title">Top Blockers</div>
|
|
157
|
-
${blockerItems}${noBlockers}
|
|
158
|
-
</div>
|
|
159
|
-
|
|
160
|
-
<div class="footer">
|
|
161
|
-
Generated by <a href="https://github.com/AutomateLab-tech/ai-seo-mcp" target="_blank" rel="noopener">AI Citation Toolkit</a> - github.com/AutomateLab-tech/ai-seo-mcp
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
</body>
|
|
87
|
+
return `<!DOCTYPE html>
|
|
88
|
+
<html lang="en">
|
|
89
|
+
<head>
|
|
90
|
+
<meta charset="UTF-8">
|
|
91
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
92
|
+
<title>AI-SEO Scorecard - ${escHtml(url)}</title>
|
|
93
|
+
<style>
|
|
94
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
95
|
+
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;padding:2rem 1rem}
|
|
96
|
+
.card{max-width:700px;margin:0 auto;background:#1e293b;border-radius:12px;overflow:hidden;box-shadow:0 4px 32px rgba(0,0,0,.4)}
|
|
97
|
+
.header{padding:2rem;border-bottom:1px solid #334155}
|
|
98
|
+
.header-url{font-size:.85rem;color:#94a3b8;word-break:break-all;margin-bottom:.5rem}
|
|
99
|
+
.header-ts{font-size:.78rem;color:#64748b}
|
|
100
|
+
.grade-row{display:flex;align-items:center;gap:1.5rem;margin-top:1.25rem}
|
|
101
|
+
.grade-badge{font-size:3rem;font-weight:800;line-height:1;color:${gradeColor(grade)}}
|
|
102
|
+
.score-num{font-size:1.75rem;font-weight:700;color:#f1f5f9}
|
|
103
|
+
.score-label{font-size:.8rem;color:#94a3b8;margin-top:.2rem}
|
|
104
|
+
.verdict-pill{display:inline-block;padding:.35rem .9rem;border-radius:999px;font-size:.85rem;font-weight:600;background:${verdictColor(citation_verdict.will_ai_cite)}22;color:${verdictColor(citation_verdict.will_ai_cite)};border:1px solid ${verdictColor(citation_verdict.will_ai_cite)}44;margin-top:.5rem}
|
|
105
|
+
.summary{font-size:.9rem;color:#cbd5e1;margin-top:.75rem;line-height:1.5}
|
|
106
|
+
.section{padding:1.5rem 2rem}
|
|
107
|
+
.section + .section{border-top:1px solid #334155}
|
|
108
|
+
.section-title{font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#64748b;margin-bottom:1rem}
|
|
109
|
+
.dim-row{display:grid;grid-template-columns:110px 1fr 36px;align-items:center;gap:.75rem;margin-bottom:.6rem}
|
|
110
|
+
.dim-label{font-size:.82rem;color:#94a3b8;text-align:right}
|
|
111
|
+
.bar-track{height:8px;background:#0f172a;border-radius:4px;overflow:hidden}
|
|
112
|
+
.bar-fill{height:100%;border-radius:4px;transition:width .3s}
|
|
113
|
+
.dim-score{font-size:.82rem;font-weight:600;color:#e2e8f0;text-align:right}
|
|
114
|
+
.blocker{background:#0f172a;border-radius:8px;padding:1rem;margin-bottom:.75rem;border-left:3px solid #ef4444}
|
|
115
|
+
.blocker:last-child{margin-bottom:0}
|
|
116
|
+
.blocker-header{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}
|
|
117
|
+
.blocker-num{width:22px;height:22px;border-radius:50%;background:#ef4444;color:#fff;font-size:.75rem;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
|
118
|
+
.blocker-cat{font-size:.8rem;font-weight:600;color:#cbd5e1;text-transform:capitalize}
|
|
119
|
+
.badge{padding:.15rem .5rem;border-radius:4px;font-size:.7rem;font-weight:600;text-transform:uppercase}
|
|
120
|
+
.badge-high{background:#ef444422;color:#ef4444}
|
|
121
|
+
.badge-medium{background:#f59e0b22;color:#f59e0b}
|
|
122
|
+
.badge-low{background:#22c55e22;color:#22c55e}
|
|
123
|
+
.blocker-msg{font-size:.82rem;color:#94a3b8;margin-bottom:.35rem;line-height:1.4}
|
|
124
|
+
.blocker-fix{font-size:.82rem;color:#64748b;line-height:1.4}
|
|
125
|
+
.blocker-fix strong{color:#94a3b8}
|
|
126
|
+
.no-blockers{font-size:.9rem;color:#22c55e}
|
|
127
|
+
.footer{padding:1rem 2rem;text-align:center;font-size:.75rem;color:#475569;border-top:1px solid #334155}
|
|
128
|
+
.footer a{color:#6366f1;text-decoration:none}
|
|
129
|
+
.footer a:hover{text-decoration:underline}
|
|
130
|
+
</style>
|
|
131
|
+
</head>
|
|
132
|
+
<body>
|
|
133
|
+
<div class="card">
|
|
134
|
+
<div class="header">
|
|
135
|
+
<div class="header-url">${escHtml(url)}</div>
|
|
136
|
+
<div class="header-ts">Audited ${escHtml(formattedDate)}</div>
|
|
137
|
+
<div class="grade-row">
|
|
138
|
+
<div>
|
|
139
|
+
<div class="grade-badge">${escHtml(grade)}</div>
|
|
140
|
+
</div>
|
|
141
|
+
<div>
|
|
142
|
+
<div class="score-num">${score}<span style="font-size:1rem;color:#64748b">/100</span></div>
|
|
143
|
+
<div class="score-label">AI Citation Score</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="verdict-pill">${escHtml(verdictLabel)}</div>
|
|
147
|
+
<p class="summary">${escHtml(citation_verdict.one_line_summary)}</p>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<div class="section">
|
|
151
|
+
<div class="section-title">Dimension Scores</div>
|
|
152
|
+
${dimensionRows}
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<div class="section">
|
|
156
|
+
<div class="section-title">Top Blockers</div>
|
|
157
|
+
${blockerItems}${noBlockers}
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<div class="footer">
|
|
161
|
+
Generated by <a href="https://github.com/AutomateLab-tech/ai-seo-mcp" target="_blank" rel="noopener">AI Citation Toolkit</a> - github.com/AutomateLab-tech/ai-seo-mcp
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</body>
|
|
165
165
|
</html>`;
|
|
166
166
|
}
|
|
167
167
|
//# sourceMappingURL=scorecard-html.js.map
|
|
@@ -5,16 +5,19 @@ export declare const auditPageInputSchema: z.ZodObject<{
|
|
|
5
5
|
include_raw_html: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
6
6
|
respect_robots: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
7
7
|
generate_report: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
8
|
+
render: z.ZodDefault<z.ZodOptional<z.ZodEnum<["static", "headless"]>>>;
|
|
8
9
|
}, "strip", z.ZodTypeAny, {
|
|
9
10
|
url: string;
|
|
10
11
|
respect_robots: boolean;
|
|
11
12
|
include_raw_html: boolean;
|
|
12
13
|
generate_report: boolean;
|
|
14
|
+
render: "static" | "headless";
|
|
13
15
|
}, {
|
|
14
16
|
url: string;
|
|
15
17
|
respect_robots?: boolean | undefined;
|
|
16
18
|
include_raw_html?: boolean | undefined;
|
|
17
19
|
generate_report?: boolean | undefined;
|
|
20
|
+
render?: "static" | "headless" | undefined;
|
|
18
21
|
}>;
|
|
19
22
|
export type AuditPageInput = z.infer<typeof auditPageInputSchema>;
|
|
20
23
|
export interface CitationVerdict {
|
|
@@ -27,6 +30,7 @@ export interface CitationVerdict {
|
|
|
27
30
|
}>;
|
|
28
31
|
one_line_summary: string;
|
|
29
32
|
}
|
|
33
|
+
export type ContentQuality = "static_html" | "ssr_likely" | "spa_empty";
|
|
30
34
|
export interface AuditPageResult extends AuditResult {
|
|
31
35
|
citation_verdict: CitationVerdict;
|
|
32
36
|
dimension_scores: {
|
|
@@ -39,6 +43,13 @@ export interface AuditPageResult extends AuditResult {
|
|
|
39
43
|
entity_density: number;
|
|
40
44
|
sitemap: number;
|
|
41
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Heuristic classification of the fetched HTML's content readiness.
|
|
48
|
+
* - `static_html`: body text >= 500 chars; audit is reliable.
|
|
49
|
+
* - `ssr_likely`: body text < 500 chars but few scripts; small/edge page or stub.
|
|
50
|
+
* - `spa_empty`: body text < 500 chars AND >5 script tags; results are degraded — re-run with render: 'headless' once available.
|
|
51
|
+
*/
|
|
52
|
+
content_quality: ContentQuality;
|
|
42
53
|
raw_html?: string;
|
|
43
54
|
report_html?: string;
|
|
44
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-page.d.ts","sourceRoot":"","sources":["../../src/tools/audit-page.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"audit-page.d.ts","sourceRoot":"","sources":["../../src/tools/audit-page.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,aAAa,CAAC;AAExD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;EAyB/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;IACjD,cAAc,EAAE,KAAK,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,EAAE,MAAM,CAAC;QACZ,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;KAC9C,CAAC,CAAC;IACH,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,YAAY,GAAG,WAAW,CAAC;AAExE,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,gBAAgB,EAAE,eAAe,CAAC;IAClC,gBAAgB,EAAE;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF;;;;;OAKG;IACH,eAAe,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAmBD,wBAAsB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAyS/E"}
|