@houtini/seo-audit-console 0.3.0 → 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/README.md +8 -3
- package/dist/audit/recon.d.ts +124 -0
- package/dist/audit/recon.d.ts.map +1 -0
- package/dist/audit/recon.js +257 -0
- package/dist/audit/recon.js.map +1 -0
- package/dist/core/AuditDatabase.d.ts.map +1 -1
- package/dist/core/AuditDatabase.js +39 -0
- package/dist/core/AuditDatabase.js.map +1 -1
- package/dist/core/DataForSeoClient.d.ts +9 -2
- package/dist/core/DataForSeoClient.d.ts.map +1 -1
- package/dist/core/DataForSeoClient.js +11 -3
- package/dist/core/DataForSeoClient.js.map +1 -1
- package/dist/core/FirecrawlClient.d.ts +39 -0
- package/dist/core/FirecrawlClient.d.ts.map +1 -0
- package/dist/core/FirecrawlClient.js +86 -0
- package/dist/core/FirecrawlClient.js.map +1 -0
- package/dist/core/SupadataClient.d.ts +29 -0
- package/dist/core/SupadataClient.d.ts.map +1 -0
- package/dist/core/SupadataClient.js +63 -0
- package/dist/core/SupadataClient.js.map +1 -0
- package/dist/core/passageScore.d.ts +40 -9
- package/dist/core/passageScore.d.ts.map +1 -1
- package/dist/core/passageScore.js +29 -3
- package/dist/core/passageScore.js.map +1 -1
- package/dist/core/reconFetch.d.ts +29 -0
- package/dist/core/reconFetch.d.ts.map +1 -0
- package/dist/core/reconFetch.js +49 -0
- package/dist/core/reconFetch.js.map +1 -0
- package/dist/core/reconResearch.d.ts +50 -0
- package/dist/core/reconResearch.d.ts.map +1 -0
- package/dist/core/reconResearch.js +122 -0
- package/dist/core/reconResearch.js.map +1 -0
- package/dist/core/serpRecon.d.ts +52 -0
- package/dist/core/serpRecon.d.ts.map +1 -0
- package/dist/core/serpRecon.js +99 -0
- package/dist/core/serpRecon.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +334 -6
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/server.json +18 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataForSeoClient.js","sourceRoot":"","sources":["../../src/core/DataForSeoClient.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoBpC,MAAM,QAAQ,GAAG,4BAA4B,CAAC;AAE9C,MAAM,OAAO,gBAAgB;IACV,IAAI,CAAS;IACb,KAAK,CAAS;IACd,KAAK,CAAoB;IAClC,KAAK,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;IAEpD,YAAY,KAAa,EAAE,QAAgB,EAAE,WAAmB,EAAE,OAAO,GAAG,EAAE;QAC5E,IAAI,CAAC,IAAI,GAAG,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC9E,IAAI,CAAC,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;QAChC,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,IAAI,CAAC,KAAK,GAAG,IAAI,QAAQ,CAAC,WAAW,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;;;;;;;;;;KAUf,CAAC,CAAC;IACL,CAAC;IAED,sFAAsF;IAC9E,SAAS,CAAI,EAAoB;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC3D,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,MAAM,CAAC,QAAgB,EAAE,IAAa;QAC5C,8EAA8E;QAC9E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAC/C,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YAC7C,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAA4B,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACvG,CAAC,CAAC,CAAC,CACN,CAAC;QACF,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAChF,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAAC,IAAI,CAAC,QAAgB,EAAE,IAAe,EAAE,SAAS,GAAG,KAAK;QAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK;aACnB,OAAO,CAAC,kFAAkF,CAAC;aAC3F,GAAG,CAAC,GAAG,CAA4E,CAAC;QACvF,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YAChE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QACzE,CAAC;QAED,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE;YAC/B,qFAAqF;YACrF,kFAAkF;YAClF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK;iBACrB,OAAO,CAAC,4EAA4E,CAAC;iBACrF,GAAG,CAAC,GAAG,CAA8D,CAAC;YACzE,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;gBACpE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YAC3E,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,GAAG,QAAQ,EAAE,EAAE;gBAChD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBACzE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;gBAC1B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;aACvC,CAAC,CAAC;YACH,MAAM,IAAI,GAAQ,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YACnC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,IAAI,EAAE,WAAW,IAAI,KAAK,EAAE,CAAC;gBAC1C,MAAM,IAAI,KAAK,CAAC,cAAc,QAAQ,YAAY,GAAG,CAAC,MAAM,IAAI,IAAI,EAAE,cAAc,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;YACvG,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAC/B,0FAA0F;YAC1F,yFAAyF;YACzF,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACpB,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;gBAC7C,MAAM,IAAI,KAAK,CACb,cAAc,QAAQ,iBAAiB,EAAE,CAAC,WAAW,IAAI,EAAE,CAAC,cAAc,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE;oBACzF,CAAC,EAAE,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,sHAAsH,CAAC,CAAC,CAAC,EAAE,CAAC,CACzJ,CAAC;YACJ,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpC,8EAA8E;YAC9E,2EAA2E;YAC3E,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,IAAI,IAAI,CAAC,CAAC;YACzH,IAAI,MAAM,EAAE,CAAC;gBACX,IAAI,CAAC,KAAK;qBACP,OAAO,CACN;;;8DAGkD,CACnD;qBACA,GAAG,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;YAC3E,CAAC;YACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,6EAA6E;IAE7E,mGAAmG;IAC3F,GAAG,CAAC,QAA0B;QACpC,IAAI,OAAO,QAAQ,KAAK,QAAQ;YAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC;QACrE,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/F,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,wBAAwB;IAC1D,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAAC,YAAY,CAAC,QAAkB,EAAE,QAA0B,EAAE,YAAY,GAAG,IAAI;QACpF,OAAO,IAAI,CAAC,IAAI,CAAC,iDAAiD,EAAE;YAClE,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,aAAa,EAAE,YAAY,EAAE;SACjE,CAAC,CAAC;IACL,CAAC;IAED
|
|
1
|
+
{"version":3,"file":"DataForSeoClient.js","sourceRoot":"","sources":["../../src/core/DataForSeoClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firecrawl client for scraping THIRD-PARTY (competitor) pages during content recon.
|
|
3
|
+
* Owned pages use our own crawler; competitor pages use Firecrawl.
|
|
4
|
+
*
|
|
5
|
+
* NB the API vs MCP divergence: the raw v2 API nests the result under `.data`
|
|
6
|
+
* ({ success, data: { markdown, metadata } }) whereas the Firecrawl MCP flattens it.
|
|
7
|
+
* We code against the API and read `json.data`.
|
|
8
|
+
*
|
|
9
|
+
* A small SQLite cache (default 7 days) avoids re-scraping the same competitor URL
|
|
10
|
+
* across a batch or a re-run — competitor pages don't change minute to minute, and it
|
|
11
|
+
* keeps recon cheap and polite.
|
|
12
|
+
*/
|
|
13
|
+
export interface ScrapeResult {
|
|
14
|
+
url: string;
|
|
15
|
+
markdown: string;
|
|
16
|
+
title: string | null;
|
|
17
|
+
cached: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare class FirecrawlClient {
|
|
20
|
+
private readonly apiKey;
|
|
21
|
+
private readonly ttlMs;
|
|
22
|
+
private readonly cache;
|
|
23
|
+
private chain;
|
|
24
|
+
constructor(apiKey: string, cacheDbPath: string, ttlDays?: number);
|
|
25
|
+
/** Serialise live scrapes — polite, and keeps us within rate limits. */
|
|
26
|
+
private serialize;
|
|
27
|
+
/** Scrape one URL to clean markdown (main content only). Cached for the TTL.
|
|
28
|
+
* proxy (v2 enum: basic | enhanced | auto — 'stealth' was the v1 name and is rejected):
|
|
29
|
+
* 'auto' retries with enhanced proxies when basic is blocked, billed at up to 5 credits.
|
|
30
|
+
* maxAge is Firecrawl's OWN cache (default 2 days) — we pass it explicitly so the freshness
|
|
31
|
+
* of a competitor page is a declared choice rather than a silent default. */
|
|
32
|
+
scrape(url: string, opts?: {
|
|
33
|
+
maxChars?: number;
|
|
34
|
+
timeoutMs?: number;
|
|
35
|
+
proxy?: 'basic' | 'enhanced' | 'auto';
|
|
36
|
+
maxAgeMs?: number;
|
|
37
|
+
}): Promise<ScrapeResult>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=FirecrawlClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FirecrawlClient.d.ts","sourceRoot":"","sources":["../../src/core/FirecrawlClient.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB;AAID,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,KAAK,CAAuC;gBAExC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,SAAI;IAiB5D,wEAAwE;IACxE,OAAO,CAAC,SAAS;IAMjB;;;;iFAI6E;IACvE,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,YAAY,CAAC;CAkDjK"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { mkdirSync } from 'node:fs';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
const BASE_URL = 'https://api.firecrawl.dev';
|
|
6
|
+
export class FirecrawlClient {
|
|
7
|
+
apiKey;
|
|
8
|
+
ttlMs;
|
|
9
|
+
cache;
|
|
10
|
+
chain = Promise.resolve();
|
|
11
|
+
constructor(apiKey, cacheDbPath, ttlDays = 7) {
|
|
12
|
+
this.apiKey = apiKey;
|
|
13
|
+
this.ttlMs = ttlDays * 86400000;
|
|
14
|
+
mkdirSync(dirname(cacheDbPath), { recursive: true });
|
|
15
|
+
this.cache = new Database(cacheDbPath);
|
|
16
|
+
this.cache.pragma('journal_mode = WAL');
|
|
17
|
+
this.cache.exec(`
|
|
18
|
+
CREATE TABLE IF NOT EXISTS firecrawl_cache (
|
|
19
|
+
cache_key TEXT PRIMARY KEY,
|
|
20
|
+
url TEXT NOT NULL,
|
|
21
|
+
markdown TEXT,
|
|
22
|
+
title TEXT,
|
|
23
|
+
fetched_at TEXT NOT NULL
|
|
24
|
+
);
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
/** Serialise live scrapes — polite, and keeps us within rate limits. */
|
|
28
|
+
serialize(fn) {
|
|
29
|
+
const result = this.chain.then(fn, fn);
|
|
30
|
+
this.chain = result.then(() => undefined, () => undefined);
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
/** Scrape one URL to clean markdown (main content only). Cached for the TTL.
|
|
34
|
+
* proxy (v2 enum: basic | enhanced | auto — 'stealth' was the v1 name and is rejected):
|
|
35
|
+
* 'auto' retries with enhanced proxies when basic is blocked, billed at up to 5 credits.
|
|
36
|
+
* maxAge is Firecrawl's OWN cache (default 2 days) — we pass it explicitly so the freshness
|
|
37
|
+
* of a competitor page is a declared choice rather than a silent default. */
|
|
38
|
+
async scrape(url, opts = {}) {
|
|
39
|
+
const key = createHash('sha256').update('scrape\n' + url).digest('hex');
|
|
40
|
+
const row = this.cache
|
|
41
|
+
.prepare('SELECT url, markdown, title, fetched_at FROM firecrawl_cache WHERE cache_key = ?')
|
|
42
|
+
.get(key);
|
|
43
|
+
if (row && Date.now() - Date.parse(row.fetched_at) < this.ttlMs) {
|
|
44
|
+
const md = opts.maxChars ? (row.markdown ?? '').slice(0, opts.maxChars) : (row.markdown ?? '');
|
|
45
|
+
return { url, markdown: md, title: row.title, cached: true };
|
|
46
|
+
}
|
|
47
|
+
return this.serialize(async () => {
|
|
48
|
+
const res = await fetch(`${BASE_URL}/v2/scrape`, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: { Authorization: `Bearer ${this.apiKey}`, 'content-type': 'application/json' },
|
|
51
|
+
body: JSON.stringify({
|
|
52
|
+
url, formats: ['markdown'], onlyMainContent: true,
|
|
53
|
+
proxy: opts.proxy ?? 'auto',
|
|
54
|
+
maxAge: opts.maxAgeMs ?? 172800000, // Firecrawl's default; explicit so it's visible
|
|
55
|
+
}),
|
|
56
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 45000),
|
|
57
|
+
});
|
|
58
|
+
const json = await res.json();
|
|
59
|
+
if (!res.ok || json?.success === false) {
|
|
60
|
+
throw new Error(`Firecrawl scrape failed for ${url}: ${res.status} ${json?.error ?? ''}`.trim());
|
|
61
|
+
}
|
|
62
|
+
// API nests under .data (MCP flattens it — we read the API shape here).
|
|
63
|
+
const data = json.data ?? json;
|
|
64
|
+
// A success:true response can still carry the TARGET's error status (403/404/challenge page):
|
|
65
|
+
// metadata.statusCode is the page's, not Firecrawl's. Treat 4xx/5xx as a failure so the
|
|
66
|
+
// caller falls back to the browser-profile fetch instead of diffing a block page.
|
|
67
|
+
const pageStatus = Number(data.metadata?.statusCode);
|
|
68
|
+
if (Number.isFinite(pageStatus) && pageStatus >= 400) {
|
|
69
|
+
throw new Error(`Firecrawl got ${pageStatus} for ${url}${data.metadata?.error ? `: ${data.metadata.error}` : ''}`);
|
|
70
|
+
}
|
|
71
|
+
const markdown = data.markdown ?? '';
|
|
72
|
+
// metadata.title is oneOf string | string[] in the v2 schema — an array would throw on bind
|
|
73
|
+
// ("can only bind numbers, strings, ...") when cached. Normalise to a single string.
|
|
74
|
+
const rawTitle = data.metadata?.title;
|
|
75
|
+
const title = Array.isArray(rawTitle) ? (rawTitle[0] ?? null) : (rawTitle ?? null);
|
|
76
|
+
this.cache
|
|
77
|
+
.prepare(`INSERT INTO firecrawl_cache (cache_key, url, markdown, title, fetched_at)
|
|
78
|
+
VALUES (?, ?, ?, ?, datetime('now'))
|
|
79
|
+
ON CONFLICT(cache_key) DO UPDATE SET markdown=excluded.markdown, title=excluded.title, fetched_at=datetime('now')`)
|
|
80
|
+
.run(key, url, markdown, title);
|
|
81
|
+
const md = opts.maxChars ? markdown.slice(0, opts.maxChars) : markdown;
|
|
82
|
+
return { url, markdown: md, title, cached: false };
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=FirecrawlClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FirecrawlClient.js","sourceRoot":"","sources":["../../src/core/FirecrawlClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supadata client for transcribing the ranking VIDEOS in content recon (YouTube, TikTok, X…).
|
|
3
|
+
* Firecrawl can't read video; this is the video leg. Owned pages → our crawler, competitor
|
|
4
|
+
* articles → Firecrawl, Reddit → its .json, videos → here.
|
|
5
|
+
*
|
|
6
|
+
* API (not the MCP): GET /v1/transcript?url=&text=true with an x-api-key header. Returns the
|
|
7
|
+
* transcript immediately (200 { content, lang }) or a jobId (202) for long videos — we poll.
|
|
8
|
+
*/
|
|
9
|
+
export interface TranscriptResult {
|
|
10
|
+
url: string;
|
|
11
|
+
content: string;
|
|
12
|
+
lang: string | null;
|
|
13
|
+
cached: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare class SupadataClient {
|
|
16
|
+
private readonly apiKey;
|
|
17
|
+
private readonly ttlMs;
|
|
18
|
+
private readonly cache;
|
|
19
|
+
constructor(apiKey: string, cacheDbPath: string, ttlDays?: number);
|
|
20
|
+
transcript(url: string, opts?: {
|
|
21
|
+
maxChars?: number;
|
|
22
|
+
lang?: string;
|
|
23
|
+
}): Promise<TranscriptResult>;
|
|
24
|
+
/** Poll /transcript/{jobId}. Statuses are queued | active | completed | failed; the docs
|
|
25
|
+
* recommend a 1s cadence and results expire an hour after completion. 30 x 1s covers the
|
|
26
|
+
* long-video generate path (2.5s x 8 = 20s was timing out on anything substantial). */
|
|
27
|
+
private pollJob;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=SupadataClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SupadataClient.d.ts","sourceRoot":"","sources":["../../src/core/SupadataClient.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE;AAIxG,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;gBAE9B,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,SAAK;IAYvD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwBzG;;2FAEuF;YACzE,OAAO;CAetB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { mkdirSync } from 'node:fs';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
const BASE_URL = 'https://api.supadata.ai/v1';
|
|
6
|
+
export class SupadataClient {
|
|
7
|
+
apiKey;
|
|
8
|
+
ttlMs;
|
|
9
|
+
cache;
|
|
10
|
+
constructor(apiKey, cacheDbPath, ttlDays = 14) {
|
|
11
|
+
this.apiKey = apiKey;
|
|
12
|
+
this.ttlMs = ttlDays * 86400000;
|
|
13
|
+
mkdirSync(dirname(cacheDbPath), { recursive: true });
|
|
14
|
+
this.cache = new Database(cacheDbPath);
|
|
15
|
+
this.cache.pragma('journal_mode = WAL');
|
|
16
|
+
this.cache.exec(`
|
|
17
|
+
CREATE TABLE IF NOT EXISTS supadata_cache (
|
|
18
|
+
cache_key TEXT PRIMARY KEY, url TEXT NOT NULL, content TEXT, lang TEXT, fetched_at TEXT NOT NULL
|
|
19
|
+
);`);
|
|
20
|
+
}
|
|
21
|
+
async transcript(url, opts = {}) {
|
|
22
|
+
const key = createHash('sha256').update('transcript\n' + url).digest('hex');
|
|
23
|
+
const row = this.cache.prepare('SELECT content, lang, fetched_at FROM supadata_cache WHERE cache_key=?')
|
|
24
|
+
.get(key);
|
|
25
|
+
if (row && Date.now() - Date.parse(row.fetched_at) < this.ttlMs) {
|
|
26
|
+
return { url, content: opts.maxChars ? (row.content ?? '').slice(0, opts.maxChars) : (row.content ?? ''), lang: row.lang, cached: true };
|
|
27
|
+
}
|
|
28
|
+
const q = new URLSearchParams({ url, text: 'true', mode: 'auto', ...(opts.lang ? { lang: opts.lang } : {}) });
|
|
29
|
+
const res = await fetch(`${BASE_URL}/transcript?${q}`, { headers: { 'x-api-key': this.apiKey }, signal: AbortSignal.timeout(45000) });
|
|
30
|
+
let json = await res.json();
|
|
31
|
+
if (!res.ok)
|
|
32
|
+
throw new Error(`Supadata transcript failed for ${url}: ${res.status} ${json?.message ?? json?.error ?? ''}`.trim());
|
|
33
|
+
// Async (202): poll the job until it completes (bounded).
|
|
34
|
+
if (json?.jobId) {
|
|
35
|
+
json = await this.pollJob(json.jobId);
|
|
36
|
+
}
|
|
37
|
+
const content = Array.isArray(json?.content) ? json.content.map((c) => c.text ?? '').join(' ') : (json?.content ?? '');
|
|
38
|
+
const lang = json?.lang ?? null;
|
|
39
|
+
this.cache.prepare(`INSERT INTO supadata_cache (cache_key, url, content, lang, fetched_at) VALUES (?,?,?,?,datetime('now'))
|
|
40
|
+
ON CONFLICT(cache_key) DO UPDATE SET content=excluded.content, lang=excluded.lang, fetched_at=datetime('now')`).run(key, url, content, lang);
|
|
41
|
+
return { url, content: opts.maxChars ? content.slice(0, opts.maxChars) : content, lang, cached: false };
|
|
42
|
+
}
|
|
43
|
+
/** Poll /transcript/{jobId}. Statuses are queued | active | completed | failed; the docs
|
|
44
|
+
* recommend a 1s cadence and results expire an hour after completion. 30 x 1s covers the
|
|
45
|
+
* long-video generate path (2.5s x 8 = 20s was timing out on anything substantial). */
|
|
46
|
+
async pollJob(jobId, tries = 30, delayMs = 1000) {
|
|
47
|
+
for (let i = 0; i < tries; i++) {
|
|
48
|
+
await new Promise(r => setTimeout(r, delayMs));
|
|
49
|
+
const res = await fetch(`${BASE_URL}/transcript/${jobId}`, { headers: { 'x-api-key': this.apiKey }, signal: AbortSignal.timeout(30000) });
|
|
50
|
+
const j = await res.json();
|
|
51
|
+
const status = j?.status;
|
|
52
|
+
if (status === 'completed' || j?.content)
|
|
53
|
+
return j;
|
|
54
|
+
if (status === 'failed') {
|
|
55
|
+
// `error` is an object in this API — interpolating it raw prints [object Object].
|
|
56
|
+
const err = typeof j?.error === 'string' ? j.error : j?.error ? JSON.stringify(j.error) : '';
|
|
57
|
+
throw new Error(`Supadata job ${jobId} failed: ${err}`.trim());
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`Supadata job ${jobId} did not complete within ${(tries * delayMs) / 1000}s`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=SupadataClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SupadataClient.js","sourceRoot":"","sources":["../../src/core/SupadataClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,19 +1,50 @@
|
|
|
1
|
+
/** The flag threshold, and the only band boundaries we assert. Raw cross-encoder logits have no
|
|
2
|
+
* intrinsic scale, so a bare "8.18" is unreadable — every consumer gets the threshold, the band
|
|
3
|
+
* convention and the run's own distribution alongside the number. */
|
|
4
|
+
export declare const WEAK_PASSAGE_THRESHOLD = 3;
|
|
5
|
+
export declare const PASSAGE_SCALE_NOTE = "Raw ms-marco-MiniLM cross-encoder logit for the single best passage vs the page's top query. Unbounded (roughly -11..+11), higher = more relevant; it is a relative measure, not a percentage. Our bands: <0 no relevant passage, 0-3 weak (flagged as weak-passage-answer), 3-6 adequate, 6+ a dense, directly-extractable answer. The bands are this tool's convention, not the model's \u2014 compare a score against the run distribution as well.";
|
|
6
|
+
export interface PassageScoreRow {
|
|
7
|
+
url: string;
|
|
8
|
+
query: string;
|
|
9
|
+
impressions: number;
|
|
10
|
+
score: number;
|
|
11
|
+
band: string;
|
|
12
|
+
relevanceProbability: number;
|
|
13
|
+
}
|
|
14
|
+
export interface PassageScoreResult {
|
|
15
|
+
scored: number;
|
|
16
|
+
flagged: number;
|
|
17
|
+
threshold: number;
|
|
18
|
+
scale: string;
|
|
19
|
+
distribution: {
|
|
20
|
+
min: number;
|
|
21
|
+
p25: number;
|
|
22
|
+
median: number;
|
|
23
|
+
p75: number;
|
|
24
|
+
max: number;
|
|
25
|
+
} | null;
|
|
26
|
+
pages: PassageScoreRow[];
|
|
27
|
+
pagesReturned: number;
|
|
28
|
+
pagesTotal: number;
|
|
29
|
+
weakest: {
|
|
30
|
+
url: string;
|
|
31
|
+
query: string;
|
|
32
|
+
score: number;
|
|
33
|
+
}[];
|
|
34
|
+
}
|
|
1
35
|
/**
|
|
2
36
|
* For each ranking page (top GSC query, has body chunks), score every chunk against that query with
|
|
3
37
|
* the local cross-encoder and persist the single best-passage score — the proxy for how neural /
|
|
4
38
|
* AI search judges whether the page has a dense, extractable answer. On-demand (ML inference cost);
|
|
5
39
|
* bounded to pages that actually rank. Powers the `weak-passage-answer` check.
|
|
40
|
+
*
|
|
41
|
+
* Returns the score for EVERY page it scored, not just the failures: "does any passage confidently
|
|
42
|
+
* answer the query" is a question about the passing pages too, and having to go to query_data for
|
|
43
|
+
* pages.max_passage_score to answer it was an undocumented extra hop.
|
|
6
44
|
*/
|
|
7
45
|
export declare function scoreSitePassages(dataDir: string, siteUrl: string, opts?: {
|
|
8
46
|
limit?: number;
|
|
9
47
|
minImpressions?: number;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
flagged: number;
|
|
13
|
-
weakest: {
|
|
14
|
-
url: string;
|
|
15
|
-
query: string;
|
|
16
|
-
score: number;
|
|
17
|
-
}[];
|
|
18
|
-
}>;
|
|
48
|
+
maxPagesReturned?: number;
|
|
49
|
+
}): Promise<PassageScoreResult>;
|
|
19
50
|
//# sourceMappingURL=passageScore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passageScore.d.ts","sourceRoot":"","sources":["../../src/core/passageScore.ts"],"names":[],"mappings":"AAKA
|
|
1
|
+
{"version":3,"file":"passageScore.d.ts","sourceRoot":"","sources":["../../src/core/passageScore.ts"],"names":[],"mappings":"AAKA;;qEAEqE;AACrE,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,eAAO,MAAM,kBAAkB,2bACyZ,CAAC;AASzb,MAAM,WAAW,eAAe;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,CAAA;CAAE;AAC/I,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5F,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC1D;AAED;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAO,GAChF,OAAO,CAAC,kBAAkB,CAAC,CAoD7B"}
|
|
@@ -2,18 +2,33 @@ import { AuditDatabase } from './AuditDatabase.js';
|
|
|
2
2
|
import { dbPathFor } from './paths.js';
|
|
3
3
|
import { gscFreshness } from './gscFreshness.js';
|
|
4
4
|
import { maxPassageScore } from './reranker.js';
|
|
5
|
+
/** The flag threshold, and the only band boundaries we assert. Raw cross-encoder logits have no
|
|
6
|
+
* intrinsic scale, so a bare "8.18" is unreadable — every consumer gets the threshold, the band
|
|
7
|
+
* convention and the run's own distribution alongside the number. */
|
|
8
|
+
export const WEAK_PASSAGE_THRESHOLD = 3;
|
|
9
|
+
export const PASSAGE_SCALE_NOTE = 'Raw ms-marco-MiniLM cross-encoder logit for the single best passage vs the page\'s top query. Unbounded (roughly -11..+11), higher = more relevant; it is a relative measure, not a percentage. Our bands: <0 no relevant passage, 0-3 weak (flagged as weak-passage-answer), 3-6 adequate, 6+ a dense, directly-extractable answer. The bands are this tool\'s convention, not the model\'s — compare a score against the run distribution as well.';
|
|
10
|
+
/** sigmoid(logit) — the model is trained with a binary relevance objective, so this reads as an
|
|
11
|
+
* approximate P(passage is relevant). Useful for "is 8.18 comfortable?" (it is: ~0.9997). */
|
|
12
|
+
const relevanceProbability = (score) => Math.round((1 / (1 + Math.exp(-score))) * 1000) / 1000;
|
|
13
|
+
const band = (s) => (s < 0 ? 'none' : s < WEAK_PASSAGE_THRESHOLD ? 'weak' : s < 6 ? 'adequate' : 'strong');
|
|
14
|
+
const quantile = (sorted, q) => sorted.length ? sorted[Math.min(sorted.length - 1, Math.floor(q * (sorted.length - 1)))] : 0;
|
|
5
15
|
/**
|
|
6
16
|
* For each ranking page (top GSC query, has body chunks), score every chunk against that query with
|
|
7
17
|
* the local cross-encoder and persist the single best-passage score — the proxy for how neural /
|
|
8
18
|
* AI search judges whether the page has a dense, extractable answer. On-demand (ML inference cost);
|
|
9
19
|
* bounded to pages that actually rank. Powers the `weak-passage-answer` check.
|
|
20
|
+
*
|
|
21
|
+
* Returns the score for EVERY page it scored, not just the failures: "does any passage confidently
|
|
22
|
+
* answer the query" is a question about the passing pages too, and having to go to query_data for
|
|
23
|
+
* pages.max_passage_score to answer it was an undocumented extra hop.
|
|
10
24
|
*/
|
|
11
25
|
export async function scoreSitePassages(dataDir, siteUrl, opts = {}) {
|
|
12
26
|
const db = new AuditDatabase(dbPathFor(dataDir, siteUrl));
|
|
13
27
|
try {
|
|
28
|
+
const empty = { scored: 0, flagged: 0, threshold: WEAK_PASSAGE_THRESHOLD, scale: PASSAGE_SCALE_NOTE, distribution: null, pages: [], pagesReturned: 0, pagesTotal: 0, weakest: [] };
|
|
14
29
|
const maxDate = gscFreshness(db.db).effectiveMax;
|
|
15
30
|
if (!maxDate)
|
|
16
|
-
return
|
|
31
|
+
return empty;
|
|
17
32
|
const minImpr = opts.minImpressions ?? 50;
|
|
18
33
|
const win = `date > date('${maxDate}', '-28 days') AND date <= '${maxDate}'`;
|
|
19
34
|
const pages = db.db.prepare(`SELECT s.page_key urlKey, s.query query, s.impr impressions, p.body_chunks bc FROM
|
|
@@ -25,6 +40,7 @@ export async function scoreSitePassages(dataDir, siteUrl, opts = {}) {
|
|
|
25
40
|
const upd = db.db.prepare(`UPDATE pages SET max_passage_score=@s, max_passage_query=@q, max_passage_impr=@i, max_passage_at=datetime('now') WHERE url_key=@u`);
|
|
26
41
|
let scored = 0, flagged = 0;
|
|
27
42
|
const weakest = [];
|
|
43
|
+
const all = [];
|
|
28
44
|
for (const pg of pages) {
|
|
29
45
|
let chunks;
|
|
30
46
|
try {
|
|
@@ -44,13 +60,23 @@ export async function scoreSitePassages(dataDir, siteUrl, opts = {}) {
|
|
|
44
60
|
const score = Math.round(max * 100) / 100;
|
|
45
61
|
upd.run({ s: score, q: pg.query, i: pg.impressions, u: pg.urlKey });
|
|
46
62
|
scored++;
|
|
47
|
-
|
|
63
|
+
all.push({ url: pg.urlKey, query: pg.query, impressions: pg.impressions, score, band: band(score), relevanceProbability: relevanceProbability(score) });
|
|
64
|
+
if (score < WEAK_PASSAGE_THRESHOLD) {
|
|
48
65
|
flagged++;
|
|
49
66
|
weakest.push({ url: pg.urlKey, query: pg.query, score });
|
|
50
67
|
}
|
|
51
68
|
}
|
|
52
69
|
weakest.sort((a, b) => a.score - b.score);
|
|
53
|
-
|
|
70
|
+
const sorted = all.map(p => p.score).sort((a, b) => a - b);
|
|
71
|
+
const cap = opts.maxPagesReturned ?? 200;
|
|
72
|
+
return {
|
|
73
|
+
scored, flagged, threshold: WEAK_PASSAGE_THRESHOLD, scale: PASSAGE_SCALE_NOTE,
|
|
74
|
+
distribution: sorted.length
|
|
75
|
+
? { min: sorted[0], p25: quantile(sorted, 0.25), median: quantile(sorted, 0.5), p75: quantile(sorted, 0.75), max: sorted[sorted.length - 1] }
|
|
76
|
+
: null,
|
|
77
|
+
pages: all.slice(0, cap), pagesReturned: Math.min(cap, all.length), pagesTotal: all.length,
|
|
78
|
+
weakest: weakest.slice(0, 20),
|
|
79
|
+
};
|
|
54
80
|
}
|
|
55
81
|
finally {
|
|
56
82
|
db.close();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passageScore.js","sourceRoot":"","sources":["../../src/core/passageScore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD
|
|
1
|
+
{"version":3,"file":"passageScore.js","sourceRoot":"","sources":["../../src/core/passageScore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owned-page access path for content recon: fetch one of OUR pages live and run the
|
|
3
|
+
* crawler's own extractor. Owned content goes through the crawler (this), never a
|
|
4
|
+
* third-party scraper — that stays for competitor pages (FirecrawlClient).
|
|
5
|
+
*/
|
|
6
|
+
export interface OwnPageView {
|
|
7
|
+
url: string;
|
|
8
|
+
status: number;
|
|
9
|
+
title: string | null;
|
|
10
|
+
h1: string | null;
|
|
11
|
+
wordCount: number;
|
|
12
|
+
headings: {
|
|
13
|
+
level: number;
|
|
14
|
+
heading: string;
|
|
15
|
+
chars: number;
|
|
16
|
+
}[];
|
|
17
|
+
fulltextLower: string;
|
|
18
|
+
jsonLdTypes: string[];
|
|
19
|
+
hasProductOrReview: boolean;
|
|
20
|
+
dateModified: string | null;
|
|
21
|
+
datePublished: string | null;
|
|
22
|
+
}
|
|
23
|
+
export declare function fetchOwnPage(url: string, hostForm?: 'apex' | 'www' | 'asis', timeoutMs?: number): Promise<OwnPageView>;
|
|
24
|
+
/** Which of these terms does the page text actually contain? Powers the coverage-gap probe. */
|
|
25
|
+
export declare function coverageProbe(fulltextLower: string, terms: string[]): {
|
|
26
|
+
term: string;
|
|
27
|
+
present: boolean;
|
|
28
|
+
}[];
|
|
29
|
+
//# sourceMappingURL=reconFetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconFetch.d.ts","sourceRoot":"","sources":["../../src/core/reconFetch.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9D,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,GAAG,KAAK,GAAG,MAAe,EAAE,SAAS,SAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,CA0CnI;AAED,+FAA+F;AAC/F,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,EAAE,CAE1G"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { extractPage } from './extract.js';
|
|
2
|
+
export async function fetchOwnPage(url, hostForm = 'asis', timeoutMs = 20000) {
|
|
3
|
+
const host = new URL(url).hostname.replace(/^www\./, '');
|
|
4
|
+
const res = await fetch(url, {
|
|
5
|
+
headers: { 'user-agent': 'seo-audit-console recon (+https://github.com/houtini-ai/seo-audit)' },
|
|
6
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
7
|
+
});
|
|
8
|
+
const html = await res.text();
|
|
9
|
+
const ex = extractPage(html, url, host, { hostForm }, res.headers.get('x-robots-tag'));
|
|
10
|
+
const chunks = ex.bodyChunks ?? [];
|
|
11
|
+
const fulltextLower = chunks.map(c => `${c.heading} ${c.text}`).join(' ').toLowerCase();
|
|
12
|
+
// jsonLd is a JSON *string* (array of raw blocks) — parse ONCE, then each block (itself a
|
|
13
|
+
// JSON string or object) may carry an @graph. (Build lesson: don't iterate the raw string.)
|
|
14
|
+
const types = new Set();
|
|
15
|
+
let dateModified = null, datePublished = null;
|
|
16
|
+
try {
|
|
17
|
+
const blocks = ex.jsonLd ? JSON.parse(ex.jsonLd) : [];
|
|
18
|
+
for (const block of blocks) {
|
|
19
|
+
const obj = typeof block === 'string' ? JSON.parse(block) : block;
|
|
20
|
+
for (const node of (obj['@graph'] ?? [obj])) {
|
|
21
|
+
const t = node['@type'];
|
|
22
|
+
(Array.isArray(t) ? t : [t]).forEach((x) => x && types.add(x));
|
|
23
|
+
if (node.dateModified && !dateModified)
|
|
24
|
+
dateModified = String(node.dateModified).slice(0, 10);
|
|
25
|
+
if (node.datePublished && !datePublished)
|
|
26
|
+
datePublished = String(node.datePublished).slice(0, 10);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch { /* malformed JSON-LD — leave types empty rather than throw */ }
|
|
31
|
+
return {
|
|
32
|
+
url,
|
|
33
|
+
status: res.status,
|
|
34
|
+
title: ex.title,
|
|
35
|
+
h1: ex.h1,
|
|
36
|
+
wordCount: ex.wordCount ?? fulltextLower.split(/\s+/).length,
|
|
37
|
+
headings: chunks.filter(c => c.heading).map(c => ({ level: c.level, heading: c.heading, chars: c.text.length })),
|
|
38
|
+
fulltextLower,
|
|
39
|
+
jsonLdTypes: [...types],
|
|
40
|
+
hasProductOrReview: types.has('Product') || types.has('Review') || types.has('AggregateRating'),
|
|
41
|
+
dateModified,
|
|
42
|
+
datePublished,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Which of these terms does the page text actually contain? Powers the coverage-gap probe. */
|
|
46
|
+
export function coverageProbe(fulltextLower, terms) {
|
|
47
|
+
return terms.map(t => ({ term: t, present: fulltextLower.includes(t.toLowerCase()) }));
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=reconFetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconFetch.js","sourceRoot":"","sources":["../../src/core/reconFetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { FirecrawlClient } from './FirecrawlClient.js';
|
|
2
|
+
import type { SupadataClient } from './SupadataClient.js';
|
|
3
|
+
/** User-agent modes for the free HTTP fetch. 'browser' mimics a real visit arriving from Google
|
|
4
|
+
* (referer + cross-site fetch metadata) — this is what gets past Reddit and the mid-tier.
|
|
5
|
+
* 'googlebot' spoofs Googlebot's UA (UA-gated sites only; strict-verified Cloudflare rejects it).
|
|
6
|
+
* Cloudflare *managed-challenge* sites (e.g. PCMag) reject every HTTP-only client regardless. */
|
|
7
|
+
export type UaMode = 'browser' | 'googlebot';
|
|
8
|
+
/**
|
|
9
|
+
* Route a competitor URL to the right fetcher by who owns the source:
|
|
10
|
+
* YouTube/TikTok/X → Supadata (transcript) — Firecrawl can't read video
|
|
11
|
+
* Reddit → its own .json endpoint — Firecrawl hard-blocks Reddit
|
|
12
|
+
* everything else → Firecrawl (markdown, proxy:auto for bot-protected sites)
|
|
13
|
+
* Each returns a uniform shape so the recon packet is easy for the research session to diff.
|
|
14
|
+
*/
|
|
15
|
+
export interface CompetitorContent {
|
|
16
|
+
url: string;
|
|
17
|
+
kind: 'video' | 'reddit' | 'page';
|
|
18
|
+
via: 'supadata' | 'reddit-json' | 'firecrawl' | 'plain-fetch' | 'none';
|
|
19
|
+
title: string | null;
|
|
20
|
+
content: string;
|
|
21
|
+
cached: boolean;
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Exact host or subdomain match — NEVER a substring test. A substring `/x\.com/` classified
|
|
25
|
+
* pimax.com as video and sent every Shopify product page to the transcriber, which 400'd
|
|
26
|
+
* (build lesson, 2026-08-07: that one regex is why scrapeCompetitors returned almost nothing). */
|
|
27
|
+
export declare function hostIsOneOf(host: string, domains: string[]): boolean;
|
|
28
|
+
/** Which fetcher a URL will be routed to — exposed so recon can report routing per URL
|
|
29
|
+
* instead of leaving a silent misroute to look like an empty competitor set. */
|
|
30
|
+
export declare function routeFor(url: string): CompetitorContent['kind'];
|
|
31
|
+
/** Reddit serves clean JSON at url.json — title + selftext + top comments, no scraper needed.
|
|
32
|
+
* Needs the browser header profile (UA + Google referer) — a bot UA gets a 403. */
|
|
33
|
+
export declare function fetchRedditJson(url: string, maxChars?: number, ua?: UaMode): Promise<{
|
|
34
|
+
title: string | null;
|
|
35
|
+
content: string;
|
|
36
|
+
}>;
|
|
37
|
+
/** Free HTTP fallback for pages Firecrawl refuses (its blocklist covers major publishers).
|
|
38
|
+
* Fetches with a browser-like UA and reuses our own extractor for clean main-content text. */
|
|
39
|
+
export declare function fetchPlainPage(url: string, maxChars?: number, ua?: UaMode): Promise<{
|
|
40
|
+
title: string | null;
|
|
41
|
+
content: string;
|
|
42
|
+
}>;
|
|
43
|
+
export declare function fetchCompetitorContent(url: string, clients: {
|
|
44
|
+
firecrawl: FirecrawlClient | null;
|
|
45
|
+
supadata: SupadataClient | null;
|
|
46
|
+
}, opts?: {
|
|
47
|
+
maxChars?: number;
|
|
48
|
+
ua?: UaMode;
|
|
49
|
+
}): Promise<CompetitorContent>;
|
|
50
|
+
//# sourceMappingURL=reconResearch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconResearch.d.ts","sourceRoot":"","sources":["../../src/core/reconResearch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAQ1D;;;iGAGiG;AACjG,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,WAAW,CAAC;AAmB7C;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClC,GAAG,EAAE,UAAU,GAAG,aAAa,GAAG,WAAW,GAAG,aAAa,GAAG,MAAM,CAAC;IACvE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD;;kGAEkG;AAClG,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAGpE;AAKD;gFACgF;AAChF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAG/D;AAED;mFACmF;AACnF,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAO,EAAE,EAAE,GAAE,MAAkB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAkB9I;AAED;8FAC8F;AAC9F,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAO,EAAE,EAAE,GAAE,MAAkB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAQ7I;AAED,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;IAAE,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAA;CAAE,EAC/E,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5C,OAAO,CAAC,iBAAiB,CAAC,CA8B5B"}
|