seo 0.2.21 → 0.2.22
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 +10 -1
- package/dist/cli.js +2 -2
- package/dist/{dist-B-zhNcyH.js → dist-DNsXiggv.js} +81 -81
- package/dist/dist-DNsXiggv.js.map +1 -0
- package/dist/{dist-BITHJak_.js → dist-DhQXrgg2.js} +1 -1
- package/dist/{esm-CSE-hgHf.js → esm-insIkRuo.js} +2 -2
- package/dist/{esm-CSE-hgHf.js.map → esm-insIkRuo.js.map} +1 -1
- package/dist/index.d.ts +261 -50
- package/dist/index.js +963 -424
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +1078 -423
- package/dist/mcp.js.map +1 -1
- package/evals/local-search-demand.json +41 -0
- package/package.json +1 -1
- package/dist/dist-B-zhNcyH.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire as __seoCreateRequire } from 'node:module'; const require = __seoCreateRequire(import.meta.url);
|
|
2
|
-
import { n as version, t as load } from "./esm-
|
|
2
|
+
import { n as version, t as load } from "./esm-insIkRuo.js";
|
|
3
3
|
import { Headers, fetch as fetch$1 } from "undici";
|
|
4
4
|
import { chmodSync, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { arch, homedir, platform, totalmem } from "node:os";
|
|
@@ -2249,7 +2249,7 @@ function aiReferralSourceForValue(value) {
|
|
|
2249
2249
|
}
|
|
2250
2250
|
//#endregion
|
|
2251
2251
|
//#region packages/core/src/analyze/ai-referrals-analysis.ts
|
|
2252
|
-
function compareText$
|
|
2252
|
+
function compareText$19(left, right) {
|
|
2253
2253
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
2254
2254
|
}
|
|
2255
2255
|
function metric$1(row, name) {
|
|
@@ -2338,7 +2338,7 @@ function analyzeAiReferralRows(input) {
|
|
|
2338
2338
|
const generalCaveat = "Google Analytics only attributes visits that arrive with a recognized session source; some AI visits appear as direct, unassigned, or another source.";
|
|
2339
2339
|
const possiblyTruncated = input.sourceDiscovery.truncated || input.detail.truncated || input.totalUsersEvidence.truncated;
|
|
2340
2340
|
const allLandingPages = [...pageTotals.entries()].map(([landingPage, metrics]) => {
|
|
2341
|
-
const topSourceId = [...metrics.sources.entries()].sort((left, right) => right[1] - left[1] || compareText$
|
|
2341
|
+
const topSourceId = [...metrics.sources.entries()].sort((left, right) => right[1] - left[1] || compareText$19(left[0], right[0]))[0]?.[0];
|
|
2342
2342
|
const topSource = topSourceId ? sourceTotals.get(topSourceId)?.definition : void 0;
|
|
2343
2343
|
if (!topSource) throw new SeoError("PROVIDER_UNAVAILABLE", "Could not resolve the top AI referral source.");
|
|
2344
2344
|
return {
|
|
@@ -2352,7 +2352,7 @@ function analyzeAiReferralRows(input) {
|
|
|
2352
2352
|
label: topSource.label
|
|
2353
2353
|
}
|
|
2354
2354
|
};
|
|
2355
|
-
}).sort((left, right) => right.sessions - left.sessions || compareText$
|
|
2355
|
+
}).sort((left, right) => right.sessions - left.sessions || compareText$19(left.landingPage, right.landingPage));
|
|
2356
2356
|
const landingPages = allLandingPages.slice(0, input.resultLimit);
|
|
2357
2357
|
const selection = {
|
|
2358
2358
|
limit: input.resultLimit,
|
|
@@ -2397,19 +2397,19 @@ function analyzeAiReferralRows(input) {
|
|
|
2397
2397
|
id: source.definition.id,
|
|
2398
2398
|
label: source.definition.label,
|
|
2399
2399
|
source: source.definition.label,
|
|
2400
|
-
observedSessionSources: [...source.observedSessionSources].sort(compareText$
|
|
2400
|
+
observedSessionSources: [...source.observedSessionSources].sort(compareText$19),
|
|
2401
2401
|
sessions: source.sessions,
|
|
2402
2402
|
eventCount: source.eventCount,
|
|
2403
2403
|
totalUsers: null,
|
|
2404
2404
|
shareOfAiSessions: total.sessions ? source.sessions / total.sessions : 0,
|
|
2405
2405
|
share: total.sessions ? source.sessions / total.sessions : 0
|
|
2406
|
-
})).sort((left, right) => right.sessions - left.sessions || compareText$
|
|
2406
|
+
})).sort((left, right) => right.sessions - left.sessions || compareText$19(left.id, right.id)),
|
|
2407
2407
|
landingPages,
|
|
2408
2408
|
daily: [...dailyTotals.entries()].map(([date, metrics]) => ({
|
|
2409
2409
|
date,
|
|
2410
2410
|
...metrics,
|
|
2411
2411
|
totalUsers: null
|
|
2412
|
-
})).sort((left, right) => compareText$
|
|
2412
|
+
})).sort((left, right) => compareText$19(left.date, right.date)),
|
|
2413
2413
|
caveats: [
|
|
2414
2414
|
generalCaveat,
|
|
2415
2415
|
"Attribution uses Google Analytics sessionSource only; landing-page text, medium text, and event referrers are never treated as referral sources.",
|
|
@@ -2499,7 +2499,7 @@ function sessionSourceInListFilter(values) {
|
|
|
2499
2499
|
//#endregion
|
|
2500
2500
|
//#region packages/core/src/analyze/ai-referrals.ts
|
|
2501
2501
|
const DEFAULT_MAX_ROWS$1 = 1e5;
|
|
2502
|
-
const MAX_ROWS = 1e5;
|
|
2502
|
+
const MAX_ROWS$1 = 1e5;
|
|
2503
2503
|
const DEFAULT_RESULT_LIMIT = 25;
|
|
2504
2504
|
const MAX_RESULT_LIMIT = 1e3;
|
|
2505
2505
|
function validIsoDate$1(value) {
|
|
@@ -2525,7 +2525,7 @@ function reportRange(input) {
|
|
|
2525
2525
|
function maxRows(input) {
|
|
2526
2526
|
if (input.maxRows !== void 0 && input.limit !== void 0 && input.maxRows !== input.limit) throw new SeoError("INVALID_INPUT", "Use maxRows or the legacy limit option, not both.");
|
|
2527
2527
|
const value = input.maxRows ?? input.limit ?? DEFAULT_MAX_ROWS$1;
|
|
2528
|
-
if (!Number.isInteger(value) || value < 1 || value > MAX_ROWS) throw new SeoError("INVALID_INPUT", `maxRows must be a whole number between 1 and ${MAX_ROWS}.`);
|
|
2528
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_ROWS$1) throw new SeoError("INVALID_INPUT", `maxRows must be a whole number between 1 and ${MAX_ROWS$1}.`);
|
|
2529
2529
|
return value;
|
|
2530
2530
|
}
|
|
2531
2531
|
function resultLimit(input) {
|
|
@@ -5948,7 +5948,7 @@ function tokenStem(token) {
|
|
|
5948
5948
|
function tokensMatch(a, b) {
|
|
5949
5949
|
return a === b || tokenStem(a) === tokenStem(b);
|
|
5950
5950
|
}
|
|
5951
|
-
function includesTokenSequence(queryTokens, termTokens) {
|
|
5951
|
+
function includesTokenSequence$1(queryTokens, termTokens) {
|
|
5952
5952
|
if (!termTokens.length || termTokens.length > queryTokens.length) return false;
|
|
5953
5953
|
for (let index = 0; index <= queryTokens.length - termTokens.length; index++) if (termTokens.every((termToken, offset) => tokensMatch(queryTokens[index + offset] ?? "", termToken))) return true;
|
|
5954
5954
|
return false;
|
|
@@ -5977,7 +5977,7 @@ function isBrandQuery(query, brandTerms) {
|
|
|
5977
5977
|
const compactMatches = (compactTermLength >= 6 || /[^\p{ASCII}]/u.test(compactTerm) && compactTermLength >= 2) && compactQuery.includes(compactTerm);
|
|
5978
5978
|
const termTokens = normalizedTerm.split(" ");
|
|
5979
5979
|
if (termTokens.length === 1) return queryTokens.some((queryToken) => tokensMatch(queryToken, normalizedTerm)) || compactMatches;
|
|
5980
|
-
return includesTokenSequence(queryTokens, termTokens) || compactMatches;
|
|
5980
|
+
return includesTokenSequence$1(queryTokens, termTokens) || compactMatches;
|
|
5981
5981
|
});
|
|
5982
5982
|
}
|
|
5983
5983
|
function shouldExcludeBrandQuery(input) {
|
|
@@ -6289,7 +6289,7 @@ function resolveQueryOpportunityInput(input, now) {
|
|
|
6289
6289
|
}
|
|
6290
6290
|
//#endregion
|
|
6291
6291
|
//#region packages/core/src/analyze/query-opportunity-rows.ts
|
|
6292
|
-
function compareText$
|
|
6292
|
+
function compareText$18(left, right) {
|
|
6293
6293
|
const leftPoints = [...left].map((value) => value.codePointAt(0) ?? 0);
|
|
6294
6294
|
const rightPoints = [...right].map((value) => value.codePointAt(0) ?? 0);
|
|
6295
6295
|
for (let index = 0; index < Math.min(leftPoints.length, rightPoints.length); index++) {
|
|
@@ -6299,9 +6299,9 @@ function compareText$17(left, right) {
|
|
|
6299
6299
|
return leftPoints.length - rightPoints.length;
|
|
6300
6300
|
}
|
|
6301
6301
|
function compareQueryOpportunityRows(left, right) {
|
|
6302
|
-
return right.impressions - left.impressions || right.clicks - left.clicks || left.position - right.position || compareText$
|
|
6302
|
+
return right.impressions - left.impressions || right.clicks - left.clicks || left.position - right.position || compareText$18(left.query, right.query);
|
|
6303
6303
|
}
|
|
6304
|
-
function validRow$
|
|
6304
|
+
function validRow$4(row, query) {
|
|
6305
6305
|
return row.keys.length === 1 && Boolean(query) && Number.isFinite(row.clicks) && Number.isFinite(row.impressions) && Number.isFinite(row.ctr) && Number.isFinite(row.position) && row.clicks >= 0 && row.impressions > 0 && row.clicks <= row.impressions && row.ctr >= 0 && row.ctr <= 1 && row.position > 0;
|
|
6306
6306
|
}
|
|
6307
6307
|
function signature(row) {
|
|
@@ -6311,7 +6311,7 @@ function normalizeQueryOpportunityRows(rows, selection) {
|
|
|
6311
6311
|
const grouped = /* @__PURE__ */ new Map();
|
|
6312
6312
|
for (const row of rows) {
|
|
6313
6313
|
const query = row.keys[0]?.trim() ?? "";
|
|
6314
|
-
if (!validRow$
|
|
6314
|
+
if (!validRow$4(row, query)) {
|
|
6315
6315
|
selection.invalidRows++;
|
|
6316
6316
|
continue;
|
|
6317
6317
|
}
|
|
@@ -7323,11 +7323,11 @@ function selectBenchmarkRows$1(input) {
|
|
|
7323
7323
|
}
|
|
7324
7324
|
//#endregion
|
|
7325
7325
|
//#region packages/core/src/analyze/page-opportunities-analysis.ts
|
|
7326
|
-
function compareText$
|
|
7326
|
+
function compareText$17(left, right) {
|
|
7327
7327
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
7328
7328
|
}
|
|
7329
7329
|
function compareItems$2(left, right) {
|
|
7330
|
-
return (right.estimatedCtrClickShortfall ?? -1) - (left.estimatedCtrClickShortfall ?? -1) || right.impressions - left.impressions || left.position - right.position || compareText$
|
|
7330
|
+
return (right.estimatedCtrClickShortfall ?? -1) - (left.estimatedCtrClickShortfall ?? -1) || right.impressions - left.impressions || left.position - right.position || compareText$17(left.query, right.query) || compareText$17(left.url, right.url);
|
|
7331
7331
|
}
|
|
7332
7332
|
function analyzeRow(input) {
|
|
7333
7333
|
const query = input.row.keys[0]?.trim() ?? "";
|
|
@@ -12024,19 +12024,19 @@ function sanitizeIssues(issues) {
|
|
|
12024
12024
|
function sanitizeMessages(values = []) {
|
|
12025
12025
|
return values.map((value) => sanitizeTenantString(value));
|
|
12026
12026
|
}
|
|
12027
|
-
function compareText$
|
|
12027
|
+
function compareText$16(left, right) {
|
|
12028
12028
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
12029
12029
|
}
|
|
12030
12030
|
function sortPages(pages) {
|
|
12031
|
-
return [...pages].sort((left, right) => compareText$
|
|
12031
|
+
return [...pages].sort((left, right) => compareText$16(left.url, right.url));
|
|
12032
12032
|
}
|
|
12033
12033
|
function sortRequests(requests) {
|
|
12034
12034
|
return [...requests].sort((left, right) => {
|
|
12035
|
-
const byUrl = compareText$
|
|
12035
|
+
const byUrl = compareText$16(left.requestedUrl, right.requestedUrl);
|
|
12036
12036
|
if (byUrl) return byUrl;
|
|
12037
|
-
const byOutcome = compareText$
|
|
12037
|
+
const byOutcome = compareText$16(left.outcome, right.outcome);
|
|
12038
12038
|
if (byOutcome) return byOutcome;
|
|
12039
|
-
return compareText$
|
|
12039
|
+
return compareText$16(left.outcome === "response" ? `${left.status}:${left.finalUrl}` : left.outcome === "skipped" ? left.reason : `${left.failureKind}:${left.error}`, right.outcome === "response" ? `${right.status}:${right.finalUrl}` : right.outcome === "skipped" ? right.reason : `${right.failureKind}:${right.error}`);
|
|
12040
12040
|
});
|
|
12041
12041
|
}
|
|
12042
12042
|
function summarizeCrawlReport(input) {
|
|
@@ -12258,8 +12258,8 @@ function createCrawlReport(input) {
|
|
|
12258
12258
|
...input.externalLinkVerification ? { externalLinkVerification: sanitizeTenantValue(input.externalLinkVerification) } : {},
|
|
12259
12259
|
...input.siteChecks ? { siteChecks: sanitizeTenantValue(input.siteChecks) } : {},
|
|
12260
12260
|
...input.agentDiscovery ? { agentDiscovery: sanitizeTenantValue(input.agentDiscovery) } : {},
|
|
12261
|
-
warnings: sanitizeMessages(input.warnings).sort(compareText$
|
|
12262
|
-
caveats: sanitizeMessages(input.caveats).sort(compareText$
|
|
12261
|
+
warnings: sanitizeMessages(input.warnings).sort(compareText$16),
|
|
12262
|
+
caveats: sanitizeMessages(input.caveats).sort(compareText$16)
|
|
12263
12263
|
};
|
|
12264
12264
|
}
|
|
12265
12265
|
function normalizeLoadedCrawlReport(report) {
|
|
@@ -12289,8 +12289,8 @@ function normalizeLoadedCrawlReport(report) {
|
|
|
12289
12289
|
pages,
|
|
12290
12290
|
issues,
|
|
12291
12291
|
issueGroups: groupCrawlIssues(issues),
|
|
12292
|
-
warnings: sanitizeMessages(report.warnings).sort(compareText$
|
|
12293
|
-
caveats: sanitizeMessages(report.caveats).sort(compareText$
|
|
12292
|
+
warnings: sanitizeMessages(report.warnings).sort(compareText$16),
|
|
12293
|
+
caveats: sanitizeMessages(report.caveats).sort(compareText$16)
|
|
12294
12294
|
};
|
|
12295
12295
|
}
|
|
12296
12296
|
//#endregion
|
|
@@ -14167,12 +14167,12 @@ function urlPath(url) {
|
|
|
14167
14167
|
return "";
|
|
14168
14168
|
}
|
|
14169
14169
|
}
|
|
14170
|
-
function compareText$
|
|
14170
|
+
function compareText$15(left, right) {
|
|
14171
14171
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
14172
14172
|
}
|
|
14173
14173
|
function landingPageValuesFromRows(rows) {
|
|
14174
14174
|
const orderedRows = [...rows].sort((left, right) => {
|
|
14175
|
-
return compareText$
|
|
14175
|
+
return compareText$15(normalizePath(left.landingPagePlusQueryString ?? ""), normalizePath(right.landingPagePlusQueryString ?? "")) || compareText$15(left.landingPagePlusQueryString ?? "", right.landingPagePlusQueryString ?? "") || compareText$15(left.sessions ?? "", right.sessions ?? "") || compareText$15(left.totalUsers ?? "", right.totalUsers ?? "") || compareText$15(left.conversions ?? "", right.conversions ?? "");
|
|
14176
14176
|
});
|
|
14177
14177
|
const values = /* @__PURE__ */ new Map();
|
|
14178
14178
|
for (const row of orderedRows) {
|
|
@@ -16858,7 +16858,7 @@ function countLabel(count, singular, pluralLabel = `${singular}s`) {
|
|
|
16858
16858
|
//#region packages/core/src/analyze/crawler/okf.ts
|
|
16859
16859
|
const OKF_DEFAULT_CONCEPTS = 500;
|
|
16860
16860
|
const OKF_MAX_CONCEPTS = 5e3;
|
|
16861
|
-
function compareText$
|
|
16861
|
+
function compareText$14(left, right) {
|
|
16862
16862
|
const leftPoints = [...left].map((value) => value.codePointAt(0) ?? 0);
|
|
16863
16863
|
const rightPoints = [...right].map((value) => value.codePointAt(0) ?? 0);
|
|
16864
16864
|
for (let index = 0; index < Math.min(leftPoints.length, rightPoints.length); index++) {
|
|
@@ -16948,7 +16948,7 @@ function okfTitle(value, report) {
|
|
|
16948
16948
|
}
|
|
16949
16949
|
function selectedOkfPages(report, requestedLimit) {
|
|
16950
16950
|
const limit = okfConceptLimit(requestedLimit);
|
|
16951
|
-
const eligible = report.pages.filter((page) => page.indexable && page.status >= 200 && page.status < 300 && !page.blocked && !page.error).sort((left, right) => (right.searchMetrics?.clicks ?? 0) - (left.searchMetrics?.clicks ?? 0) || (right.searchMetrics?.impressions ?? 0) - (left.searchMetrics?.impressions ?? 0) || (right.internalLinkAuthorityScore ?? 0) - (left.internalLinkAuthorityScore ?? 0) || (right.internalInlinkCount ?? 0) - (left.internalInlinkCount ?? 0) || compareText$
|
|
16951
|
+
const eligible = report.pages.filter((page) => page.indexable && page.status >= 200 && page.status < 300 && !page.blocked && !page.error).sort((left, right) => (right.searchMetrics?.clicks ?? 0) - (left.searchMetrics?.clicks ?? 0) || (right.searchMetrics?.impressions ?? 0) - (left.searchMetrics?.impressions ?? 0) || (right.internalLinkAuthorityScore ?? 0) - (left.internalLinkAuthorityScore ?? 0) || (right.internalInlinkCount ?? 0) - (left.internalInlinkCount ?? 0) || compareText$14(left.finalUrl, right.finalUrl));
|
|
16952
16952
|
const unique = /* @__PURE__ */ new Map();
|
|
16953
16953
|
for (const page of eligible) if (!unique.has(page.finalUrl)) unique.set(page.finalUrl, page);
|
|
16954
16954
|
const uniquePages = [...unique.values()];
|
|
@@ -18446,7 +18446,7 @@ const CTR_DEFAULT_MIN_IMPRESSIONS = 200;
|
|
|
18446
18446
|
const CTR_MAX_MIN_IMPRESSIONS = 1e9;
|
|
18447
18447
|
const CTR_DEFAULT_LIMIT = 25;
|
|
18448
18448
|
const CTR_MAX_LIMIT = 100;
|
|
18449
|
-
function compareText$
|
|
18449
|
+
function compareText$13(left, right) {
|
|
18450
18450
|
const leftPoints = [...left].map((value) => value.codePointAt(0) ?? 0);
|
|
18451
18451
|
const rightPoints = [...right].map((value) => value.codePointAt(0) ?? 0);
|
|
18452
18452
|
for (let index = 0; index < Math.min(leftPoints.length, rightPoints.length); index++) {
|
|
@@ -18455,7 +18455,7 @@ function compareText$12(left, right) {
|
|
|
18455
18455
|
}
|
|
18456
18456
|
return leftPoints.length - rightPoints.length;
|
|
18457
18457
|
}
|
|
18458
|
-
function boundedInteger$
|
|
18458
|
+
function boundedInteger$7(value, fallback, minimum, maximum) {
|
|
18459
18459
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
18460
18460
|
return Math.min(maximum, Math.max(minimum, Math.floor(value)));
|
|
18461
18461
|
}
|
|
@@ -18494,7 +18494,7 @@ function aggregateCtrRows(rows, selection) {
|
|
|
18494
18494
|
weightedPosition: 0,
|
|
18495
18495
|
sourceRows: 0
|
|
18496
18496
|
};
|
|
18497
|
-
if (compareText$
|
|
18497
|
+
if (compareText$13(query, group.query) < 0) group.query = query;
|
|
18498
18498
|
group.clicks += row.clicks;
|
|
18499
18499
|
group.impressions += row.impressions;
|
|
18500
18500
|
group.weightedPosition += row.position * row.impressions;
|
|
@@ -18532,8 +18532,8 @@ function initialSelection(sourceRows) {
|
|
|
18532
18532
|
};
|
|
18533
18533
|
}
|
|
18534
18534
|
function analyzeCtrUnderperformersFromRows(input) {
|
|
18535
|
-
const minImpressions = boundedInteger$
|
|
18536
|
-
const limit = boundedInteger$
|
|
18535
|
+
const minImpressions = boundedInteger$7(input.minImpressions, 200, 1, CTR_MAX_MIN_IMPRESSIONS);
|
|
18536
|
+
const limit = boundedInteger$7(input.limit, 25, 1, 100);
|
|
18537
18537
|
const selection = initialSelection(input.rows.length);
|
|
18538
18538
|
const benchmarkRows = aggregateCtrRows(input.rows, selection).filter((row) => {
|
|
18539
18539
|
const query = row.keys[0] ?? "";
|
|
@@ -18598,7 +18598,7 @@ function analyzeCtrUnderperformersFromRows(input) {
|
|
|
18598
18598
|
confidence: "low"
|
|
18599
18599
|
}
|
|
18600
18600
|
};
|
|
18601
|
-
}).filter((item) => item.actualCtr < item.expectedCtr * .6).sort((left, right) => right.clickShortfall - left.clickShortfall || compareText$
|
|
18601
|
+
}).filter((item) => item.actualCtr < item.expectedCtr * .6).sort((left, right) => right.clickShortfall - left.clickShortfall || compareText$13(left.query, right.query) || compareText$13(left.url, right.url));
|
|
18602
18602
|
const items = eligibleItems.slice(0, limit);
|
|
18603
18603
|
selection.eligibleUnderperformers = eligibleItems.length;
|
|
18604
18604
|
selection.returnedUnderperformers = items.length;
|
|
@@ -18829,7 +18829,7 @@ function diagnosisDataStatus(input) {
|
|
|
18829
18829
|
function fixed$1(value) {
|
|
18830
18830
|
return Number(value.toFixed(3));
|
|
18831
18831
|
}
|
|
18832
|
-
function compareText$
|
|
18832
|
+
function compareText$12(left, right) {
|
|
18833
18833
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
18834
18834
|
}
|
|
18835
18835
|
function validMetric$2(value) {
|
|
@@ -18909,8 +18909,8 @@ function compareRetainedSegmentRows(input) {
|
|
|
18909
18909
|
else unmatched.push(unmatchedRow(row, "before"));
|
|
18910
18910
|
}
|
|
18911
18911
|
for (const [key, row] of after.rows) if (!before.rows.has(key)) unmatched.push(unmatchedRow(row, "after"));
|
|
18912
|
-
matched.sort((left, right) => Math.abs(right.clickDelta) - Math.abs(left.clickDelta) || compareText$
|
|
18913
|
-
unmatched.sort((left, right) => right.clicks - left.clicks || compareText$
|
|
18912
|
+
matched.sort((left, right) => Math.abs(right.clickDelta) - Math.abs(left.clickDelta) || compareText$12(left.key, right.key));
|
|
18913
|
+
unmatched.sort((left, right) => right.clicks - left.clicks || compareText$12(left.retainedIn, right.retainedIn) || compareText$12(left.key, right.key));
|
|
18914
18914
|
return {
|
|
18915
18915
|
before,
|
|
18916
18916
|
after,
|
|
@@ -19667,13 +19667,13 @@ const MINIMUM_PAGE_SHARE = .1;
|
|
|
19667
19667
|
const MINIMUM_PAGE_IMPRESSIONS = 10;
|
|
19668
19668
|
const MAXIMUM_DOMINANT_SHARE = .8;
|
|
19669
19669
|
const MAX_SUPPRESSIONS = 100;
|
|
19670
|
-
function boundedInteger$
|
|
19670
|
+
function boundedInteger$6(value, fallback, minimum, maximum) {
|
|
19671
19671
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
19672
19672
|
return Math.min(maximum, Math.max(minimum, Math.floor(value)));
|
|
19673
19673
|
}
|
|
19674
19674
|
function analyzeCannibalRows(input) {
|
|
19675
|
-
const minImpressions = boundedInteger$
|
|
19676
|
-
const limit = boundedInteger$
|
|
19675
|
+
const minImpressions = boundedInteger$6(input.minImpressions, 50, 0, 1e9);
|
|
19676
|
+
const limit = boundedInteger$6(input.limit, 25, 1, 100);
|
|
19677
19677
|
const selection = {
|
|
19678
19678
|
sourceRows: input.rows.length,
|
|
19679
19679
|
invalidRows: 0,
|
|
@@ -19999,7 +19999,7 @@ function normalizeUrl(value) {
|
|
|
19999
19999
|
return;
|
|
20000
20000
|
}
|
|
20001
20001
|
}
|
|
20002
|
-
function validRow$
|
|
20002
|
+
function validRow$3(row, query, url) {
|
|
20003
20003
|
return Boolean(query && url) && Number.isFinite(row.clicks) && row.clicks >= 0 && Number.isFinite(row.impressions) && row.impressions > 0 && row.clicks <= row.impressions && Number.isFinite(row.ctr) && row.ctr >= 0 && row.ctr <= 1 && Number.isFinite(row.position) && row.position > 0;
|
|
20004
20004
|
}
|
|
20005
20005
|
function aggregateDecayRows(rows) {
|
|
@@ -20008,7 +20008,7 @@ function aggregateDecayRows(rows) {
|
|
|
20008
20008
|
for (const row of rows) {
|
|
20009
20009
|
const query = row.keys[0]?.trim() ?? "";
|
|
20010
20010
|
const url = normalizeUrl(row.keys[1]?.trim() ?? "");
|
|
20011
|
-
if (!validRow$
|
|
20011
|
+
if (!validRow$3(row, query, url)) {
|
|
20012
20012
|
invalid++;
|
|
20013
20013
|
continue;
|
|
20014
20014
|
}
|
|
@@ -20415,7 +20415,7 @@ function normalizedQuery(value) {
|
|
|
20415
20415
|
function groupKey$1(item) {
|
|
20416
20416
|
return `${item.template.id}:${normalizedQuery(item.query)}`;
|
|
20417
20417
|
}
|
|
20418
|
-
function compareText$
|
|
20418
|
+
function compareText$11(left, right) {
|
|
20419
20419
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
20420
20420
|
}
|
|
20421
20421
|
function groupAction(input) {
|
|
@@ -20451,11 +20451,11 @@ function groupQuickWins(items) {
|
|
|
20451
20451
|
technicalUrls
|
|
20452
20452
|
});
|
|
20453
20453
|
return group;
|
|
20454
|
-
}).filter((group) => group !== void 0).sort((left, right) => right.totalEstimatedCtrClickShortfall - left.totalEstimatedCtrClickShortfall || right.totalImpressions - left.totalImpressions || compareText$
|
|
20454
|
+
}).filter((group) => group !== void 0).sort((left, right) => right.totalEstimatedCtrClickShortfall - left.totalEstimatedCtrClickShortfall || right.totalImpressions - left.totalImpressions || compareText$11(left.id, right.id));
|
|
20455
20455
|
}
|
|
20456
20456
|
//#endregion
|
|
20457
20457
|
//#region packages/core/src/analyze/site-diagnostics/quick-wins-analysis-primitives.ts
|
|
20458
|
-
function boundedInteger$
|
|
20458
|
+
function boundedInteger$5(value, fallback, minimum, maximum) {
|
|
20459
20459
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
20460
20460
|
return Math.min(maximum, Math.max(minimum, Math.floor(value)));
|
|
20461
20461
|
}
|
|
@@ -20467,7 +20467,7 @@ function validHttpUrl(value) {
|
|
|
20467
20467
|
return false;
|
|
20468
20468
|
}
|
|
20469
20469
|
}
|
|
20470
|
-
function validRow$
|
|
20470
|
+
function validRow$2(row, query, url) {
|
|
20471
20471
|
return row.keys.length >= 2 && Boolean(query) && validHttpUrl(url) && Number.isFinite(row.clicks) && row.clicks >= 0 && Number.isFinite(row.impressions) && row.impressions > 0 && row.clicks <= row.impressions && Number.isFinite(row.ctr) && row.ctr >= 0 && row.ctr <= 1 && Number.isFinite(row.position) && row.position >= 1;
|
|
20472
20472
|
}
|
|
20473
20473
|
function selectBenchmarkRows(input) {
|
|
@@ -20489,7 +20489,7 @@ function selectBenchmarkRows(input) {
|
|
|
20489
20489
|
for (const row of input.rows) {
|
|
20490
20490
|
const query = row.keys[0]?.trim() ?? "";
|
|
20491
20491
|
const url = row.keys[1]?.trim() ?? "";
|
|
20492
|
-
if (!validRow$
|
|
20492
|
+
if (!validRow$2(row, query, url)) selection.invalidRows++;
|
|
20493
20493
|
else if (row.position > 10) selection.outsideBenchmarkPositionRows++;
|
|
20494
20494
|
else if (isLowActionabilityQuery(query)) selection.lowActionabilityRows++;
|
|
20495
20495
|
else if (shouldExcludeBrandQuery({
|
|
@@ -20563,11 +20563,11 @@ function quickWinItem(input) {
|
|
|
20563
20563
|
}
|
|
20564
20564
|
};
|
|
20565
20565
|
}
|
|
20566
|
-
function compareText$
|
|
20566
|
+
function compareText$10(left, right) {
|
|
20567
20567
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
20568
20568
|
}
|
|
20569
20569
|
function compareQuickWinItems(left, right) {
|
|
20570
|
-
return right.estimatedCtrClickShortfall - left.estimatedCtrClickShortfall || right.impressions - left.impressions || compareText$
|
|
20570
|
+
return right.estimatedCtrClickShortfall - left.estimatedCtrClickShortfall || right.impressions - left.impressions || compareText$10(left.query, right.query) || compareText$10(left.url, right.url);
|
|
20571
20571
|
}
|
|
20572
20572
|
function roundedSum(values) {
|
|
20573
20573
|
return Number(values.reduce((sum, value) => sum + value, 0).toFixed(2));
|
|
@@ -20580,8 +20580,8 @@ const DEFAULT_LIMIT$3 = 25;
|
|
|
20580
20580
|
const MAX_LIMIT$3 = 100;
|
|
20581
20581
|
const FALLBACK_SOURCE = "builtin_position_ctr_curve_v1";
|
|
20582
20582
|
function analyzeQuickWinsFromRows(input) {
|
|
20583
|
-
const minImpressions = boundedInteger$
|
|
20584
|
-
const limit = boundedInteger$
|
|
20583
|
+
const minImpressions = boundedInteger$5(input.minImpressions, DEFAULT_MIN_IMPRESSIONS$2, 0, MAX_MIN_IMPRESSIONS$2);
|
|
20584
|
+
const limit = boundedInteger$5(input.limit, DEFAULT_LIMIT$3, 1, MAX_LIMIT$3);
|
|
20585
20585
|
const selected = selectBenchmarkRows(input);
|
|
20586
20586
|
const benchmarkContext = createCtrBenchmarkContext(selected.rows, {
|
|
20587
20587
|
samplePopulation: "all_qualified_url_samples",
|
|
@@ -20682,7 +20682,7 @@ function analyzeQuickWinsFromRows(input) {
|
|
|
20682
20682
|
}
|
|
20683
20683
|
//#endregion
|
|
20684
20684
|
//#region packages/core/src/analyze/site-diagnostics/quick-wins-templates.ts
|
|
20685
|
-
function compareText$
|
|
20685
|
+
function compareText$9(left, right) {
|
|
20686
20686
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
20687
20687
|
}
|
|
20688
20688
|
function unique$2(values) {
|
|
@@ -20708,7 +20708,7 @@ function quickWinTemplateRecommendations(items) {
|
|
|
20708
20708
|
action: technicalUrls > 0 ? `Fix verified technical issues on ${technicalUrls} affected URLs before testing shared template changes.` : `Compare live results and verified page evidence for ${urls.length} affected URLs before testing a shared title, snippet, or heading change.`,
|
|
20709
20709
|
evidence: `${templateItems.length} eligible rows across ${urls.length} distinct URLs share this recognised template. Example queries: ${queries.join("; ")}.`
|
|
20710
20710
|
};
|
|
20711
|
-
}).filter((item) => item !== void 0).sort((left, right) => right.totalEstimatedCtrClickShortfall - left.totalEstimatedCtrClickShortfall || right.totalImpressions - left.totalImpressions || compareText$
|
|
20711
|
+
}).filter((item) => item !== void 0).sort((left, right) => right.totalEstimatedCtrClickShortfall - left.totalEstimatedCtrClickShortfall || right.totalImpressions - left.totalImpressions || compareText$9(left.templateId, right.templateId));
|
|
20712
20712
|
}
|
|
20713
20713
|
function quickWinTemplateSummaries(items) {
|
|
20714
20714
|
const templates = /* @__PURE__ */ new Map();
|
|
@@ -20727,13 +20727,13 @@ function quickWinTemplateSummaries(items) {
|
|
|
20727
20727
|
label: item.label,
|
|
20728
20728
|
rowCount: item.rows,
|
|
20729
20729
|
urlCount: item.urls.size,
|
|
20730
|
-
sampleUrls: [...item.urls].sort(compareText$
|
|
20731
|
-
})).sort((left, right) => right.urlCount - left.urlCount || right.rowCount - left.rowCount || compareText$
|
|
20730
|
+
sampleUrls: [...item.urls].sort(compareText$9).slice(0, 3)
|
|
20731
|
+
})).sort((left, right) => right.urlCount - left.urlCount || right.rowCount - left.rowCount || compareText$9(left.id, right.id)).slice(0, 5);
|
|
20732
20732
|
}
|
|
20733
20733
|
//#endregion
|
|
20734
20734
|
//#region packages/core/src/analyze/site-diagnostics/quick-wins.ts
|
|
20735
20735
|
const DEFAULT_DAYS$3 = 28;
|
|
20736
|
-
const MAX_DAYS$
|
|
20736
|
+
const MAX_DAYS$4 = 548;
|
|
20737
20737
|
const DEFAULT_VERIFY_LIMIT$1 = 5;
|
|
20738
20738
|
const MAX_VERIFY_LIMIT = 100;
|
|
20739
20739
|
const MAX_SOURCE_ROWS$3 = 1e5;
|
|
@@ -20757,12 +20757,12 @@ function applyVerification$1(item, coverage) {
|
|
|
20757
20757
|
};
|
|
20758
20758
|
}
|
|
20759
20759
|
async function quickWinsReport(input, dependencies = defaultDependencies$9) {
|
|
20760
|
-
const explicitRange = explicitDateRange(input, MAX_DAYS$
|
|
20760
|
+
const explicitRange = explicitDateRange(input, MAX_DAYS$4);
|
|
20761
20761
|
const days = explicitRange?.days ?? integerOption$2({
|
|
20762
20762
|
value: input.days,
|
|
20763
20763
|
fallback: DEFAULT_DAYS$3,
|
|
20764
20764
|
minimum: 1,
|
|
20765
|
-
maximum: MAX_DAYS$
|
|
20765
|
+
maximum: MAX_DAYS$4,
|
|
20766
20766
|
label: "Days"
|
|
20767
20767
|
});
|
|
20768
20768
|
const verifyLimit = integerOption$2({
|
|
@@ -20912,7 +20912,7 @@ async function quickWinsReport(input, dependencies = defaultDependencies$9) {
|
|
|
20912
20912
|
}
|
|
20913
20913
|
//#endregion
|
|
20914
20914
|
//#region packages/core/src/analyze/striking-distance-analysis-primitives.ts
|
|
20915
|
-
function compareText$
|
|
20915
|
+
function compareText$8(left, right) {
|
|
20916
20916
|
const leftPoints = [...left].map((character) => character.codePointAt(0) ?? 0);
|
|
20917
20917
|
const rightPoints = [...right].map((character) => character.codePointAt(0) ?? 0);
|
|
20918
20918
|
const length = Math.min(leftPoints.length, rightPoints.length);
|
|
@@ -20978,7 +20978,7 @@ function strikingDistanceItem(row) {
|
|
|
20978
20978
|
};
|
|
20979
20979
|
}
|
|
20980
20980
|
function compareStrikingDistanceItems(left, right) {
|
|
20981
|
-
return right.priority.score - left.priority.score || right.impressions - left.impressions || left.position - right.position || compareText$
|
|
20981
|
+
return right.priority.score - left.priority.score || right.impressions - left.impressions || left.position - right.position || compareText$8(left.query, right.query) || compareText$8(left.url, right.url);
|
|
20982
20982
|
}
|
|
20983
20983
|
function groupStrikingDistanceItems(items) {
|
|
20984
20984
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -21015,7 +21015,7 @@ function groupStrikingDistanceItems(items) {
|
|
|
21015
21015
|
action: shared ? `Review the sampled ${first.template.label.toLowerCase()} URLs for a recurring technical, content, or internal-link pattern before changing the shared template.` : `Review these URLs individually; the current evidence does not support a shared-template change.`
|
|
21016
21016
|
}
|
|
21017
21017
|
};
|
|
21018
|
-
}).filter((group) => group !== void 0).sort((left, right) => right.totalImpressions - left.totalImpressions || right.uniqueUrls - left.uniqueUrls || compareText$
|
|
21018
|
+
}).filter((group) => group !== void 0).sort((left, right) => right.totalImpressions - left.totalImpressions || right.uniqueUrls - left.uniqueUrls || compareText$8(left.id, right.id));
|
|
21019
21019
|
}
|
|
21020
21020
|
//#endregion
|
|
21021
21021
|
//#region packages/core/src/analyze/striking-distance-analysis.ts
|
|
@@ -21130,7 +21130,7 @@ function analyzeStrikingDistanceRows(input) {
|
|
|
21130
21130
|
//#endregion
|
|
21131
21131
|
//#region packages/core/src/analyze/striking-distance.ts
|
|
21132
21132
|
const DEFAULT_DAYS$2 = 28;
|
|
21133
|
-
const MAX_DAYS$
|
|
21133
|
+
const MAX_DAYS$3 = 548;
|
|
21134
21134
|
const DEFAULT_VERIFY_LIMIT = 5;
|
|
21135
21135
|
const MAX_SOURCE_ROWS$2 = 1e5;
|
|
21136
21136
|
const defaultDependencies$8 = {
|
|
@@ -21138,13 +21138,13 @@ const defaultDependencies$8 = {
|
|
|
21138
21138
|
verifyContent: verifyQueryContent,
|
|
21139
21139
|
now: () => /* @__PURE__ */ new Date()
|
|
21140
21140
|
};
|
|
21141
|
-
function boundedInteger$
|
|
21141
|
+
function boundedInteger$4(value, fallback, minimum, maximum) {
|
|
21142
21142
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
21143
21143
|
return Math.min(maximum, Math.max(minimum, Math.floor(value)));
|
|
21144
21144
|
}
|
|
21145
21145
|
function reportDays(value) {
|
|
21146
21146
|
if (value === void 0) return DEFAULT_DAYS$2;
|
|
21147
|
-
if (!Number.isInteger(value) || value < 1 || value > MAX_DAYS$
|
|
21147
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_DAYS$3) throw new SeoError("INVALID_INPUT", `Days must be a whole number between 1 and ${MAX_DAYS$3}.`);
|
|
21148
21148
|
return value;
|
|
21149
21149
|
}
|
|
21150
21150
|
function dateRange(days, now) {
|
|
@@ -21202,7 +21202,7 @@ async function strikingDistance(input, dependencies = defaultDependencies$8) {
|
|
|
21202
21202
|
brandTerms: input.brandTerms,
|
|
21203
21203
|
includeBrand: input.includeBrand
|
|
21204
21204
|
});
|
|
21205
|
-
const verifyLimit = boundedInteger$
|
|
21205
|
+
const verifyLimit = boundedInteger$4(input.verifyLimit, DEFAULT_VERIFY_LIMIT, 0, analysis.limit);
|
|
21206
21206
|
const verificationRequested = input.verifyContent === true || input.verifyContent !== false && input.verifyLimit !== void 0;
|
|
21207
21207
|
const attempted = verificationRequested ? Math.min(verifyLimit, analysis.items.length) : 0;
|
|
21208
21208
|
const items = await Promise.all(analysis.items.map(async (item, index) => {
|
|
@@ -25579,7 +25579,7 @@ function resolveProvider(input) {
|
|
|
25579
25579
|
//#endregion
|
|
25580
25580
|
//#region packages/core/src/analyze/domain-research/shared.ts
|
|
25581
25581
|
const MAX_GSC_DOMAIN_ROWS = 1e5;
|
|
25582
|
-
function compareText$
|
|
25582
|
+
function compareText$7(left, right) {
|
|
25583
25583
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
25584
25584
|
}
|
|
25585
25585
|
function normalizedKeyword$1(value) {
|
|
@@ -25696,8 +25696,8 @@ function aggregateGscQueries(rows) {
|
|
|
25696
25696
|
clicks: item.clicks,
|
|
25697
25697
|
impressions: item.impressions,
|
|
25698
25698
|
averagePosition: item.positionWeight ? item.weightedPosition / item.positionWeight : null,
|
|
25699
|
-
urls: [...item.urls].sort(compareText$
|
|
25700
|
-
})).sort((left, right) => right.impressions - left.impressions || compareText$
|
|
25699
|
+
urls: [...item.urls].sort(compareText$7).slice(0, 5)
|
|
25700
|
+
})).sort((left, right) => right.impressions - left.impressions || compareText$7(left.query, right.query));
|
|
25701
25701
|
}
|
|
25702
25702
|
async function acquireGscQueries(input) {
|
|
25703
25703
|
const range = finalGscDateRange(input.days, input.now);
|
|
@@ -26063,7 +26063,7 @@ function pseoQueryPatterns(rows) {
|
|
|
26063
26063
|
//#region packages/core/src/analyze/pseo/templates.ts
|
|
26064
26064
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
26065
26065
|
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
26066
|
-
function compareText$
|
|
26066
|
+
function compareText$6(left, right) {
|
|
26067
26067
|
const leftPoints = [...left].map((value) => value.codePointAt(0) ?? 0);
|
|
26068
26068
|
const rightPoints = [...right].map((value) => value.codePointAt(0) ?? 0);
|
|
26069
26069
|
for (let index = 0; index < Math.min(leftPoints.length, rightPoints.length); index++) {
|
|
@@ -26119,7 +26119,7 @@ function valueTokens(values) {
|
|
|
26119
26119
|
const tokens = value.normalize("NFKC").toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
26120
26120
|
for (const token of tokens.filter((item) => item.length > 1 || /[^\p{ASCII}]/u.test(item))) counts.set(token, (counts.get(token) ?? 0) + 1);
|
|
26121
26121
|
}
|
|
26122
|
-
return [...counts.entries()].sort((a, b) => b[1] - a[1] || compareText$
|
|
26122
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1] || compareText$6(a[0], b[0])).slice(0, 8).map(([token]) => token);
|
|
26123
26123
|
}
|
|
26124
26124
|
function templateShape(signature, urls) {
|
|
26125
26125
|
const signatureParts = signature.split("/").filter(Boolean);
|
|
@@ -26168,7 +26168,7 @@ function collectClusters(urls, signatures) {
|
|
|
26168
26168
|
return bySignature;
|
|
26169
26169
|
}
|
|
26170
26170
|
function clusterPseoTemplates(urls, opts = {}) {
|
|
26171
|
-
const uniqueUrls = [...new Set(urls)].sort(compareText$
|
|
26171
|
+
const uniqueUrls = [...new Set(urls)].sort(compareText$6);
|
|
26172
26172
|
const minUrls = opts.minUrls ?? 3;
|
|
26173
26173
|
const minShare = opts.minShare ?? .01;
|
|
26174
26174
|
const sampleSize = opts.sampleSize ?? 5;
|
|
@@ -26186,15 +26186,15 @@ function clusterPseoTemplates(urls, opts = {}) {
|
|
|
26186
26186
|
shape: templateShape(signature, clusterUrls),
|
|
26187
26187
|
urlCount: clusterUrls.length,
|
|
26188
26188
|
share: uniqueUrls.length ? clusterUrls.length / uniqueUrls.length : 0,
|
|
26189
|
-
sampleUrls: clusterUrls.sort(compareText$
|
|
26190
|
-
})).filter((cluster) => cluster.shape.variableSegments.length > 0 && cluster.urlCount >= minUrls && cluster.share >= minShare).sort((a, b) => b.urlCount - a.urlCount || compareText$
|
|
26189
|
+
sampleUrls: clusterUrls.sort(compareText$6).slice(0, sampleSize)
|
|
26190
|
+
})).filter((cluster) => cluster.shape.variableSegments.length > 0 && cluster.urlCount >= minUrls && cluster.share >= minShare).sort((a, b) => b.urlCount - a.urlCount || compareText$6(a.signature, b.signature)).slice(0, opts.limit ?? 50);
|
|
26191
26191
|
}
|
|
26192
26192
|
//#endregion
|
|
26193
26193
|
//#region packages/core/src/analyze/domain-research/gap-analysis.ts
|
|
26194
26194
|
const MAX_RELEVANCE_QUERY_REFS = 3;
|
|
26195
26195
|
const MAX_FIRST_PARTY_PATTERN_URLS = 1e4;
|
|
26196
26196
|
function terms(value) {
|
|
26197
|
-
return [...new Set(pseoQueryTerms(value).map(canonicalPseoTerm).filter(Boolean))].sort(compareText$
|
|
26197
|
+
return [...new Set(pseoQueryTerms(value).map(canonicalPseoTerm).filter(Boolean))].sort(compareText$7);
|
|
26198
26198
|
}
|
|
26199
26199
|
function observedVolume$1(row) {
|
|
26200
26200
|
return value(row.monthlySearchVolume) ?? -1;
|
|
@@ -26238,7 +26238,7 @@ function relevanceForKeyword(keyword, acquisition, tokenIndex) {
|
|
|
26238
26238
|
query: row.query,
|
|
26239
26239
|
overlap: terms(row.query).filter((token) => keywordTerms.includes(token)).length,
|
|
26240
26240
|
impressions: row.impressions
|
|
26241
|
-
})).filter((row) => row.overlap > 0).sort((left, right) => right.overlap - left.overlap || right.impressions - left.impressions || compareText$
|
|
26241
|
+
})).filter((row) => row.overlap > 0).sort((left, right) => right.overlap - left.overlap || right.impressions - left.impressions || compareText$7(left.query, right.query)).slice(0, MAX_RELEVANCE_QUERY_REFS).map((row) => row.query);
|
|
26242
26242
|
return {
|
|
26243
26243
|
state: sharedTokens.length >= 2 ? "observed-overlap" : sharedTokens.length === 1 ? "weak-overlap" : "unavailable",
|
|
26244
26244
|
sharedTokens,
|
|
@@ -26260,10 +26260,10 @@ function repeatedCompetitorPatterns(rows) {
|
|
|
26260
26260
|
urlCount: pattern.urlCount,
|
|
26261
26261
|
sampleUrls: pattern.sampleUrls,
|
|
26262
26262
|
evidenceRefs: items.flatMap((item) => pattern.sampleUrls.includes(item.row.url) ? [item.evidenceRef] : [])
|
|
26263
|
-
}))).sort((left, right) => right.urlCount - left.urlCount || compareText$
|
|
26263
|
+
}))).sort((left, right) => right.urlCount - left.urlCount || compareText$7(left.domain, right.domain) || compareText$7(left.signature, right.signature));
|
|
26264
26264
|
}
|
|
26265
26265
|
function ownPatterns(acquisition) {
|
|
26266
|
-
return clusterPseoTemplates([...new Set(acquisition.sourceRows.rows.flatMap((row) => row.urls))].sort(compareText$
|
|
26266
|
+
return clusterPseoTemplates([...new Set(acquisition.sourceRows.rows.flatMap((row) => row.urls))].sort(compareText$7).slice(0, MAX_FIRST_PARTY_PATTERN_URLS), {
|
|
26267
26267
|
minUrls: 3,
|
|
26268
26268
|
minShare: 0,
|
|
26269
26269
|
limit: 50,
|
|
@@ -26304,10 +26304,10 @@ function analyzeCompetitorGap(acquisition) {
|
|
|
26304
26304
|
const byDomain = /* @__PURE__ */ new Map();
|
|
26305
26305
|
for (const item of rows) {
|
|
26306
26306
|
const current = byDomain.get(item.domain);
|
|
26307
|
-
if (!current || item.row.rankGroup < current.row.rankGroup || item.row.rankGroup === current.row.rankGroup && compareText$
|
|
26307
|
+
if (!current || item.row.rankGroup < current.row.rankGroup || item.row.rankGroup === current.row.rankGroup && compareText$7(item.row.url, current.row.url) < 0) byDomain.set(item.domain, item);
|
|
26308
26308
|
}
|
|
26309
|
-
const retainedRows = [...byDomain.values()].sort((left, right) => left.row.rankGroup - right.row.rankGroup || compareText$
|
|
26310
|
-
const best = [...retainedRows].sort((left, right) => observedVolume$1(right.row) - observedVolume$1(left.row) || left.row.rankGroup - right.row.rankGroup || compareText$
|
|
26309
|
+
const retainedRows = [...byDomain.values()].sort((left, right) => left.row.rankGroup - right.row.rankGroup || compareText$7(left.domain, right.domain));
|
|
26310
|
+
const best = [...retainedRows].sort((left, right) => observedVolume$1(right.row) - observedVolume$1(left.row) || left.row.rankGroup - right.row.rankGroup || compareText$7(left.domain, right.domain))[0];
|
|
26311
26311
|
const firstParty = firstPartyByQuery.get(keyword);
|
|
26312
26312
|
const relevance = relevanceForKeyword(keyword, acquisition, tokenIndex.index);
|
|
26313
26313
|
const ownRank = ownRanks.get(keyword) ?? null;
|
|
@@ -26351,7 +26351,7 @@ function analyzeCompetitorGap(acquisition) {
|
|
|
26351
26351
|
},
|
|
26352
26352
|
evidenceRefs: retainedRows.map((item) => item.evidenceRef)
|
|
26353
26353
|
};
|
|
26354
|
-
}).sort((left, right) => classificationOrder(left.classification) - classificationOrder(right.classification) || right.competitorCount - left.competitorCount || (value(right.monthlySearchVolume) ?? -1) - (value(left.monthlySearchVolume) ?? -1) || Math.min(...left.competitors.map((item) => item.rank)) - Math.min(...right.competitors.map((item) => item.rank)) || compareText$
|
|
26354
|
+
}).sort((left, right) => classificationOrder(left.classification) - classificationOrder(right.classification) || right.competitorCount - left.competitorCount || (value(right.monthlySearchVolume) ?? -1) - (value(left.monthlySearchVolume) ?? -1) || Math.min(...left.competitors.map((item) => item.rank)) - Math.min(...right.competitors.map((item) => item.rank)) || compareText$7(left.keyword, right.keyword));
|
|
26355
26355
|
return {
|
|
26356
26356
|
rawRows,
|
|
26357
26357
|
candidates,
|
|
@@ -26823,7 +26823,7 @@ async function rankingPagesReport(input, dependencies = {}) {
|
|
|
26823
26823
|
impressions: row.impressions,
|
|
26824
26824
|
averagePosition: row.position
|
|
26825
26825
|
}] : [];
|
|
26826
|
-
}).sort((left, right) => right.impressions - left.impressions || compareText$
|
|
26826
|
+
}).sort((left, right) => right.impressions - left.impressions || compareText$7(left.url, right.url));
|
|
26827
26827
|
const firstPartyStatus = !input.site ? "not-requested" : (firstPartyResponse?.rowsFetched ?? 0) >= MAX_GSC_PAGE_ROWS ? "partial" : firstPartyResponse?.rows.length === 0 ? "empty" : "complete";
|
|
26828
26828
|
const findings = patterns.slice(0, 5).map((pattern, index) => ({
|
|
26829
26829
|
code: "repeated-ranking-page-pattern",
|
|
@@ -26904,7 +26904,7 @@ async function serpCompetitorsReport(input, dependencies = {}) {
|
|
|
26904
26904
|
const now = (dependencies.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
26905
26905
|
const market = validatedMarket(input.market);
|
|
26906
26906
|
const providerId = validatedProvider(input.provider);
|
|
26907
|
-
const keywords = [...new Set(input.keywords.map(normalizedKeyword$1))].filter(Boolean).sort(compareText$
|
|
26907
|
+
const keywords = [...new Set(input.keywords.map(normalizedKeyword$1))].filter(Boolean).sort(compareText$7);
|
|
26908
26908
|
if (keywords.length < 2 || keywords.length > 200 || keywords.some((keyword) => keyword.length > 80 || keyword.split(/\s+/u).length > 10)) throw new SeoError("INVALID_INPUT", "SERP competitor research requires 2 to 200 keywords of at most 80 characters and 10 words.");
|
|
26909
26909
|
const target = input.targetDomain ? normalizeDomain$1(input.targetDomain) : null;
|
|
26910
26910
|
const declared = declaredCompetitors(input.declaredCompetitors);
|
|
@@ -28685,7 +28685,7 @@ function providerReportError(error) {
|
|
|
28685
28685
|
if (error.code === "rate-limit") throw new SeoError("RATE_LIMITED", error.message);
|
|
28686
28686
|
throw new SeoError("PROVIDER_UNAVAILABLE", error.message);
|
|
28687
28687
|
}
|
|
28688
|
-
function validateInput$
|
|
28688
|
+
function validateInput$4(input) {
|
|
28689
28689
|
if (input.keywords.length < 1 || input.keywords.length > MAX_REPORT_KEYWORDS || input.keywords.some((keyword) => {
|
|
28690
28690
|
const value = keyword.trim();
|
|
28691
28691
|
return value.length < 1 || value.length > 80 || value.split(/\s+/u).length > 10;
|
|
@@ -28719,7 +28719,7 @@ function findings$1(analysis) {
|
|
|
28719
28719
|
}).sort((left, right) => Number(right.code === "recent-demand-increase") - Number(left.code === "recent-demand-increase") || right.recentAverage - left.recentAverage || compareCodepoints$2(left.keyword, right.keyword)).slice(0, 10).map(({ recentAverage: _recentAverage, ...finding }) => finding);
|
|
28720
28720
|
}
|
|
28721
28721
|
async function keywordMetricsReport(input, dependencies = {}) {
|
|
28722
|
-
const validated = validateInput$
|
|
28722
|
+
const validated = validateInput$4(input);
|
|
28723
28723
|
let candidates;
|
|
28724
28724
|
try {
|
|
28725
28725
|
candidates = dependencies.candidates ?? await defaultCandidates$3();
|
|
@@ -29017,16 +29017,16 @@ function benchmarkDetails(benchmark) {
|
|
|
29017
29017
|
positiveUrlSamples: benchmark.positiveUrlSamples
|
|
29018
29018
|
};
|
|
29019
29019
|
}
|
|
29020
|
-
function boundedInteger$
|
|
29020
|
+
function boundedInteger$3(input) {
|
|
29021
29021
|
const value = Number.isFinite(input.value) ? input.value ?? 0 : input.fallback;
|
|
29022
29022
|
return Math.min(input.maximum ?? Number.POSITIVE_INFINITY, Math.max(1, Math.floor(value)));
|
|
29023
29023
|
}
|
|
29024
29024
|
function analyzeQueryClustersFromRows(input) {
|
|
29025
|
-
const minImpressions = boundedInteger$
|
|
29025
|
+
const minImpressions = boundedInteger$3({
|
|
29026
29026
|
value: input.minImpressions,
|
|
29027
29027
|
fallback: 25
|
|
29028
29028
|
});
|
|
29029
|
-
const limit = boundedInteger$
|
|
29029
|
+
const limit = boundedInteger$3({
|
|
29030
29030
|
value: input.limit,
|
|
29031
29031
|
fallback: 25,
|
|
29032
29032
|
maximum: 100
|
|
@@ -29145,7 +29145,7 @@ function integerInRange(value, fallback, minimum, maximum) {
|
|
|
29145
29145
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
29146
29146
|
return Math.min(maximum, Math.max(minimum, Math.floor(value)));
|
|
29147
29147
|
}
|
|
29148
|
-
function compareText$
|
|
29148
|
+
function compareText$5(left, right) {
|
|
29149
29149
|
const leftPoints = [...left].map((character) => character.codePointAt(0) ?? 0);
|
|
29150
29150
|
const rightPoints = [...right].map((character) => character.codePointAt(0) ?? 0);
|
|
29151
29151
|
for (let index = 0; index < Math.min(leftPoints.length, rightPoints.length); index++) {
|
|
@@ -29164,7 +29164,7 @@ function normalizedHttpUrl(value) {
|
|
|
29164
29164
|
return;
|
|
29165
29165
|
}
|
|
29166
29166
|
}
|
|
29167
|
-
function validRow(row, query, url) {
|
|
29167
|
+
function validRow$1(row, query, url) {
|
|
29168
29168
|
return Boolean(query && url) && Number.isFinite(row.clicks) && Number.isFinite(row.impressions) && Number.isFinite(row.ctr) && Number.isFinite(row.position) && row.clicks >= 0 && row.impressions > 0 && row.clicks <= row.impressions && row.ctr >= 0 && row.ctr <= 1 && row.position > 0;
|
|
29169
29169
|
}
|
|
29170
29170
|
function queryFromRow(row) {
|
|
@@ -29177,7 +29177,7 @@ function queryFromRow(row) {
|
|
|
29177
29177
|
};
|
|
29178
29178
|
}
|
|
29179
29179
|
function compareQueries(left, right) {
|
|
29180
|
-
return right.impressions - left.impressions || left.position - right.position || right.clicks - left.clicks || compareText$
|
|
29180
|
+
return right.impressions - left.impressions || left.position - right.position || right.clicks - left.clicks || compareText$5(left.query, right.query);
|
|
29181
29181
|
}
|
|
29182
29182
|
function recommendation(input) {
|
|
29183
29183
|
return {
|
|
@@ -29223,7 +29223,7 @@ function pageItem(url, rows) {
|
|
|
29223
29223
|
};
|
|
29224
29224
|
}
|
|
29225
29225
|
function compareItems$1(left, right) {
|
|
29226
|
-
return right.priority.score - left.priority.score || right.impressions - left.impressions || left.position - right.position || compareText$
|
|
29226
|
+
return right.priority.score - left.priority.score || right.impressions - left.impressions || left.position - right.position || compareText$5(left.url, right.url);
|
|
29227
29227
|
}
|
|
29228
29228
|
function analyzeSecondPageRows(input) {
|
|
29229
29229
|
const minImpressions = integerInRange(input.minImpressions, DEFAULT_MIN_IMPRESSIONS, 0, MAX_MIN_IMPRESSIONS);
|
|
@@ -29244,7 +29244,7 @@ function analyzeSecondPageRows(input) {
|
|
|
29244
29244
|
for (const row of input.rows) {
|
|
29245
29245
|
const query = row.keys[0]?.trim() ?? "";
|
|
29246
29246
|
const url = normalizedHttpUrl(row.keys[1]?.trim() ?? "");
|
|
29247
|
-
if (!validRow(row, query, url)) selection.invalidRows++;
|
|
29247
|
+
if (!validRow$1(row, query, url)) selection.invalidRows++;
|
|
29248
29248
|
else if (row.position <= 10 || row.position > 20) selection.outsidePositionRows++;
|
|
29249
29249
|
else if (shouldExcludeBrandQuery({
|
|
29250
29250
|
query,
|
|
@@ -29336,7 +29336,7 @@ function analyzeSecondPageRows(input) {
|
|
|
29336
29336
|
//#endregion
|
|
29337
29337
|
//#region packages/core/src/analyze/keyword-opportunities.ts
|
|
29338
29338
|
const DEFAULT_DAYS$1 = 28;
|
|
29339
|
-
const MAX_DAYS$
|
|
29339
|
+
const MAX_DAYS$2 = 548;
|
|
29340
29340
|
const DEFAULT_REPORT_LIMIT = 10;
|
|
29341
29341
|
const MAX_REPORT_LIMIT = 25;
|
|
29342
29342
|
const DEFAULT_KEYWORD_LIMIT = 30;
|
|
@@ -29353,7 +29353,7 @@ const SOURCE_ORDER = [
|
|
|
29353
29353
|
"second-page",
|
|
29354
29354
|
"striking-distance"
|
|
29355
29355
|
];
|
|
29356
|
-
function compareText$
|
|
29356
|
+
function compareText$4(left, right) {
|
|
29357
29357
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
29358
29358
|
}
|
|
29359
29359
|
function rounded(value, precision = 3) {
|
|
@@ -29365,7 +29365,7 @@ function integerOption$1(input) {
|
|
|
29365
29365
|
if (!Number.isInteger(input.value) || input.value < input.minimum || input.value > input.maximum) throw new SeoError("INVALID_INPUT", `${input.label} must be a whole number between ${input.minimum} and ${input.maximum}.`);
|
|
29366
29366
|
return input.value;
|
|
29367
29367
|
}
|
|
29368
|
-
function validateInput$
|
|
29368
|
+
function validateInput$3(input) {
|
|
29369
29369
|
const site = input.site.trim();
|
|
29370
29370
|
if (!site || site.length > 2048) throw new SeoError("INVALID_INPUT", "Use a valid Search Console property.");
|
|
29371
29371
|
if (input.minImpressions !== void 0 && (!Number.isInteger(input.minImpressions) || input.minImpressions < 0 || input.minImpressions > 1e9)) throw new SeoError("INVALID_INPUT", "Minimum impressions must be a whole number between 0 and 1000000000.");
|
|
@@ -29385,7 +29385,7 @@ function validateInput$2(input) {
|
|
|
29385
29385
|
value: input.days,
|
|
29386
29386
|
fallback: DEFAULT_DAYS$1,
|
|
29387
29387
|
minimum: 1,
|
|
29388
|
-
maximum: MAX_DAYS$
|
|
29388
|
+
maximum: MAX_DAYS$2,
|
|
29389
29389
|
label: "Days"
|
|
29390
29390
|
}),
|
|
29391
29391
|
limit: integerOption$1({
|
|
@@ -29497,7 +29497,7 @@ function aggregateCandidates(rows) {
|
|
|
29497
29497
|
impressions: rounded(row.impressions),
|
|
29498
29498
|
ctr: rounded(row.ctr, 4),
|
|
29499
29499
|
averagePosition: rounded(row.position, 2)
|
|
29500
|
-
})).sort((left, right) => right.impressions - left.impressions || left.averagePosition - right.averagePosition || compareText$
|
|
29500
|
+
})).sort((left, right) => right.impressions - left.impressions || left.averagePosition - right.averagePosition || compareText$4(left.url, right.url));
|
|
29501
29501
|
return [keyword, {
|
|
29502
29502
|
keyword,
|
|
29503
29503
|
clicks: rounded(clicks),
|
|
@@ -29731,7 +29731,7 @@ function nextSteps(input) {
|
|
|
29731
29731
|
return steps;
|
|
29732
29732
|
}
|
|
29733
29733
|
async function keywordOpportunitiesReport(input, dependencies = {}) {
|
|
29734
|
-
const validated = validateInput$
|
|
29734
|
+
const validated = validateInput$3(input);
|
|
29735
29735
|
const now = (dependencies.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
29736
29736
|
const generatedAt = now.toISOString();
|
|
29737
29737
|
const range = finalGscDateRange(validated.days, now);
|
|
@@ -30099,7 +30099,7 @@ function providerError$3(error) {
|
|
|
30099
30099
|
if (error.code === "configuration") throw new SeoError("INVALID_INPUT", error.message);
|
|
30100
30100
|
throw new SeoError("PROVIDER_UNAVAILABLE", error.message);
|
|
30101
30101
|
}
|
|
30102
|
-
function validateInput$
|
|
30102
|
+
function validateInput$2(input) {
|
|
30103
30103
|
if (input.seeds.length < 1 || input.seeds.length > MAX_RESEARCH_SEEDS || input.seeds.some((seed) => {
|
|
30104
30104
|
const value = seed.trim();
|
|
30105
30105
|
return value.length < 1 || value.length > 80 || value.split(/\s+/u).length > 10;
|
|
@@ -30152,7 +30152,7 @@ async function keywordResearchReport(input, dependencies = {}) {
|
|
|
30152
30152
|
"suggestions"
|
|
30153
30153
|
];
|
|
30154
30154
|
const limit = input.limit ?? 50;
|
|
30155
|
-
const validated = validateInput$
|
|
30155
|
+
const validated = validateInput$2({
|
|
30156
30156
|
...input,
|
|
30157
30157
|
sources,
|
|
30158
30158
|
limit
|
|
@@ -30235,6 +30235,864 @@ async function keywordResearchReport(input, dependencies = {}) {
|
|
|
30235
30235
|
};
|
|
30236
30236
|
}
|
|
30237
30237
|
//#endregion
|
|
30238
|
+
//#region packages/core/src/analyze/local-search/intent.ts
|
|
30239
|
+
const NEARBY_PHRASES = [
|
|
30240
|
+
"closest to me",
|
|
30241
|
+
"in my area",
|
|
30242
|
+
"near me",
|
|
30243
|
+
"nearby"
|
|
30244
|
+
];
|
|
30245
|
+
const UK_POSTCODE = /\b[A-Z]{1,2}\d[A-Z\d]?\s*\d[A-Z]{2}\b/iu;
|
|
30246
|
+
const US_ZIP_CODE = /\b\d{5}(?:-\d{4})?\b/u;
|
|
30247
|
+
const US_ZIP_CONTEXT = /\b(?:around|near|nearby|postal(?:\s+code)?|zip(?:\s+code)?)\b/iu;
|
|
30248
|
+
function normalizeLocalSearchText(value) {
|
|
30249
|
+
return value.normalize("NFKC").trim().replace(/\s+/gu, " ").toLowerCase();
|
|
30250
|
+
}
|
|
30251
|
+
function tokens(value) {
|
|
30252
|
+
return normalizeLocalSearchText(value).match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
30253
|
+
}
|
|
30254
|
+
function includesTokenSequence(source, candidate) {
|
|
30255
|
+
if (candidate.length === 0 || candidate.length > source.length) return false;
|
|
30256
|
+
return source.some((_, start) => candidate.every((token, offset) => source[start + offset] === token));
|
|
30257
|
+
}
|
|
30258
|
+
function termMatcher(terms) {
|
|
30259
|
+
const root = {
|
|
30260
|
+
children: /* @__PURE__ */ new Map(),
|
|
30261
|
+
terms: []
|
|
30262
|
+
};
|
|
30263
|
+
for (const term of terms) {
|
|
30264
|
+
let node = root;
|
|
30265
|
+
for (const token of tokens(term)) {
|
|
30266
|
+
const child = node.children.get(token) ?? {
|
|
30267
|
+
children: /* @__PURE__ */ new Map(),
|
|
30268
|
+
terms: []
|
|
30269
|
+
};
|
|
30270
|
+
node.children.set(token, child);
|
|
30271
|
+
node = child;
|
|
30272
|
+
}
|
|
30273
|
+
node.terms.push(term);
|
|
30274
|
+
}
|
|
30275
|
+
return (source) => {
|
|
30276
|
+
const matches = /* @__PURE__ */ new Set();
|
|
30277
|
+
for (let start = 0; start < source.length; start++) {
|
|
30278
|
+
let node = root;
|
|
30279
|
+
for (let index = start; index < source.length; index++) {
|
|
30280
|
+
node = node.children.get(source[index]);
|
|
30281
|
+
if (!node) break;
|
|
30282
|
+
for (const term of node.terms) matches.add(term);
|
|
30283
|
+
}
|
|
30284
|
+
}
|
|
30285
|
+
return [...matches].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
30286
|
+
};
|
|
30287
|
+
}
|
|
30288
|
+
function normalizeLocationTerms(values = []) {
|
|
30289
|
+
return [...new Set(values.map(normalizeLocalSearchText).filter(Boolean))].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
30290
|
+
}
|
|
30291
|
+
function classifyLocalIntent(query, locationTerms) {
|
|
30292
|
+
return createLocalIntentClassifier(locationTerms)(query);
|
|
30293
|
+
}
|
|
30294
|
+
function createLocalIntentClassifier(locationTerms) {
|
|
30295
|
+
const matchLocations = termMatcher(locationTerms);
|
|
30296
|
+
return (query) => {
|
|
30297
|
+
const queryTokens = tokens(normalizeLocalSearchText(query));
|
|
30298
|
+
const matchedLocations = matchLocations(queryTokens);
|
|
30299
|
+
const matchedNearby = NEARBY_PHRASES.filter((phrase) => includesTokenSequence(queryTokens, tokens(phrase)));
|
|
30300
|
+
const ukPostcode = UK_POSTCODE.exec(query)?.[0] ?? null;
|
|
30301
|
+
const usZipCode = US_ZIP_CONTEXT.test(query) && US_ZIP_CODE.test(query) ? US_ZIP_CODE.exec(query)?.[0] ?? null : null;
|
|
30302
|
+
const postalCode = ukPostcode ?? usZipCode;
|
|
30303
|
+
const classes = [];
|
|
30304
|
+
if (matchedLocations.length > 0) classes.push("named-location");
|
|
30305
|
+
if (matchedNearby.length > 0) classes.push("nearby");
|
|
30306
|
+
if (postalCode) classes.push("postal-code");
|
|
30307
|
+
if (classes.length === 0) return null;
|
|
30308
|
+
return {
|
|
30309
|
+
heuristic: true,
|
|
30310
|
+
method: "explicit-local-intent-v1",
|
|
30311
|
+
classes,
|
|
30312
|
+
matchedTerms: [
|
|
30313
|
+
...matchedLocations,
|
|
30314
|
+
...matchedNearby,
|
|
30315
|
+
...postalCode ? [normalizeLocalSearchText(postalCode)] : []
|
|
30316
|
+
]
|
|
30317
|
+
};
|
|
30318
|
+
};
|
|
30319
|
+
}
|
|
30320
|
+
//#endregion
|
|
30321
|
+
//#region packages/core/src/analyze/local-search/analysis.ts
|
|
30322
|
+
function compareText$3(left, right) {
|
|
30323
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
30324
|
+
}
|
|
30325
|
+
function validRow(row) {
|
|
30326
|
+
const query = row.keys[0]?.trim() ?? "";
|
|
30327
|
+
const page = row.keys[1]?.trim() ?? "";
|
|
30328
|
+
let validPage = false;
|
|
30329
|
+
try {
|
|
30330
|
+
validPage = ["http:", "https:"].includes(new URL(page).protocol);
|
|
30331
|
+
} catch {
|
|
30332
|
+
validPage = false;
|
|
30333
|
+
}
|
|
30334
|
+
if (row.keys.length !== 2 || !query || !validPage || !Number.isFinite(row.clicks) || !Number.isFinite(row.impressions) || !Number.isFinite(row.ctr) || !Number.isFinite(row.position) || row.clicks < 0 || row.impressions <= 0 || row.clicks > row.impressions || row.ctr < 0 || row.ctr > 1 || row.position <= 0) return null;
|
|
30335
|
+
return {
|
|
30336
|
+
query,
|
|
30337
|
+
page,
|
|
30338
|
+
clicks: row.clicks,
|
|
30339
|
+
impressions: row.impressions,
|
|
30340
|
+
ctr: row.ctr,
|
|
30341
|
+
position: row.position
|
|
30342
|
+
};
|
|
30343
|
+
}
|
|
30344
|
+
function rowSignature(row) {
|
|
30345
|
+
return `${row.clicks}\u0000${row.impressions}\u0000${row.ctr}\u0000${row.position}`;
|
|
30346
|
+
}
|
|
30347
|
+
function normalizeRows(rows, selection) {
|
|
30348
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
30349
|
+
for (const source of rows) {
|
|
30350
|
+
const row = validRow(source);
|
|
30351
|
+
if (!row) {
|
|
30352
|
+
selection.invalidRows++;
|
|
30353
|
+
continue;
|
|
30354
|
+
}
|
|
30355
|
+
const key = `${row.query}\u0000${row.page}`;
|
|
30356
|
+
grouped.set(key, [...grouped.get(key) ?? [], row]);
|
|
30357
|
+
}
|
|
30358
|
+
const result = [];
|
|
30359
|
+
for (const group of grouped.values()) {
|
|
30360
|
+
if (new Set(group.map(rowSignature)).size > 1) {
|
|
30361
|
+
selection.conflictingRows += group.length;
|
|
30362
|
+
continue;
|
|
30363
|
+
}
|
|
30364
|
+
selection.exactDuplicateRows += group.length - 1;
|
|
30365
|
+
const row = group[0];
|
|
30366
|
+
if (row) result.push(row);
|
|
30367
|
+
}
|
|
30368
|
+
return result;
|
|
30369
|
+
}
|
|
30370
|
+
function opportunityAction(position, pageCount) {
|
|
30371
|
+
if (pageCount > 1) return "review-page-overlap";
|
|
30372
|
+
if (position <= 3) return "protect-visibility";
|
|
30373
|
+
if (position <= 20) return "improve-existing-page";
|
|
30374
|
+
return "investigate-relevance";
|
|
30375
|
+
}
|
|
30376
|
+
function aggregateQueries(input) {
|
|
30377
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
30378
|
+
for (const row of input.rows) {
|
|
30379
|
+
if (isLowActionabilityQuery(row.query)) {
|
|
30380
|
+
input.selection.lowActionabilityRows++;
|
|
30381
|
+
continue;
|
|
30382
|
+
}
|
|
30383
|
+
if (shouldExcludeBrandQuery({
|
|
30384
|
+
query: row.query,
|
|
30385
|
+
siteUrl: input.site,
|
|
30386
|
+
brandTerms: input.brandTerms,
|
|
30387
|
+
includeBrand: input.includeBrand
|
|
30388
|
+
})) {
|
|
30389
|
+
input.selection.brandRows++;
|
|
30390
|
+
continue;
|
|
30391
|
+
}
|
|
30392
|
+
grouped.set(row.query, [...grouped.get(row.query) ?? [], row]);
|
|
30393
|
+
}
|
|
30394
|
+
const opportunities = [];
|
|
30395
|
+
for (const [query, rows] of grouped) {
|
|
30396
|
+
const intent = input.classify(query);
|
|
30397
|
+
if (!intent) {
|
|
30398
|
+
input.selection.nonLocalRows += rows.length;
|
|
30399
|
+
continue;
|
|
30400
|
+
}
|
|
30401
|
+
const impressions = rows.reduce((sum, row) => sum + row.impressions, 0);
|
|
30402
|
+
if (impressions < input.minImpressions) {
|
|
30403
|
+
input.selection.belowMinimumRows += rows.length;
|
|
30404
|
+
continue;
|
|
30405
|
+
}
|
|
30406
|
+
const clicks = rows.reduce((sum, row) => sum + row.clicks, 0);
|
|
30407
|
+
const averagePosition = rows.reduce((sum, row) => sum + row.position * row.impressions, 0) / impressions;
|
|
30408
|
+
const pages = rows.map((row) => ({
|
|
30409
|
+
url: row.page,
|
|
30410
|
+
clicks: row.clicks,
|
|
30411
|
+
impressions: row.impressions,
|
|
30412
|
+
ctr: row.impressions ? row.clicks / row.impressions : 0,
|
|
30413
|
+
averagePosition: row.position
|
|
30414
|
+
})).sort((left, right) => right.impressions - left.impressions || right.clicks - left.clicks || left.averagePosition - right.averagePosition || compareText$3(left.url, right.url));
|
|
30415
|
+
opportunities.push({
|
|
30416
|
+
query,
|
|
30417
|
+
clicks,
|
|
30418
|
+
impressions,
|
|
30419
|
+
ctr: impressions ? clicks / impressions : 0,
|
|
30420
|
+
averagePosition,
|
|
30421
|
+
intent,
|
|
30422
|
+
action: opportunityAction(averagePosition, pages.length),
|
|
30423
|
+
pages,
|
|
30424
|
+
pageCoverage: {
|
|
30425
|
+
available: pages.length,
|
|
30426
|
+
returned: Math.min(3, pages.length),
|
|
30427
|
+
omitted: Math.max(0, pages.length - 3)
|
|
30428
|
+
}
|
|
30429
|
+
});
|
|
30430
|
+
}
|
|
30431
|
+
return opportunities.sort((left, right) => right.impressions - left.impressions || right.clicks - left.clicks || left.averagePosition - right.averagePosition || compareText$3(left.query, right.query));
|
|
30432
|
+
}
|
|
30433
|
+
function localTemplates(opportunities) {
|
|
30434
|
+
const clusters = clusterPseoTemplates(opportunities.flatMap((item) => item.pages.map((page) => page.url)), {
|
|
30435
|
+
minUrls: 2,
|
|
30436
|
+
minShare: 0,
|
|
30437
|
+
limit: 10,
|
|
30438
|
+
sampleSize: 5
|
|
30439
|
+
});
|
|
30440
|
+
return clusters.map((cluster) => {
|
|
30441
|
+
const matched = opportunities.flatMap((item) => item.pages.filter((page) => templateForUrl(page.url, clusters) === cluster.signature).map((page) => ({
|
|
30442
|
+
query: item.query,
|
|
30443
|
+
page
|
|
30444
|
+
})));
|
|
30445
|
+
return {
|
|
30446
|
+
heuristic: true,
|
|
30447
|
+
signature: cluster.signature,
|
|
30448
|
+
urlCount: cluster.urlCount,
|
|
30449
|
+
sampleUrls: cluster.sampleUrls,
|
|
30450
|
+
queryCount: new Set(matched.map((item) => item.query)).size,
|
|
30451
|
+
clicks: matched.reduce((sum, item) => sum + item.page.clicks, 0),
|
|
30452
|
+
impressions: matched.reduce((sum, item) => sum + item.page.impressions, 0)
|
|
30453
|
+
};
|
|
30454
|
+
});
|
|
30455
|
+
}
|
|
30456
|
+
function analyzeLocalSearchRows(input) {
|
|
30457
|
+
const selection = {
|
|
30458
|
+
sourceRows: input.rows.length,
|
|
30459
|
+
invalidRows: 0,
|
|
30460
|
+
exactDuplicateRows: 0,
|
|
30461
|
+
conflictingRows: 0,
|
|
30462
|
+
lowActionabilityRows: 0,
|
|
30463
|
+
brandRows: 0,
|
|
30464
|
+
nonLocalRows: 0,
|
|
30465
|
+
belowMinimumRows: 0,
|
|
30466
|
+
eligibleQueries: 0,
|
|
30467
|
+
returnedQueries: 0,
|
|
30468
|
+
omittedQueries: 0,
|
|
30469
|
+
limit: input.limit,
|
|
30470
|
+
minImpressions: input.minImpressions
|
|
30471
|
+
};
|
|
30472
|
+
const normalized = normalizeRows(input.rows, selection);
|
|
30473
|
+
const eligible = aggregateQueries({
|
|
30474
|
+
...input,
|
|
30475
|
+
rows: normalized,
|
|
30476
|
+
selection,
|
|
30477
|
+
classify: createLocalIntentClassifier(input.locationTerms)
|
|
30478
|
+
});
|
|
30479
|
+
selection.eligibleQueries = eligible.length;
|
|
30480
|
+
selection.returnedQueries = Math.min(input.limit, eligible.length);
|
|
30481
|
+
selection.omittedQueries = Math.max(0, eligible.length - input.limit);
|
|
30482
|
+
return {
|
|
30483
|
+
selection,
|
|
30484
|
+
eligibleSummary: {
|
|
30485
|
+
clicks: eligible.reduce((sum, item) => sum + item.clicks, 0),
|
|
30486
|
+
impressions: eligible.reduce((sum, item) => sum + item.impressions, 0),
|
|
30487
|
+
namedLocationQueries: eligible.filter((item) => item.intent.classes.includes("named-location")).length,
|
|
30488
|
+
nearbyQueries: eligible.filter((item) => item.intent.classes.includes("nearby")).length,
|
|
30489
|
+
postalCodeQueries: eligible.filter((item) => item.intent.classes.includes("postal-code")).length,
|
|
30490
|
+
pageOverlapQueries: eligible.filter((item) => item.action === "review-page-overlap").length
|
|
30491
|
+
},
|
|
30492
|
+
eligiblePageCount: new Set(eligible.flatMap((item) => item.pages.map((page) => page.url))).size,
|
|
30493
|
+
opportunities: eligible.slice(0, input.limit).map((item) => ({
|
|
30494
|
+
...item,
|
|
30495
|
+
pages: item.pages.slice(0, 3)
|
|
30496
|
+
})),
|
|
30497
|
+
templates: localTemplates(eligible)
|
|
30498
|
+
};
|
|
30499
|
+
}
|
|
30500
|
+
//#endregion
|
|
30501
|
+
//#region packages/core/src/providers/dataforseo/serp-snapshot.ts
|
|
30502
|
+
const MAX_SERP_DEPTH = 100;
|
|
30503
|
+
const SERP_ENDPOINT = "v3/serp/google/organic/live/advanced";
|
|
30504
|
+
function safeUrl$1(value) {
|
|
30505
|
+
if (!value) return null;
|
|
30506
|
+
try {
|
|
30507
|
+
const parsed = new URL(value);
|
|
30508
|
+
if (!["http:", "https:"].includes(parsed.protocol)) return null;
|
|
30509
|
+
parsed.username = "";
|
|
30510
|
+
parsed.password = "";
|
|
30511
|
+
return parsed.toString();
|
|
30512
|
+
} catch {
|
|
30513
|
+
return null;
|
|
30514
|
+
}
|
|
30515
|
+
}
|
|
30516
|
+
function validOrganic(item) {
|
|
30517
|
+
return item.type === "organic" && Number.isSafeInteger(item.rank_group) && (item.rank_group ?? 0) > 0 && Number.isSafeInteger(item.rank_absolute) && (item.rank_absolute ?? 0) > 0 && Number.isSafeInteger(item.page) && (item.page ?? 0) > 0 && Boolean(item.domain?.trim()) && safeUrl$1(item.url) !== null;
|
|
30518
|
+
}
|
|
30519
|
+
function organicResult(item) {
|
|
30520
|
+
if (!validOrganic(item)) return null;
|
|
30521
|
+
const url = safeUrl$1(item.url);
|
|
30522
|
+
if (!url) return null;
|
|
30523
|
+
return {
|
|
30524
|
+
rankGroup: item.rank_group,
|
|
30525
|
+
rankAbsolute: item.rank_absolute,
|
|
30526
|
+
page: item.page,
|
|
30527
|
+
domain: item.domain.trim().toLowerCase(),
|
|
30528
|
+
url,
|
|
30529
|
+
title: item.title ?? null,
|
|
30530
|
+
description: item.description ?? null,
|
|
30531
|
+
isFeaturedSnippet: item.is_featured_snippet ?? null
|
|
30532
|
+
};
|
|
30533
|
+
}
|
|
30534
|
+
function checkedAt(value, fallback) {
|
|
30535
|
+
const timestamp = Date.parse(value);
|
|
30536
|
+
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : fallback;
|
|
30537
|
+
}
|
|
30538
|
+
function mapDataForSeoSerpSnapshot(input, snapshot, endpoint = SERP_ENDPOINT) {
|
|
30539
|
+
const market = searchMarketSchema.parse(input.market);
|
|
30540
|
+
const keyword = normalizedKeyword$2(input.keyword);
|
|
30541
|
+
const location = locationRequest(market, "serp-snapshot");
|
|
30542
|
+
const result = snapshot.response.tasks.flatMap((task) => task.result ?? []).find((item) => normalizedKeyword$2(item.keyword) === keyword);
|
|
30543
|
+
if (!result) throw new ProviderError({
|
|
30544
|
+
provider: "dataforseo",
|
|
30545
|
+
operation: "serp-snapshot",
|
|
30546
|
+
code: "invalid-response",
|
|
30547
|
+
message: "DataForSEO returned no matching SERP result."
|
|
30548
|
+
});
|
|
30549
|
+
const warnings = [...snapshot.warnings];
|
|
30550
|
+
const items = result.items ?? [];
|
|
30551
|
+
const organicItems = items.filter((item) => item.type === "organic");
|
|
30552
|
+
const mappedOrganicResults = organicItems.flatMap((item) => {
|
|
30553
|
+
const mapped = organicResult(item);
|
|
30554
|
+
return mapped ? [mapped] : [];
|
|
30555
|
+
}).sort((left, right) => left.rankAbsolute - right.rankAbsolute || left.rankGroup - right.rankGroup || compareCodepoints$3(left.url, right.url));
|
|
30556
|
+
const organicResults = mappedOrganicResults.slice(0, input.depth);
|
|
30557
|
+
const invalidRows = organicItems.length - mappedOrganicResults.length;
|
|
30558
|
+
const resultCapped = mappedOrganicResults.length > input.depth;
|
|
30559
|
+
const invalidResultCount = result.se_results_count !== null && result.se_results_count !== void 0 && result.se_results_count < organicResults.length;
|
|
30560
|
+
if (invalidRows > 0) warnings.push({
|
|
30561
|
+
code: "invalid-organic-results",
|
|
30562
|
+
field: "organicResults",
|
|
30563
|
+
message: `DataForSEO returned ${invalidRows} organic result${invalidRows === 1 ? "" : "s"} without a valid rank, domain, or URL.`
|
|
30564
|
+
});
|
|
30565
|
+
if (!Number.isFinite(Date.parse(result.datetime))) warnings.push({
|
|
30566
|
+
code: "invalid-serp-observation-time",
|
|
30567
|
+
field: "checkedAt",
|
|
30568
|
+
message: "DataForSEO returned an invalid SERP observation time; the local response time was retained."
|
|
30569
|
+
});
|
|
30570
|
+
const checkUrl = safeUrl$1(result.check_url);
|
|
30571
|
+
if (result.check_url && !checkUrl) warnings.push({
|
|
30572
|
+
code: "invalid-serp-check-url",
|
|
30573
|
+
field: "checkUrl",
|
|
30574
|
+
message: "DataForSEO returned an invalid SERP check URL."
|
|
30575
|
+
});
|
|
30576
|
+
if (invalidResultCount) warnings.push({
|
|
30577
|
+
code: "invalid-serp-result-count",
|
|
30578
|
+
field: "resultCount",
|
|
30579
|
+
message: "DataForSEO returned an estimated result count below the retained organic result count; the estimate was discarded."
|
|
30580
|
+
});
|
|
30581
|
+
const features = [.../* @__PURE__ */ new Set([...result.item_types ?? [], ...items.map((item) => item.type)])].sort(compareCodepoints$3);
|
|
30582
|
+
return {
|
|
30583
|
+
schemaVersion: 1,
|
|
30584
|
+
provider: "dataforseo",
|
|
30585
|
+
capability: "serp-snapshot",
|
|
30586
|
+
data: {
|
|
30587
|
+
keyword,
|
|
30588
|
+
effectiveKeyword: normalizedKeyword$2(result.spell?.keyword ?? result.keyword),
|
|
30589
|
+
searchEngineDomain: result.se_domain?.trim().toLowerCase() ?? null,
|
|
30590
|
+
checkedAt: checkedAt(result.datetime, snapshot.observedAt),
|
|
30591
|
+
checkUrl,
|
|
30592
|
+
resultCount: invalidResultCount ? null : result.se_results_count ?? null,
|
|
30593
|
+
pagesCount: result.pages_count === void 0 ? null : result.pages_count,
|
|
30594
|
+
features,
|
|
30595
|
+
organicResults
|
|
30596
|
+
},
|
|
30597
|
+
observedAt: snapshot.observedAt,
|
|
30598
|
+
market: {
|
|
30599
|
+
...market,
|
|
30600
|
+
device: market.device ?? "desktop"
|
|
30601
|
+
},
|
|
30602
|
+
coverage: {
|
|
30603
|
+
requestedRows: input.depth,
|
|
30604
|
+
returnedRows: snapshot.returnedRows,
|
|
30605
|
+
retainedRows: organicResults.length,
|
|
30606
|
+
invalidRows,
|
|
30607
|
+
providerTotalRows: invalidResultCount ? null : result.se_results_count ?? null,
|
|
30608
|
+
completeness: invalidRows > 0 || invalidResultCount ? "partial" : resultCapped ? "capped" : "complete",
|
|
30609
|
+
nextCursor: null
|
|
30610
|
+
},
|
|
30611
|
+
cache: snapshot.cache,
|
|
30612
|
+
cost: snapshot.cost,
|
|
30613
|
+
request: {
|
|
30614
|
+
operation: "serp-snapshot",
|
|
30615
|
+
endpoint,
|
|
30616
|
+
limit: input.depth,
|
|
30617
|
+
filters: {
|
|
30618
|
+
countryCode: market.countryCode,
|
|
30619
|
+
languageCode: market.languageCode,
|
|
30620
|
+
device: market.device ?? "desktop",
|
|
30621
|
+
...location.locationCode !== void 0 ? { locationCode: location.locationCode } : { locationName: location.locationName }
|
|
30622
|
+
},
|
|
30623
|
+
sort: ["rankAbsolute:ascending", "url:codepoint-ascending"]
|
|
30624
|
+
},
|
|
30625
|
+
warnings
|
|
30626
|
+
};
|
|
30627
|
+
}
|
|
30628
|
+
var DataForSeoSerpSnapshotProvider = class {
|
|
30629
|
+
provider = "dataforseo";
|
|
30630
|
+
capabilitySupport = [{
|
|
30631
|
+
capability: "serp-snapshot",
|
|
30632
|
+
status: "available",
|
|
30633
|
+
markets: [{
|
|
30634
|
+
searchEngines: ["google"],
|
|
30635
|
+
devices: ["desktop", "mobile"],
|
|
30636
|
+
location: "any"
|
|
30637
|
+
}]
|
|
30638
|
+
}];
|
|
30639
|
+
client;
|
|
30640
|
+
constructor(options = {}) {
|
|
30641
|
+
this.client = options.client ?? new DataForSeoClient(options);
|
|
30642
|
+
}
|
|
30643
|
+
async serpSnapshot(input) {
|
|
30644
|
+
const market = searchMarketSchema.parse(input.market);
|
|
30645
|
+
if (market.searchEngine !== "google") throw new ProviderError({
|
|
30646
|
+
provider: "dataforseo",
|
|
30647
|
+
operation: "serp-snapshot",
|
|
30648
|
+
code: "configuration",
|
|
30649
|
+
message: "DataForSEO SERP snapshots currently support Google."
|
|
30650
|
+
});
|
|
30651
|
+
if (!Number.isSafeInteger(input.depth) || input.depth < 1 || input.depth > MAX_SERP_DEPTH) throw new ProviderError({
|
|
30652
|
+
provider: "dataforseo",
|
|
30653
|
+
operation: "serp-snapshot",
|
|
30654
|
+
code: "configuration",
|
|
30655
|
+
message: "SERP depth must be from 1 to 100."
|
|
30656
|
+
});
|
|
30657
|
+
const keyword = normalizedKeyword$2(input.keyword);
|
|
30658
|
+
if (!keyword || keyword.length > 80 || keyword.split(/\s+/u).length > 10) throw new ProviderError({
|
|
30659
|
+
provider: "dataforseo",
|
|
30660
|
+
operation: "serp-snapshot",
|
|
30661
|
+
code: "configuration",
|
|
30662
|
+
message: "SERP snapshots require a keyword of at most 80 characters and 10 words."
|
|
30663
|
+
});
|
|
30664
|
+
const context = input.context ?? {
|
|
30665
|
+
reportId: "serp-results",
|
|
30666
|
+
reportRunId: randomUUID()
|
|
30667
|
+
};
|
|
30668
|
+
const location = locationRequest(market, "serp-snapshot");
|
|
30669
|
+
return mapDataForSeoSerpSnapshot(input, await this.client.serpLive({
|
|
30670
|
+
keyword,
|
|
30671
|
+
languageCode: market.languageCode.split("-")[0],
|
|
30672
|
+
...location,
|
|
30673
|
+
device: market.device ?? "desktop",
|
|
30674
|
+
depth: input.depth,
|
|
30675
|
+
refresh: input.refresh,
|
|
30676
|
+
context
|
|
30677
|
+
}));
|
|
30678
|
+
}
|
|
30679
|
+
};
|
|
30680
|
+
//#endregion
|
|
30681
|
+
//#region packages/core/src/analyze/serp-results.ts
|
|
30682
|
+
const MAX_REPORT_DEPTH = 100;
|
|
30683
|
+
function compareCodepoints(left, right) {
|
|
30684
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
30685
|
+
}
|
|
30686
|
+
function snapshotProvider(candidate) {
|
|
30687
|
+
return "serpSnapshot" in candidate && typeof candidate.serpSnapshot === "function" ? candidate : null;
|
|
30688
|
+
}
|
|
30689
|
+
async function defaultCandidates$1() {
|
|
30690
|
+
return [{
|
|
30691
|
+
adapter: new DataForSeoSerpSnapshotProvider(),
|
|
30692
|
+
connected: Boolean(await readDataForSeoCredentials()),
|
|
30693
|
+
priority: 10
|
|
30694
|
+
}];
|
|
30695
|
+
}
|
|
30696
|
+
function providerError$2(error) {
|
|
30697
|
+
if (!(error instanceof ProviderError)) throw error;
|
|
30698
|
+
if (error.code === "rate-limit") throw new SeoError("RATE_LIMITED", error.message);
|
|
30699
|
+
if (error.code === "configuration") throw new SeoError("INVALID_INPUT", error.message);
|
|
30700
|
+
throw new SeoError("PROVIDER_UNAVAILABLE", error.message);
|
|
30701
|
+
}
|
|
30702
|
+
function validateInput$1(input) {
|
|
30703
|
+
const keyword = input.keyword.trim().replace(/\s+/gu, " ");
|
|
30704
|
+
if (!keyword || keyword.length > 80 || keyword.split(/\s+/u).length > 10) throw new SeoError("INVALID_INPUT", "SERP results requires a keyword of at most 80 characters and 10 words.");
|
|
30705
|
+
if (!Number.isSafeInteger(input.depth) || input.depth < 1 || input.depth > MAX_REPORT_DEPTH) throw new SeoError("INVALID_INPUT", `SERP result depth must be from 1 to ${MAX_REPORT_DEPTH}.`);
|
|
30706
|
+
const market = searchMarketSchema.safeParse(input.market);
|
|
30707
|
+
if (!market.success) throw new SeoError("INVALID_INPUT", "Use a valid search market.");
|
|
30708
|
+
const provider = input.provider ? providerIdSchema.safeParse(input.provider) : void 0;
|
|
30709
|
+
if (provider && !provider.success) throw new SeoError("INVALID_INPUT", "Use a supported SERP provider.");
|
|
30710
|
+
return {
|
|
30711
|
+
keyword,
|
|
30712
|
+
market: market.data,
|
|
30713
|
+
provider: provider?.data
|
|
30714
|
+
};
|
|
30715
|
+
}
|
|
30716
|
+
function reportStatus$1(evidence) {
|
|
30717
|
+
if (evidence.coverage.completeness === "unavailable" || evidence.coverage.completeness === "invalid") return "unavailable";
|
|
30718
|
+
return evidence.coverage.completeness === "complete" ? "complete" : "partial";
|
|
30719
|
+
}
|
|
30720
|
+
function domainSummaries(snapshot) {
|
|
30721
|
+
const byDomain = /* @__PURE__ */ new Map();
|
|
30722
|
+
for (const result of snapshot.organicResults) byDomain.set(result.domain, [...byDomain.get(result.domain) ?? [], result.rankAbsolute]);
|
|
30723
|
+
return [...byDomain.entries()].map(([domain, ranks]) => ({
|
|
30724
|
+
domain,
|
|
30725
|
+
resultCount: ranks.length,
|
|
30726
|
+
ranks: [...ranks].sort((left, right) => left - right)
|
|
30727
|
+
})).sort((left, right) => right.resultCount - left.resultCount || (left.ranks[0] ?? Number.MAX_SAFE_INTEGER) - (right.ranks[0] ?? Number.MAX_SAFE_INTEGER) || compareCodepoints(left.domain, right.domain));
|
|
30728
|
+
}
|
|
30729
|
+
function reportFindings(snapshot, domains) {
|
|
30730
|
+
const findings = [];
|
|
30731
|
+
if (snapshot.effectiveKeyword !== snapshot.keyword) findings.push({
|
|
30732
|
+
code: "query-correction",
|
|
30733
|
+
evidenceRef: "evidence.data.effectiveKeyword",
|
|
30734
|
+
principle: "A corrected query changes the observed result context and does not prove the original wording has the same intent.",
|
|
30735
|
+
detail: `The provider observed results for ${snapshot.effectiveKeyword} after the requested query ${snapshot.keyword}.`
|
|
30736
|
+
});
|
|
30737
|
+
for (const [index, domain] of domains.entries()) {
|
|
30738
|
+
if (domain.resultCount <= 1) continue;
|
|
30739
|
+
findings.push({
|
|
30740
|
+
code: "repeated-domain",
|
|
30741
|
+
evidenceRef: `domains[${index}]`,
|
|
30742
|
+
principle: "Repeated results are descriptive snapshot evidence, not a domain strength or quality score.",
|
|
30743
|
+
detail: `${domain.domain} appears ${domain.resultCount} times at absolute ranks ${domain.ranks.join(", ")}.`
|
|
30744
|
+
});
|
|
30745
|
+
}
|
|
30746
|
+
return findings.slice(0, 10);
|
|
30747
|
+
}
|
|
30748
|
+
async function serpResultsReport(input, dependencies = {}) {
|
|
30749
|
+
const depth = input.depth ?? 10;
|
|
30750
|
+
const validated = validateInput$1({
|
|
30751
|
+
...input,
|
|
30752
|
+
depth
|
|
30753
|
+
});
|
|
30754
|
+
let candidates;
|
|
30755
|
+
try {
|
|
30756
|
+
candidates = dependencies.candidates ?? await defaultCandidates$1();
|
|
30757
|
+
} catch (error) {
|
|
30758
|
+
return providerError$2(error);
|
|
30759
|
+
}
|
|
30760
|
+
const resolution = resolveProvider({
|
|
30761
|
+
capability: "serp-snapshot",
|
|
30762
|
+
market: validated.market,
|
|
30763
|
+
candidates,
|
|
30764
|
+
provider: validated.provider
|
|
30765
|
+
});
|
|
30766
|
+
if (resolution.status === "unavailable") throw new SeoError("PROVIDER_UNAVAILABLE", resolution.reason === "provider-not-connected" ? "No connected provider can return live SERP results. Run `seo providers dataforseo connect` first." : validated.provider ? `${validated.provider} cannot return live SERP results for this market.` : "No configured provider can return live SERP results for this market.");
|
|
30767
|
+
const provider = snapshotProvider(resolution.provider);
|
|
30768
|
+
if (!provider) throw new SeoError("PROVIDER_UNAVAILABLE", "The selected provider has no live SERP implementation.");
|
|
30769
|
+
let evidence;
|
|
30770
|
+
try {
|
|
30771
|
+
evidence = await provider.serpSnapshot({
|
|
30772
|
+
keyword: validated.keyword,
|
|
30773
|
+
market: validated.market,
|
|
30774
|
+
depth,
|
|
30775
|
+
refresh: input.refresh,
|
|
30776
|
+
context: {
|
|
30777
|
+
projectId: input.context?.projectId ?? input.projectId,
|
|
30778
|
+
reportId: input.context?.reportId ?? "serp-results",
|
|
30779
|
+
reportRunId: input.context?.reportRunId ?? randomUUID()
|
|
30780
|
+
}
|
|
30781
|
+
});
|
|
30782
|
+
} catch (error) {
|
|
30783
|
+
return providerError$2(error);
|
|
30784
|
+
}
|
|
30785
|
+
const domains = domainSummaries(evidence.data);
|
|
30786
|
+
const dataStatus = reportStatus$1(evidence);
|
|
30787
|
+
const generatedAt = (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
30788
|
+
const correctedQuery = evidence.data.effectiveKeyword !== evidence.data.keyword;
|
|
30789
|
+
return {
|
|
30790
|
+
schemaVersion: 1,
|
|
30791
|
+
generatedAt,
|
|
30792
|
+
dataStatus,
|
|
30793
|
+
market: evidence.market,
|
|
30794
|
+
summary: {
|
|
30795
|
+
keyword: evidence.data.keyword,
|
|
30796
|
+
effectiveKeyword: evidence.data.effectiveKeyword,
|
|
30797
|
+
requestedDepth: depth,
|
|
30798
|
+
organicResults: evidence.data.organicResults.length,
|
|
30799
|
+
uniqueDomains: domains.length,
|
|
30800
|
+
observedFeatures: evidence.data.features.length,
|
|
30801
|
+
correctedQuery,
|
|
30802
|
+
verdict: `${evidence.data.organicResults.length} organic results across ${domains.length} domains were retained from the ${evidence.market.device ?? "desktop"} snapshot.`
|
|
30803
|
+
},
|
|
30804
|
+
evidence,
|
|
30805
|
+
domains,
|
|
30806
|
+
findings: reportFindings(evidence.data, domains),
|
|
30807
|
+
caveats: [
|
|
30808
|
+
"This is one market, location, language, and device-specific result snapshot; it is not rank history.",
|
|
30809
|
+
"Search results can change between checks and may differ from a person's signed-in or personalized results.",
|
|
30810
|
+
"The provider result count is an estimate and may be missing; retained organic rows are bounded by the requested depth.",
|
|
30811
|
+
"Repeated domains, result features, titles, and snippets do not establish content quality, authority, or ranking feasibility."
|
|
30812
|
+
],
|
|
30813
|
+
nextSteps: [
|
|
30814
|
+
"Review the retained pages and result types before deciding whether the query fits an existing page, a new page, or no page.",
|
|
30815
|
+
"Keep Search Console average position separate from these exact snapshot ranks when comparing first-party performance.",
|
|
30816
|
+
"Repeat the same market and device check later if the decision depends on result stability."
|
|
30817
|
+
]
|
|
30818
|
+
};
|
|
30819
|
+
}
|
|
30820
|
+
//#endregion
|
|
30821
|
+
//#region packages/core/src/analyze/local-search/report.ts
|
|
30822
|
+
const MAX_DAYS$1 = 548;
|
|
30823
|
+
const MAX_ROWS = 5e4;
|
|
30824
|
+
function boundedInteger$2(input) {
|
|
30825
|
+
const value = input.value ?? input.fallback;
|
|
30826
|
+
if (!Number.isSafeInteger(value) || value < input.minimum || value > input.maximum) throw new SeoError("INVALID_INPUT", `${input.label} must be a whole number between ${input.minimum} and ${input.maximum}.`);
|
|
30827
|
+
return value;
|
|
30828
|
+
}
|
|
30829
|
+
function validateInput(input) {
|
|
30830
|
+
const site = input.site.trim();
|
|
30831
|
+
if (!site) throw new SeoError("INVALID_INPUT", "site must not be empty.");
|
|
30832
|
+
if (input.locationTerms && (input.locationTerms.length > 100 || input.locationTerms.some((term) => !term.trim() || term.length > 100))) throw new SeoError("INVALID_INPUT", "locationTerms must contain at most 100 non-empty terms of 100 characters or fewer.");
|
|
30833
|
+
if (input.brandTerms && (input.brandTerms.length > 20 || input.brandTerms.some((term) => !term.trim() || term.length > 200))) throw new SeoError("INVALID_INPUT", "brandTerms must contain at most 20 non-empty terms of 200 characters or fewer.");
|
|
30834
|
+
const includeSerps = input.includeSerps ?? false;
|
|
30835
|
+
let market;
|
|
30836
|
+
if (input.market) {
|
|
30837
|
+
const parsed = searchMarketSchema.safeParse(input.market);
|
|
30838
|
+
if (!parsed.success) throw new SeoError("INVALID_INPUT", "Use a valid search market.");
|
|
30839
|
+
market = parsed.data;
|
|
30840
|
+
}
|
|
30841
|
+
if (includeSerps && !market?.location) throw new SeoError("INVALID_INPUT", "Local SERP evidence requires a country, language, and canonical location.");
|
|
30842
|
+
if (!includeSerps && (input.market || input.provider || input.serpLimit !== void 0 || input.serpDepth !== void 0)) throw new SeoError("INVALID_INPUT", "Set includeSerps to true before passing market, provider, or SERP options.");
|
|
30843
|
+
return {
|
|
30844
|
+
site,
|
|
30845
|
+
days: boundedInteger$2({
|
|
30846
|
+
value: input.days,
|
|
30847
|
+
fallback: 28,
|
|
30848
|
+
minimum: 1,
|
|
30849
|
+
maximum: MAX_DAYS$1,
|
|
30850
|
+
label: "days"
|
|
30851
|
+
}),
|
|
30852
|
+
locationTerms: normalizeLocationTerms(input.locationTerms),
|
|
30853
|
+
minImpressions: boundedInteger$2({
|
|
30854
|
+
value: input.minImpressions,
|
|
30855
|
+
fallback: 1,
|
|
30856
|
+
minimum: 0,
|
|
30857
|
+
maximum: 1e9,
|
|
30858
|
+
label: "minImpressions"
|
|
30859
|
+
}),
|
|
30860
|
+
limit: boundedInteger$2({
|
|
30861
|
+
value: input.limit,
|
|
30862
|
+
fallback: 25,
|
|
30863
|
+
minimum: 1,
|
|
30864
|
+
maximum: 100,
|
|
30865
|
+
label: "limit"
|
|
30866
|
+
}),
|
|
30867
|
+
maxRows: boundedInteger$2({
|
|
30868
|
+
value: input.maxRows,
|
|
30869
|
+
fallback: MAX_ROWS,
|
|
30870
|
+
minimum: 1,
|
|
30871
|
+
maximum: MAX_ROWS,
|
|
30872
|
+
label: "maxRows"
|
|
30873
|
+
}),
|
|
30874
|
+
includeSerps,
|
|
30875
|
+
market,
|
|
30876
|
+
serpLimit: boundedInteger$2({
|
|
30877
|
+
value: input.serpLimit,
|
|
30878
|
+
fallback: 3,
|
|
30879
|
+
minimum: 1,
|
|
30880
|
+
maximum: 3,
|
|
30881
|
+
label: "serpLimit"
|
|
30882
|
+
}),
|
|
30883
|
+
serpDepth: boundedInteger$2({
|
|
30884
|
+
value: input.serpDepth,
|
|
30885
|
+
fallback: 10,
|
|
30886
|
+
minimum: 1,
|
|
30887
|
+
maximum: 20,
|
|
30888
|
+
label: "serpDepth"
|
|
30889
|
+
})
|
|
30890
|
+
};
|
|
30891
|
+
}
|
|
30892
|
+
function emptyCost() {
|
|
30893
|
+
return {
|
|
30894
|
+
currency: "USD",
|
|
30895
|
+
estimatedMicros: 0,
|
|
30896
|
+
actualMicros: 0,
|
|
30897
|
+
taskIds: []
|
|
30898
|
+
};
|
|
30899
|
+
}
|
|
30900
|
+
function aggregateCosts(costs) {
|
|
30901
|
+
const estimated = costs.map((cost) => cost.estimatedMicros);
|
|
30902
|
+
const actual = costs.map((cost) => cost.actualMicros);
|
|
30903
|
+
return {
|
|
30904
|
+
currency: "USD",
|
|
30905
|
+
estimatedMicros: estimated.some((value) => value === null) ? null : estimated.reduce((sum, value) => sum + (value ?? 0), 0),
|
|
30906
|
+
actualMicros: actual.some((value) => value === null) ? null : actual.reduce((sum, value) => sum + (value ?? 0), 0),
|
|
30907
|
+
taskIds: [...new Set(costs.flatMap((cost) => cost.taskIds))].sort()
|
|
30908
|
+
};
|
|
30909
|
+
}
|
|
30910
|
+
function costsWithUnknownRequest(costs) {
|
|
30911
|
+
return {
|
|
30912
|
+
currency: "USD",
|
|
30913
|
+
estimatedMicros: null,
|
|
30914
|
+
actualMicros: null,
|
|
30915
|
+
taskIds: [...new Set(costs.flatMap((cost) => cost.taskIds))].sort()
|
|
30916
|
+
};
|
|
30917
|
+
}
|
|
30918
|
+
async function acquireSerps(input) {
|
|
30919
|
+
const selected = input.queries.slice(0, input.limit);
|
|
30920
|
+
const selection = {
|
|
30921
|
+
availableQueries: input.queries.length,
|
|
30922
|
+
requestedQueries: selected.length,
|
|
30923
|
+
omittedQueries: Math.max(0, input.queries.length - selected.length),
|
|
30924
|
+
limit: input.limit,
|
|
30925
|
+
depth: input.depth,
|
|
30926
|
+
method: "highest-impression-local-queries-v1"
|
|
30927
|
+
};
|
|
30928
|
+
if (!input.requested) return {
|
|
30929
|
+
requested: false,
|
|
30930
|
+
status: "not-requested",
|
|
30931
|
+
selection,
|
|
30932
|
+
market: null,
|
|
30933
|
+
reports: [],
|
|
30934
|
+
cost: emptyCost(),
|
|
30935
|
+
reason: "Local SERP snapshots were not requested, so no paid provider work was attempted."
|
|
30936
|
+
};
|
|
30937
|
+
if (selected.length === 0) return {
|
|
30938
|
+
requested: true,
|
|
30939
|
+
status: "skipped",
|
|
30940
|
+
selection,
|
|
30941
|
+
market: input.market ?? null,
|
|
30942
|
+
reports: [],
|
|
30943
|
+
cost: emptyCost(),
|
|
30944
|
+
reason: "No retained local-intent query was available for a SERP snapshot."
|
|
30945
|
+
};
|
|
30946
|
+
if (!input.market) throw new SeoError("INVALID_INPUT", "Local SERP evidence requires a market.");
|
|
30947
|
+
const reports = [];
|
|
30948
|
+
try {
|
|
30949
|
+
for (const query of selected) reports.push(await input.run({
|
|
30950
|
+
keyword: query,
|
|
30951
|
+
market: input.market,
|
|
30952
|
+
depth: input.depth,
|
|
30953
|
+
provider: input.provider,
|
|
30954
|
+
projectId: input.projectId,
|
|
30955
|
+
context: {
|
|
30956
|
+
reportId: "local-search-demand",
|
|
30957
|
+
reportRunId: input.reportRunId
|
|
30958
|
+
},
|
|
30959
|
+
refresh: input.refresh
|
|
30960
|
+
}));
|
|
30961
|
+
} catch (error) {
|
|
30962
|
+
if (error instanceof SeoError && (error.code === "PROVIDER_UNAVAILABLE" || error.code === "RATE_LIMITED")) return {
|
|
30963
|
+
requested: true,
|
|
30964
|
+
status: reports.length > 0 ? "partial" : "unavailable",
|
|
30965
|
+
selection,
|
|
30966
|
+
market: input.market,
|
|
30967
|
+
reports,
|
|
30968
|
+
cost: costsWithUnknownRequest(reports.map((report) => report.evidence.cost)),
|
|
30969
|
+
reason: "First-party local demand remains available, but not every requested SERP snapshot could be acquired.",
|
|
30970
|
+
error: {
|
|
30971
|
+
code: error.code,
|
|
30972
|
+
message: error.message,
|
|
30973
|
+
retryable: error.retryable
|
|
30974
|
+
}
|
|
30975
|
+
};
|
|
30976
|
+
throw error;
|
|
30977
|
+
}
|
|
30978
|
+
return {
|
|
30979
|
+
requested: true,
|
|
30980
|
+
status: reports.every((report) => report.dataStatus === "complete") ? "complete" : "partial",
|
|
30981
|
+
selection,
|
|
30982
|
+
market: input.market,
|
|
30983
|
+
reports,
|
|
30984
|
+
cost: aggregateCosts(reports.map((report) => report.evidence.cost))
|
|
30985
|
+
};
|
|
30986
|
+
}
|
|
30987
|
+
async function localSearchReport(input, dependencies = {}) {
|
|
30988
|
+
const options = validateInput(input);
|
|
30989
|
+
const now = (dependencies.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
30990
|
+
const generatedAt = now.toISOString();
|
|
30991
|
+
const range = finalGscDateRange(options.days, now);
|
|
30992
|
+
const source = await (dependencies.searchAnalytics ?? querySearchAnalytics)(options.site, {
|
|
30993
|
+
...range,
|
|
30994
|
+
dimensions: ["query", "page"],
|
|
30995
|
+
type: "web",
|
|
30996
|
+
dataState: "final",
|
|
30997
|
+
maxRows: options.maxRows
|
|
30998
|
+
}, { refresh: input.refresh });
|
|
30999
|
+
const analysis = analyzeLocalSearchRows({
|
|
31000
|
+
rows: source.rows,
|
|
31001
|
+
site: options.site,
|
|
31002
|
+
locationTerms: options.locationTerms,
|
|
31003
|
+
brandTerms: input.brandTerms,
|
|
31004
|
+
includeBrand: input.includeBrand,
|
|
31005
|
+
minImpressions: options.minImpressions,
|
|
31006
|
+
limit: options.limit
|
|
31007
|
+
});
|
|
31008
|
+
const serpEvidence = await acquireSerps({
|
|
31009
|
+
requested: options.includeSerps,
|
|
31010
|
+
queries: analysis.opportunities.map((item) => item.query),
|
|
31011
|
+
market: options.market,
|
|
31012
|
+
provider: input.provider,
|
|
31013
|
+
projectId: input.projectId,
|
|
31014
|
+
refresh: input.refresh,
|
|
31015
|
+
limit: options.serpLimit,
|
|
31016
|
+
depth: options.serpDepth,
|
|
31017
|
+
reportRunId: randomUUID(),
|
|
31018
|
+
run: dependencies.serpResults ?? serpResultsReport
|
|
31019
|
+
});
|
|
31020
|
+
const possiblyTruncated = source.rowsFetched >= options.maxRows;
|
|
31021
|
+
const sourceEmpty = source.rows.length === 0;
|
|
31022
|
+
const filtered = !sourceEmpty && analysis.selection.eligibleQueries === 0;
|
|
31023
|
+
const partial = possiblyTruncated || serpEvidence.status === "partial" || serpEvidence.status === "unavailable";
|
|
31024
|
+
const opportunities = analysis.opportunities;
|
|
31025
|
+
const localPackSnapshots = serpEvidence.reports.filter((report) => report.evidence.data.features.includes("local_pack")).length;
|
|
31026
|
+
const dataStatus = sourceEmpty ? "empty" : partial ? "partial" : filtered ? "filtered" : "complete";
|
|
31027
|
+
const warnings = [];
|
|
31028
|
+
if (possiblyTruncated) warnings.push(`Search Console reached the ${options.maxRows}-row retention cap. Lower-volume local queries may be missing.`);
|
|
31029
|
+
if (analysis.selection.invalidRows > 0) warnings.push(`${analysis.selection.invalidRows} malformed Search Console rows were excluded.`);
|
|
31030
|
+
if (analysis.selection.conflictingRows > 0) warnings.push(`${analysis.selection.conflictingRows} conflicting duplicate Search Console rows were excluded.`);
|
|
31031
|
+
if (serpEvidence.error) warnings.push(serpEvidence.error.message);
|
|
31032
|
+
const nextSteps = [];
|
|
31033
|
+
if (!options.includeSerps) nextSteps.push("If the shortlisted queries justify paid verification, rerun with includeSerps true and an exact location and device to inspect current local result features and organic competitors.");
|
|
31034
|
+
else if (serpEvidence.status === "unavailable") nextSteps.push("Check the provider connection, market support, and local spend limits before retrying the missing SERP snapshots.");
|
|
31035
|
+
if (analysis.templates.length > 0) nextSteps.push("Run pseo-audit on the strongest repeated local page pattern before expanding it; verify source fields, page uniqueness, indexability, and representative output.");
|
|
31036
|
+
if (opportunities.some((item) => item.action === "review-page-overlap")) nextSteps.push("Review overlapping pages query by query before consolidating anything; multiple retained pages do not by themselves prove harmful cannibalisation.");
|
|
31037
|
+
nextSteps.push("Use location terms for the actual service area and repeat the report for materially different markets or devices instead of treating one area as representative.");
|
|
31038
|
+
return {
|
|
31039
|
+
schemaVersion: 1,
|
|
31040
|
+
methodology: "local-search-demand-v1",
|
|
31041
|
+
site: options.site,
|
|
31042
|
+
generatedAt,
|
|
31043
|
+
range,
|
|
31044
|
+
rangeDays: options.days,
|
|
31045
|
+
dataStatus,
|
|
31046
|
+
source: {
|
|
31047
|
+
provider: "google-search-console",
|
|
31048
|
+
dimensions: ["query", "page"],
|
|
31049
|
+
searchType: "web",
|
|
31050
|
+
dataState: "final",
|
|
31051
|
+
rowsFetched: source.rowsFetched,
|
|
31052
|
+
calls: source.calls,
|
|
31053
|
+
maxRows: options.maxRows,
|
|
31054
|
+
possiblyTruncated,
|
|
31055
|
+
completeness: possiblyTruncated ? "possibly-truncated" : "retained-query-page-rows-only"
|
|
31056
|
+
},
|
|
31057
|
+
methodologyDetails: {
|
|
31058
|
+
intentMethod: "explicit-local-intent-v1",
|
|
31059
|
+
suppliedLocationTerms: options.locationTerms,
|
|
31060
|
+
automaticPatterns: [
|
|
31061
|
+
"nearby-phrases",
|
|
31062
|
+
"uk-postcodes",
|
|
31063
|
+
"contextual-us-zip-codes"
|
|
31064
|
+
],
|
|
31065
|
+
opportunityOrder: "impressions-clicks-position-query-v1",
|
|
31066
|
+
templateMethod: "pseo-url-template-clustering-v1"
|
|
31067
|
+
},
|
|
31068
|
+
selection: analysis.selection,
|
|
31069
|
+
summary: {
|
|
31070
|
+
localQueries: analysis.selection.eligibleQueries,
|
|
31071
|
+
returnedQueries: opportunities.length,
|
|
31072
|
+
pages: analysis.eligiblePageCount,
|
|
31073
|
+
...analysis.eligibleSummary,
|
|
31074
|
+
templates: analysis.templates.length,
|
|
31075
|
+
serpSnapshots: serpEvidence.reports.length,
|
|
31076
|
+
localPackSnapshots,
|
|
31077
|
+
verdict: sourceEmpty ? "Search Console returned no retained query/page rows for this date window." : filtered ? "No retained query/page rows matched the supplied terms or automatic local-intent patterns." : `${countLabel(analysis.selection.eligibleQueries, "local-intent query")} found; ${countLabel(opportunities.length, "query")} returned for review.`
|
|
31078
|
+
},
|
|
31079
|
+
opportunities,
|
|
31080
|
+
templates: analysis.templates,
|
|
31081
|
+
serpEvidence,
|
|
31082
|
+
warnings,
|
|
31083
|
+
caveats: [
|
|
31084
|
+
"Search Console exposes retained top query/page rows and does not guarantee every query. A row cap means lower-volume local demand may be missing.",
|
|
31085
|
+
"Named-place, proximity, and postal-code matching are explicit heuristics. They can miss implicit local intent and postal-looking non-location terms.",
|
|
31086
|
+
"A place in a query describes query wording, not the searcher's physical location. Search Console average position is not an exact local rank.",
|
|
31087
|
+
"A Google Analytics city or region describes measured users under Analytics rules; it is not joined here because it cannot prove which search query brought a user.",
|
|
31088
|
+
"A local_pack feature in a live snapshot proves only that the result type appeared in that market, device, and observation. It does not expose or validate every business listing.",
|
|
31089
|
+
"Repeated URL patterns and page overlap are review signals, not proof that more pages should exist or that consolidation is required.",
|
|
31090
|
+
input.includeBrand ? "Brand queries were included when they matched the local-intent rules." : "Brand-like queries were excluded using the site and supplied brand terms."
|
|
31091
|
+
],
|
|
31092
|
+
nextSteps
|
|
31093
|
+
};
|
|
31094
|
+
}
|
|
31095
|
+
//#endregion
|
|
30238
31096
|
//#region packages/core/src/analyze/monitoring/crawl-recommendations.ts
|
|
30239
31097
|
function statusMovedToError(item) {
|
|
30240
31098
|
return item.kind !== "removed" && (item.after?.status ?? 0) >= 400 && (item.before?.status ?? 200) < 400;
|
|
@@ -34790,326 +35648,6 @@ function pseoOpportunityVerdict(input) {
|
|
|
34790
35648
|
return `${expansions} template-expansion candidate${expansions === 1 ? "" : "s"} and ${input.competitors.length} observed competitor domain${input.competitors.length === 1 ? "" : "s"} were retained for review.`;
|
|
34791
35649
|
}
|
|
34792
35650
|
//#endregion
|
|
34793
|
-
//#region packages/core/src/providers/dataforseo/serp-snapshot.ts
|
|
34794
|
-
const MAX_SERP_DEPTH = 100;
|
|
34795
|
-
const SERP_ENDPOINT = "v3/serp/google/organic/live/advanced";
|
|
34796
|
-
function safeUrl$1(value) {
|
|
34797
|
-
if (!value) return null;
|
|
34798
|
-
try {
|
|
34799
|
-
const parsed = new URL(value);
|
|
34800
|
-
if (!["http:", "https:"].includes(parsed.protocol)) return null;
|
|
34801
|
-
parsed.username = "";
|
|
34802
|
-
parsed.password = "";
|
|
34803
|
-
return parsed.toString();
|
|
34804
|
-
} catch {
|
|
34805
|
-
return null;
|
|
34806
|
-
}
|
|
34807
|
-
}
|
|
34808
|
-
function validOrganic(item) {
|
|
34809
|
-
return item.type === "organic" && Number.isSafeInteger(item.rank_group) && (item.rank_group ?? 0) > 0 && Number.isSafeInteger(item.rank_absolute) && (item.rank_absolute ?? 0) > 0 && Number.isSafeInteger(item.page) && (item.page ?? 0) > 0 && Boolean(item.domain?.trim()) && safeUrl$1(item.url) !== null;
|
|
34810
|
-
}
|
|
34811
|
-
function organicResult(item) {
|
|
34812
|
-
if (!validOrganic(item)) return null;
|
|
34813
|
-
const url = safeUrl$1(item.url);
|
|
34814
|
-
if (!url) return null;
|
|
34815
|
-
return {
|
|
34816
|
-
rankGroup: item.rank_group,
|
|
34817
|
-
rankAbsolute: item.rank_absolute,
|
|
34818
|
-
page: item.page,
|
|
34819
|
-
domain: item.domain.trim().toLowerCase(),
|
|
34820
|
-
url,
|
|
34821
|
-
title: item.title ?? null,
|
|
34822
|
-
description: item.description ?? null,
|
|
34823
|
-
isFeaturedSnippet: item.is_featured_snippet ?? null
|
|
34824
|
-
};
|
|
34825
|
-
}
|
|
34826
|
-
function checkedAt(value, fallback) {
|
|
34827
|
-
const timestamp = Date.parse(value);
|
|
34828
|
-
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : fallback;
|
|
34829
|
-
}
|
|
34830
|
-
function mapDataForSeoSerpSnapshot(input, snapshot, endpoint = SERP_ENDPOINT) {
|
|
34831
|
-
const market = searchMarketSchema.parse(input.market);
|
|
34832
|
-
const keyword = normalizedKeyword$2(input.keyword);
|
|
34833
|
-
const location = locationRequest(market, "serp-snapshot");
|
|
34834
|
-
const result = snapshot.response.tasks.flatMap((task) => task.result ?? []).find((item) => normalizedKeyword$2(item.keyword) === keyword);
|
|
34835
|
-
if (!result) throw new ProviderError({
|
|
34836
|
-
provider: "dataforseo",
|
|
34837
|
-
operation: "serp-snapshot",
|
|
34838
|
-
code: "invalid-response",
|
|
34839
|
-
message: "DataForSEO returned no matching SERP result."
|
|
34840
|
-
});
|
|
34841
|
-
const warnings = [...snapshot.warnings];
|
|
34842
|
-
const items = result.items ?? [];
|
|
34843
|
-
const organicItems = items.filter((item) => item.type === "organic");
|
|
34844
|
-
const mappedOrganicResults = organicItems.flatMap((item) => {
|
|
34845
|
-
const mapped = organicResult(item);
|
|
34846
|
-
return mapped ? [mapped] : [];
|
|
34847
|
-
}).sort((left, right) => left.rankAbsolute - right.rankAbsolute || left.rankGroup - right.rankGroup || compareCodepoints$3(left.url, right.url));
|
|
34848
|
-
const organicResults = mappedOrganicResults.slice(0, input.depth);
|
|
34849
|
-
const invalidRows = organicItems.length - mappedOrganicResults.length;
|
|
34850
|
-
const resultCapped = mappedOrganicResults.length > input.depth;
|
|
34851
|
-
const invalidResultCount = result.se_results_count !== null && result.se_results_count !== void 0 && result.se_results_count < organicResults.length;
|
|
34852
|
-
if (invalidRows > 0) warnings.push({
|
|
34853
|
-
code: "invalid-organic-results",
|
|
34854
|
-
field: "organicResults",
|
|
34855
|
-
message: `DataForSEO returned ${invalidRows} organic result${invalidRows === 1 ? "" : "s"} without a valid rank, domain, or URL.`
|
|
34856
|
-
});
|
|
34857
|
-
if (!Number.isFinite(Date.parse(result.datetime))) warnings.push({
|
|
34858
|
-
code: "invalid-serp-observation-time",
|
|
34859
|
-
field: "checkedAt",
|
|
34860
|
-
message: "DataForSEO returned an invalid SERP observation time; the local response time was retained."
|
|
34861
|
-
});
|
|
34862
|
-
const checkUrl = safeUrl$1(result.check_url);
|
|
34863
|
-
if (result.check_url && !checkUrl) warnings.push({
|
|
34864
|
-
code: "invalid-serp-check-url",
|
|
34865
|
-
field: "checkUrl",
|
|
34866
|
-
message: "DataForSEO returned an invalid SERP check URL."
|
|
34867
|
-
});
|
|
34868
|
-
if (invalidResultCount) warnings.push({
|
|
34869
|
-
code: "invalid-serp-result-count",
|
|
34870
|
-
field: "resultCount",
|
|
34871
|
-
message: "DataForSEO returned an estimated result count below the retained organic result count; the estimate was discarded."
|
|
34872
|
-
});
|
|
34873
|
-
const features = [.../* @__PURE__ */ new Set([...result.item_types ?? [], ...items.map((item) => item.type)])].sort(compareCodepoints$3);
|
|
34874
|
-
return {
|
|
34875
|
-
schemaVersion: 1,
|
|
34876
|
-
provider: "dataforseo",
|
|
34877
|
-
capability: "serp-snapshot",
|
|
34878
|
-
data: {
|
|
34879
|
-
keyword,
|
|
34880
|
-
effectiveKeyword: normalizedKeyword$2(result.spell?.keyword ?? result.keyword),
|
|
34881
|
-
searchEngineDomain: result.se_domain?.trim().toLowerCase() ?? null,
|
|
34882
|
-
checkedAt: checkedAt(result.datetime, snapshot.observedAt),
|
|
34883
|
-
checkUrl,
|
|
34884
|
-
resultCount: invalidResultCount ? null : result.se_results_count ?? null,
|
|
34885
|
-
pagesCount: result.pages_count === void 0 ? null : result.pages_count,
|
|
34886
|
-
features,
|
|
34887
|
-
organicResults
|
|
34888
|
-
},
|
|
34889
|
-
observedAt: snapshot.observedAt,
|
|
34890
|
-
market: {
|
|
34891
|
-
...market,
|
|
34892
|
-
device: market.device ?? "desktop"
|
|
34893
|
-
},
|
|
34894
|
-
coverage: {
|
|
34895
|
-
requestedRows: input.depth,
|
|
34896
|
-
returnedRows: snapshot.returnedRows,
|
|
34897
|
-
retainedRows: organicResults.length,
|
|
34898
|
-
invalidRows,
|
|
34899
|
-
providerTotalRows: invalidResultCount ? null : result.se_results_count ?? null,
|
|
34900
|
-
completeness: invalidRows > 0 || invalidResultCount ? "partial" : resultCapped ? "capped" : "complete",
|
|
34901
|
-
nextCursor: null
|
|
34902
|
-
},
|
|
34903
|
-
cache: snapshot.cache,
|
|
34904
|
-
cost: snapshot.cost,
|
|
34905
|
-
request: {
|
|
34906
|
-
operation: "serp-snapshot",
|
|
34907
|
-
endpoint,
|
|
34908
|
-
limit: input.depth,
|
|
34909
|
-
filters: {
|
|
34910
|
-
countryCode: market.countryCode,
|
|
34911
|
-
languageCode: market.languageCode,
|
|
34912
|
-
device: market.device ?? "desktop",
|
|
34913
|
-
...location.locationCode !== void 0 ? { locationCode: location.locationCode } : { locationName: location.locationName }
|
|
34914
|
-
},
|
|
34915
|
-
sort: ["rankAbsolute:ascending", "url:codepoint-ascending"]
|
|
34916
|
-
},
|
|
34917
|
-
warnings
|
|
34918
|
-
};
|
|
34919
|
-
}
|
|
34920
|
-
var DataForSeoSerpSnapshotProvider = class {
|
|
34921
|
-
provider = "dataforseo";
|
|
34922
|
-
capabilitySupport = [{
|
|
34923
|
-
capability: "serp-snapshot",
|
|
34924
|
-
status: "available",
|
|
34925
|
-
markets: [{
|
|
34926
|
-
searchEngines: ["google"],
|
|
34927
|
-
devices: ["desktop", "mobile"],
|
|
34928
|
-
location: "any"
|
|
34929
|
-
}]
|
|
34930
|
-
}];
|
|
34931
|
-
client;
|
|
34932
|
-
constructor(options = {}) {
|
|
34933
|
-
this.client = options.client ?? new DataForSeoClient(options);
|
|
34934
|
-
}
|
|
34935
|
-
async serpSnapshot(input) {
|
|
34936
|
-
const market = searchMarketSchema.parse(input.market);
|
|
34937
|
-
if (market.searchEngine !== "google") throw new ProviderError({
|
|
34938
|
-
provider: "dataforseo",
|
|
34939
|
-
operation: "serp-snapshot",
|
|
34940
|
-
code: "configuration",
|
|
34941
|
-
message: "DataForSEO SERP snapshots currently support Google."
|
|
34942
|
-
});
|
|
34943
|
-
if (!Number.isSafeInteger(input.depth) || input.depth < 1 || input.depth > MAX_SERP_DEPTH) throw new ProviderError({
|
|
34944
|
-
provider: "dataforseo",
|
|
34945
|
-
operation: "serp-snapshot",
|
|
34946
|
-
code: "configuration",
|
|
34947
|
-
message: "SERP depth must be from 1 to 100."
|
|
34948
|
-
});
|
|
34949
|
-
const keyword = normalizedKeyword$2(input.keyword);
|
|
34950
|
-
if (!keyword || keyword.length > 80 || keyword.split(/\s+/u).length > 10) throw new ProviderError({
|
|
34951
|
-
provider: "dataforseo",
|
|
34952
|
-
operation: "serp-snapshot",
|
|
34953
|
-
code: "configuration",
|
|
34954
|
-
message: "SERP snapshots require a keyword of at most 80 characters and 10 words."
|
|
34955
|
-
});
|
|
34956
|
-
const context = input.context ?? {
|
|
34957
|
-
reportId: "serp-results",
|
|
34958
|
-
reportRunId: randomUUID()
|
|
34959
|
-
};
|
|
34960
|
-
const location = locationRequest(market, "serp-snapshot");
|
|
34961
|
-
return mapDataForSeoSerpSnapshot(input, await this.client.serpLive({
|
|
34962
|
-
keyword,
|
|
34963
|
-
languageCode: market.languageCode.split("-")[0],
|
|
34964
|
-
...location,
|
|
34965
|
-
device: market.device ?? "desktop",
|
|
34966
|
-
depth: input.depth,
|
|
34967
|
-
refresh: input.refresh,
|
|
34968
|
-
context
|
|
34969
|
-
}));
|
|
34970
|
-
}
|
|
34971
|
-
};
|
|
34972
|
-
//#endregion
|
|
34973
|
-
//#region packages/core/src/analyze/serp-results.ts
|
|
34974
|
-
const MAX_REPORT_DEPTH = 100;
|
|
34975
|
-
function compareCodepoints(left, right) {
|
|
34976
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
34977
|
-
}
|
|
34978
|
-
function snapshotProvider(candidate) {
|
|
34979
|
-
return "serpSnapshot" in candidate && typeof candidate.serpSnapshot === "function" ? candidate : null;
|
|
34980
|
-
}
|
|
34981
|
-
async function defaultCandidates$1() {
|
|
34982
|
-
return [{
|
|
34983
|
-
adapter: new DataForSeoSerpSnapshotProvider(),
|
|
34984
|
-
connected: Boolean(await readDataForSeoCredentials()),
|
|
34985
|
-
priority: 10
|
|
34986
|
-
}];
|
|
34987
|
-
}
|
|
34988
|
-
function providerError$2(error) {
|
|
34989
|
-
if (!(error instanceof ProviderError)) throw error;
|
|
34990
|
-
if (error.code === "rate-limit") throw new SeoError("RATE_LIMITED", error.message);
|
|
34991
|
-
if (error.code === "configuration") throw new SeoError("INVALID_INPUT", error.message);
|
|
34992
|
-
throw new SeoError("PROVIDER_UNAVAILABLE", error.message);
|
|
34993
|
-
}
|
|
34994
|
-
function validateInput(input) {
|
|
34995
|
-
const keyword = input.keyword.trim().replace(/\s+/gu, " ");
|
|
34996
|
-
if (!keyword || keyword.length > 80 || keyword.split(/\s+/u).length > 10) throw new SeoError("INVALID_INPUT", "SERP results requires a keyword of at most 80 characters and 10 words.");
|
|
34997
|
-
if (!Number.isSafeInteger(input.depth) || input.depth < 1 || input.depth > MAX_REPORT_DEPTH) throw new SeoError("INVALID_INPUT", `SERP result depth must be from 1 to ${MAX_REPORT_DEPTH}.`);
|
|
34998
|
-
const market = searchMarketSchema.safeParse(input.market);
|
|
34999
|
-
if (!market.success) throw new SeoError("INVALID_INPUT", "Use a valid search market.");
|
|
35000
|
-
const provider = input.provider ? providerIdSchema.safeParse(input.provider) : void 0;
|
|
35001
|
-
if (provider && !provider.success) throw new SeoError("INVALID_INPUT", "Use a supported SERP provider.");
|
|
35002
|
-
return {
|
|
35003
|
-
keyword,
|
|
35004
|
-
market: market.data,
|
|
35005
|
-
provider: provider?.data
|
|
35006
|
-
};
|
|
35007
|
-
}
|
|
35008
|
-
function reportStatus$1(evidence) {
|
|
35009
|
-
if (evidence.coverage.completeness === "unavailable" || evidence.coverage.completeness === "invalid") return "unavailable";
|
|
35010
|
-
return evidence.coverage.completeness === "complete" ? "complete" : "partial";
|
|
35011
|
-
}
|
|
35012
|
-
function domainSummaries(snapshot) {
|
|
35013
|
-
const byDomain = /* @__PURE__ */ new Map();
|
|
35014
|
-
for (const result of snapshot.organicResults) byDomain.set(result.domain, [...byDomain.get(result.domain) ?? [], result.rankAbsolute]);
|
|
35015
|
-
return [...byDomain.entries()].map(([domain, ranks]) => ({
|
|
35016
|
-
domain,
|
|
35017
|
-
resultCount: ranks.length,
|
|
35018
|
-
ranks: [...ranks].sort((left, right) => left - right)
|
|
35019
|
-
})).sort((left, right) => right.resultCount - left.resultCount || (left.ranks[0] ?? Number.MAX_SAFE_INTEGER) - (right.ranks[0] ?? Number.MAX_SAFE_INTEGER) || compareCodepoints(left.domain, right.domain));
|
|
35020
|
-
}
|
|
35021
|
-
function reportFindings(snapshot, domains) {
|
|
35022
|
-
const findings = [];
|
|
35023
|
-
if (snapshot.effectiveKeyword !== snapshot.keyword) findings.push({
|
|
35024
|
-
code: "query-correction",
|
|
35025
|
-
evidenceRef: "evidence.data.effectiveKeyword",
|
|
35026
|
-
principle: "A corrected query changes the observed result context and does not prove the original wording has the same intent.",
|
|
35027
|
-
detail: `The provider observed results for ${snapshot.effectiveKeyword} after the requested query ${snapshot.keyword}.`
|
|
35028
|
-
});
|
|
35029
|
-
for (const [index, domain] of domains.entries()) {
|
|
35030
|
-
if (domain.resultCount <= 1) continue;
|
|
35031
|
-
findings.push({
|
|
35032
|
-
code: "repeated-domain",
|
|
35033
|
-
evidenceRef: `domains[${index}]`,
|
|
35034
|
-
principle: "Repeated results are descriptive snapshot evidence, not a domain strength or quality score.",
|
|
35035
|
-
detail: `${domain.domain} appears ${domain.resultCount} times at absolute ranks ${domain.ranks.join(", ")}.`
|
|
35036
|
-
});
|
|
35037
|
-
}
|
|
35038
|
-
return findings.slice(0, 10);
|
|
35039
|
-
}
|
|
35040
|
-
async function serpResultsReport(input, dependencies = {}) {
|
|
35041
|
-
const depth = input.depth ?? 10;
|
|
35042
|
-
const validated = validateInput({
|
|
35043
|
-
...input,
|
|
35044
|
-
depth
|
|
35045
|
-
});
|
|
35046
|
-
let candidates;
|
|
35047
|
-
try {
|
|
35048
|
-
candidates = dependencies.candidates ?? await defaultCandidates$1();
|
|
35049
|
-
} catch (error) {
|
|
35050
|
-
return providerError$2(error);
|
|
35051
|
-
}
|
|
35052
|
-
const resolution = resolveProvider({
|
|
35053
|
-
capability: "serp-snapshot",
|
|
35054
|
-
market: validated.market,
|
|
35055
|
-
candidates,
|
|
35056
|
-
provider: validated.provider
|
|
35057
|
-
});
|
|
35058
|
-
if (resolution.status === "unavailable") throw new SeoError("PROVIDER_UNAVAILABLE", resolution.reason === "provider-not-connected" ? "No connected provider can return live SERP results. Run `seo providers dataforseo connect` first." : validated.provider ? `${validated.provider} cannot return live SERP results for this market.` : "No configured provider can return live SERP results for this market.");
|
|
35059
|
-
const provider = snapshotProvider(resolution.provider);
|
|
35060
|
-
if (!provider) throw new SeoError("PROVIDER_UNAVAILABLE", "The selected provider has no live SERP implementation.");
|
|
35061
|
-
let evidence;
|
|
35062
|
-
try {
|
|
35063
|
-
evidence = await provider.serpSnapshot({
|
|
35064
|
-
keyword: validated.keyword,
|
|
35065
|
-
market: validated.market,
|
|
35066
|
-
depth,
|
|
35067
|
-
refresh: input.refresh,
|
|
35068
|
-
context: {
|
|
35069
|
-
projectId: input.context?.projectId ?? input.projectId,
|
|
35070
|
-
reportId: input.context?.reportId ?? "serp-results",
|
|
35071
|
-
reportRunId: input.context?.reportRunId ?? randomUUID()
|
|
35072
|
-
}
|
|
35073
|
-
});
|
|
35074
|
-
} catch (error) {
|
|
35075
|
-
return providerError$2(error);
|
|
35076
|
-
}
|
|
35077
|
-
const domains = domainSummaries(evidence.data);
|
|
35078
|
-
const dataStatus = reportStatus$1(evidence);
|
|
35079
|
-
const generatedAt = (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
35080
|
-
const correctedQuery = evidence.data.effectiveKeyword !== evidence.data.keyword;
|
|
35081
|
-
return {
|
|
35082
|
-
schemaVersion: 1,
|
|
35083
|
-
generatedAt,
|
|
35084
|
-
dataStatus,
|
|
35085
|
-
market: evidence.market,
|
|
35086
|
-
summary: {
|
|
35087
|
-
keyword: evidence.data.keyword,
|
|
35088
|
-
effectiveKeyword: evidence.data.effectiveKeyword,
|
|
35089
|
-
requestedDepth: depth,
|
|
35090
|
-
organicResults: evidence.data.organicResults.length,
|
|
35091
|
-
uniqueDomains: domains.length,
|
|
35092
|
-
observedFeatures: evidence.data.features.length,
|
|
35093
|
-
correctedQuery,
|
|
35094
|
-
verdict: `${evidence.data.organicResults.length} organic results across ${domains.length} domains were retained from the ${evidence.market.device ?? "desktop"} snapshot.`
|
|
35095
|
-
},
|
|
35096
|
-
evidence,
|
|
35097
|
-
domains,
|
|
35098
|
-
findings: reportFindings(evidence.data, domains),
|
|
35099
|
-
caveats: [
|
|
35100
|
-
"This is one market, location, language, and device-specific result snapshot; it is not rank history.",
|
|
35101
|
-
"Search results can change between checks and may differ from a person's signed-in or personalized results.",
|
|
35102
|
-
"The provider result count is an estimate and may be missing; retained organic rows are bounded by the requested depth.",
|
|
35103
|
-
"Repeated domains, result features, titles, and snippets do not establish content quality, authority, or ranking feasibility."
|
|
35104
|
-
],
|
|
35105
|
-
nextSteps: [
|
|
35106
|
-
"Review the retained pages and result types before deciding whether the query fits an existing page, a new page, or no page.",
|
|
35107
|
-
"Keep Search Console average position separate from these exact snapshot ranks when comparing first-party performance.",
|
|
35108
|
-
"Repeat the same market and device check later if the decision depends on result stability."
|
|
35109
|
-
]
|
|
35110
|
-
};
|
|
35111
|
-
}
|
|
35112
|
-
//#endregion
|
|
35113
35651
|
//#region packages/core/src/analyze/pseo-opportunities/serps.ts
|
|
35114
35652
|
function selectSerpKeywords(candidates, limit) {
|
|
35115
35653
|
const seedRefs = /* @__PURE__ */ new Set();
|
|
@@ -42988,6 +43526,7 @@ const TELEMETRY_REPORTS = [
|
|
|
42988
43526
|
"crawl-history",
|
|
42989
43527
|
"crawler-rules",
|
|
42990
43528
|
"llms-txt-audit",
|
|
43529
|
+
"local-search-demand",
|
|
42991
43530
|
"generate-llms-txt",
|
|
42992
43531
|
"measure-change",
|
|
42993
43532
|
"monthly-report",
|
|
@@ -43331,6 +43870,6 @@ function isoDate(daysAgo = 0) {
|
|
|
43331
43870
|
return date.toISOString().slice(0, 10);
|
|
43332
43871
|
}
|
|
43333
43872
|
//#endregion
|
|
43334
|
-
export { AI_REFERRAL_SOURCES, AI_REFERRAL_SOURCES_VERSION, AI_SEARCH_SCORECARD_METHODOLOGY_ID, AI_SEARCH_SCORECARD_METHODOLOGY_VERSION, AUDIT_PAGE_RULE_IDS, AUTOMATIC_BASELINE_SITE_RETENTION, AUTOMATIC_BASELINE_TOTAL_RETENTION, BING_API_KEY_ENV, BING_API_KEY_SECRET, BROWSER_USER_AGENT, BingWebmasterClient, CANNIBAL_MAXIMUM_DOMINANT_SHARE, CANNIBAL_MINIMUM_PAGE_SHARE, CRAWL_REPORT_STORAGE_VERSION, CRAWL_RUN_RETENTION, CRAWL_SKIP_REASONS, CTR_DEFAULT_LIMIT, CTR_DEFAULT_MIN_IMPRESSIONS, CTR_MAX_LIMIT, CTR_MAX_MIN_IMPRESSIONS, DATAFORSEO_CREDENTIAL_SECRET, DATAFORSEO_LOGIN_ENV, DATAFORSEO_PASSWORD_ENV, DEFAULT_PROVIDER_SPEND_LIMITS, DataForSeoClient, DataForSeoKeywordDiscoveryProvider, DataForSeoKeywordMetricsProvider, DataForSeoLinkProvider, DataForSeoProvider, DataForSeoRankTrackingCollector, DataForSeoSerpSnapshotProvider, GSC_FINAL_DATA_LAG_DAYS, GSC_TIME_ZONE, INDEXNOW_ENDPOINT, INDEXNOW_KEYS_SECRET, INDEXNOW_KEY_ENV, INDEXNOW_MAX_URLS, INDEX_WATCH_ATTEMPT_RETENTION, INTERNAL_LINK_LEXICAL_TARGET_LIMIT, INTERNAL_LINK_MATCH_EVIDENCE_LIMIT, JsonFileStorageAdapter, KEYWORD_SET_FIELD_LIMITS, KEYWORD_SET_LIMITS, KEYWORD_SET_SCHEMA_SQL, LINK_RECOVER_RUN_RETENTION, LOG_MAX_AGE_MS, LOG_MAX_FILE_BYTES, LOG_MAX_TOTAL_BYTES, LocalCredentialsProvider, MAX_CRAWL_CONCURRENCY, MAX_CRAWL_DEPTH, MAX_CRAWL_PAGES, MAX_CRAWL_TIMEOUT_MS, OKF_DEFAULT_CONCEPTS, OKF_MAX_CONCEPTS, PROVIDER_SPEND_MAX_BYTES, PROVIDER_SPEND_MAX_ROWS, PROVIDER_SPEND_RETENTION_DAYS, ProviderError, RANK_TRACKING_LIMITS, RANK_TRACKING_SCHEMA_SQL, REPORT_BASELINE_MAX_AGE_MS, REPORT_BASELINE_MAX_DEPTH, REPORT_BASELINE_MAX_PAGES, ResponseSizeLimitError, SCORECARD_CHECK_WEIGHTS, SCORECARD_STATUS_CREDIT, SEO_CRAWLER_IDENTITY, SEO_CRAWLER_TOKEN, SEO_CRAWLER_USER_AGENT, SEO_VERSION, SERP_TITLE_REFERENCE_WIDTH_PX, SERP_TITLE_WIDTH_PROFILE, SemrushProvider, SeoError, ServiceAccountAccessTokenClient, SessionLedger, TELEMETRY_AGENTS, TELEMETRY_ENDPOINT, TELEMETRY_ERROR_CATEGORIES, TELEMETRY_EVENTS, TELEMETRY_NOTICE, TELEMETRY_REPORTS, TELEMETRY_SCHEMA_VERSION, TELEMETRY_TIMEOUT_MS, acquirePseoAuditEvidence, activeRankTrackingRun, addKeywordsToSet, addStatus, affectedUrls, agentReadiness, aiPromptsForQuery, aiReadiness, aiReferralSourceForValue, aiReferralsReport, aiSearchScorecard, allocateIndexUrlsToProperties, analyticsConnectionsSchema, analyzeAiReferralRows, analyzeBingCrawl, analyzeBingDimensions, analyzeBingTraffic, analyzeCannibalRows, analyzeCtrUnderperformersFromRows, analyzeDecay, analyzeIndexCoverageSignals, analyzeInternalLinksFromRows, analyzeKeywordTrend, analyzePageOpportunitiesFromRows, analyzeQueryClustersFromRows, analyzeQuickWinsFromRows, analyzeSecondPageRows, analyzeSegmentRows, analyzeStrikingDistanceRows, analyzeTrafficAnomalyRows, assertCrawlConfigLimits, assertUrlMatchesGscProperty, attachCrawlRecommendations, auditCrawlPages, auditCrawlRequests, auditLlmsTxt, auditPage, authStatus, bingAnalysisDefaults, bingWebmasterOverview, bingWebmasterSiteUrl, boundedSitemapInventory, buildOkfBundle, buildPseoAuditFromAcquisition, buildPseoAuditReportFromRows, buildQueryClusterReportFromRows, buildTelemetryPayload, cannibalReport, changeMeasurementCsvRows, checkDatabaseReadiness, classifyCommunityIntent, classifyCrawler, clearCache, clientProfileSchema, clusterPseoTemplates, clusterQueryRows, collectAgentDiscovery, collectBingLinkEvidence, collectDataForSeoLinkEvidence, combineRobotsValues, communityIntentReport, compareCrawlPages, compareCrawlReports, compareLinkEvidence, compareSegmentRows, competitorKeywordGapReport, configSchema, contentCoverageRecommendation, contentCoverageType, contentOptimizationFromPageOpportunities, contentOptimizationReport, countLabel, crawlConfigHash, crawlConfigSchema, crawlDefinitionId, crawlDiff, crawlFixToQueueItem, crawlImplementationQueueWorkflow, crawlIssueGroupSchema, crawlIssueSchema, crawlOne, crawlPageSnapshotSchema, crawlReportSchema, crawlReportStore, crawlReportSummarySchema, crawlRequestObservationSchema, crawlRunId, crawlSite, crawlSkipImpact, crawlSkipReasonCountsFromRecord, crawlStatusOnly, crawlTopFixSchema, crawlerJsonSchemas, crawlerRuleCategorySchema, crawlerRuleIdSchema, crawlerRuleInfoSchema, crawlerRuleRecommendationSchema, crawlerRuleSeveritySchema, crawlerSchemas, createAuthorizedClient, createBingWebmasterClient, createContentGroup, createCrawlReport, createCtrBenchmarkContext, createGoogleAccessTokenClient, createIndexNowKey, createIndexNowKeyRecord, createKeywordSet, createLogger, createPageRenderer, ctrUnderperformersReport, decayClusterDrafts, decayComparisonRange, decayingReport, deleteChange, deleteClient, deleteContentGroup, deleteCrawlReport, deleteDataForSeoCredentials, deleteKeywordSet, deleteProviderSecret, deleteTokens, deriveBrandTerms, detectBrandTerms, detectTelemetryAgent, diagnoseCsvFiles, diagnoseProperty, diagnosePropertyWorkflow, domainOverviewReport, effectiveRobotsDirectives, effectiveSnippetControl, emptyStatusBreakdown, enforceRankTrackingLogicalBytes, ensurePrivateDatabaseFiles, ensureSeoCliDirs, entityReadiness, estimateSerpTitleWidth, executeRankTracking, expectedCtrForPosition, explainOkfValidation, explainRule, failRankTrackingTask, fetchCruxFieldData, fetchPage, fetchPageStatus, fetchSearchStatusIncidents, fetchSitemapUrls, fetchText, fileSize, finalGscDateRange, finalizeProviderSpend, findOverlappingSearchUpdates, formatRelativeExpiry, ga4MatchReason, ga4PropertyIdFromName, ga4ReportQualityWarnings, ga4RequestCanUseCache, ga4RowsToObjects, generateLlmsTxt, geoGaps, geoGapsReport, getAuthModeStatus, getCacheStats, getChange, getClient, getClientConfig, getContentGroup, getDb, getKeywordProvider, getKeywordSet, getOrCreateRankTrackingConfiguration, getPreviousRun, getPrivacySnapshot, getProviderSpendLimits, getProviderSpendSummary, getRankTrackingRun, getRepeatedLinkRecoverUrls, getRunPages, getSeoCliPaths, getServiceAccountConfig, getServiceAccountStatus, getTelemetryStatus, getTokenStorageStatus, googleAnalyticsPropertyId, groupCrawlIssues, hasMetaRobotsDirective, hasRule, hasXRobotsDirective, hashKey, importLinkEvidence, importServerLog, indexCoveragePlan, indexCoverageSignals, indexMonitor, indexWatch, inferPseoTemplate, inferTemplateMovement, initializeTelemetry, insertCrawlRun, insertLinkRecoverRun, inspectUrl, internalLinksReport, interpretUpdateCorrelation, isBrandQuery, isCompatibleTechnicalBaseline, isCurrentTechnicalBaseline, isEntityEvidenceSchemaType, isSiteIdentitySchemaType, isSkippableReportError, isTelemetryCi, isTelemetryReport, isoDate, isoWeek, keywordDiscoverySourceSchema, keywordMetricsReport, keywordOpportunitiesReport, keywordResearchReport, keywordSetLogicalBytes, latestCrawlReport, latestCrawlSummaries, latestFinalGscDate, latestIndexWatchSummary, latestLinkRecoverSummary, latestRankTrackingRun, linkEntries, linkEvidenceKey, linkEvidenceReport, linkRecover, linkRecoverRecommendation, linkTargetContext, listChanges, listClients, listContentGroups, listCrawlReports, listGa4AccountSummaries, listGa4DataStreams, listIndexNowKeys, listKeywordSets, listRules, listSearchUpdates, listSites, loadCrawlReport, localCredentialsProvider, loginWithLoopback, makeRunId, mapDataForSeoSerpSnapshot, markRankTasksPosted, markRankTasksPosting, matchGa4WebStreams, measureChange, measureCoverage, metaRobotsDirectives, migrateLegacyMacConfig, missingOAuthClientMessage, missingSharedClientLoginMessage, monitoringStatus, monthlyReport, monthlyReportWorkflow, narrativeCsvFiles, normalizeCrawlConfig, normalizeCrawlSkipReasonCounts, normalizeForCoverage, normalizeHttpUrl, normalizeJavaScriptRenderingMode, normalizeLinkEvidenceRow, normalizeLoadedCrawlReport, normalizeSavedKeyword, noteCacheWrite, observedValue, okfConceptLimit, pageEvidenceRecommendation, pageOpportunitiesReport, parseCombinedLogLine, parseIndexNowSite, parseJsonLogLine, parseOlderThan, parsePseoPath, performanceAudit, performanceReportIsCacheable, planDueIndexUrls, planIndexCoverageFromUrls, plural, priorComparableRankTrackingRun, propertyMatchesUrl, providerCapabilitySchema, providerIdSchema, providerPreferenceSchema, providerRequestJson, providerRequestText, providerSpendLimitOverridesSchema, pruneIndexWatchSnapshots, pruneLogs, pseoAuditOptions, pseoAuditReport, pseoCsvFiles, pseoIndexStatus, pseoOpportunitiesReport, pseoPresentation, publicHttpFetch, queryClusterReport, queryClusterTokens, queryContentCoverageFromPage, queryContentFieldsFromPage, queryFieldCoverage, queryOpportunityRecommendation, queryPageMetrics, queryPageTopQuery, queryPagesMetrics, queryPagesMetricsBatch, queryPagesTopQueries, queryPagesTopQueriesBatch, querySearchAnalytics, quickWinsCsvRows, quickWinsReport, rankObservations, rankTrackingLogicalBytes, rankTrackingReport, rankTrackingTasks, rankedKeywordsReport, rankingPagesReport, readBoundedResponseText, readConfig, readDataForSeoCredentials, readOauthClient, readProviderSecret, readTokens, recommendCrawlDiffItem, recommendationForRule, recordChange, recoverRankTaskReceipt, redirectTrace, refreshAuthToken, refreshKeywordSet, refreshPrioritiesCsvFiles, refreshPrioritiesWorkflow, refreshRankTrackingRun, removeIndexNowKey, removeKeywordsFromSet, renderCrawlCsv, renderCrawlHtml, renderCrawlJunit, renderCrawlMarkdownTickets, renderCrawlPagesCsv, renderCrawlPretty, renderCsv, renderPresentationTablesMarkdown, renderPseoMarkdown, renderReportHtml, renderWorkflowMarkdown, reportNarrative, reportPresentation, requestHeaders, reserveProviderSpend, resolveIndexNowKey, resolveProvider, resolveSegmentRanges, resolveSeoCliPaths, resolveTechnicalBaseline, reviewObservations, roundedPosition, runDoctor, runGa4Report, safeError, saveClient, saveCrawlReport, saveIndexNowKey, saveRankObservation, saveRankObservations, savedKeywordSetReport, searchMarketSchema, secondPage, segmentComparisonRange, segmentImpact, segmentRangeDays, selectAffectedUrls, selectDueIndexUrls, seoErrorEnvelope, seoToAiQueryReport, serpCompetitorsReport, serpResultsReport, serverLogReport, sessionSourceInListFilter, setClientBingSite, setDefaultClient, setKeywordSetRefreshTime, setProviderSpendLimits, setTelemetryEnabled, setTokenStorageMode, shiftIsoDate, shouldExcludeBrandQuery, siteSchema, sqliteCrawlReportStore, startRankTrackingRun, strikingDistance, submitIndexNow, summarizeCrawlReport, targetMatchesDomain, pageTechnicalSignals as technicalPageSignals, technicalWatchWorkflow, telemetryErrorCategory, templateForUrl, toSeoError, tokenSchema, topCrawlRecommendations, topFixes, trackTelemetryReportComplete, trackTelemetryReportFailed, trackTelemetryReportStart, trackTelemetrySetupComplete, trafficAnomaly, unavailableSegmentImpactReport, unavailableValue, unicodeTokens, updateCorrelation, updatePostmortemCsvFiles, updatePostmortemWorkflow, validateIndexNowKey, validateIndexNowKeyRecord, validateOkfFiles, verifyIndexNowKey, verifyQueryContent, workflowPresentation, writeConfig, writeDataForSeoCredentials, writeOauthClient, writeProviderSecret, writeTokens, xRobotsDirectives };
|
|
43873
|
+
export { AI_REFERRAL_SOURCES, AI_REFERRAL_SOURCES_VERSION, AI_SEARCH_SCORECARD_METHODOLOGY_ID, AI_SEARCH_SCORECARD_METHODOLOGY_VERSION, AUDIT_PAGE_RULE_IDS, AUTOMATIC_BASELINE_SITE_RETENTION, AUTOMATIC_BASELINE_TOTAL_RETENTION, BING_API_KEY_ENV, BING_API_KEY_SECRET, BROWSER_USER_AGENT, BingWebmasterClient, CANNIBAL_MAXIMUM_DOMINANT_SHARE, CANNIBAL_MINIMUM_PAGE_SHARE, CRAWL_REPORT_STORAGE_VERSION, CRAWL_RUN_RETENTION, CRAWL_SKIP_REASONS, CTR_DEFAULT_LIMIT, CTR_DEFAULT_MIN_IMPRESSIONS, CTR_MAX_LIMIT, CTR_MAX_MIN_IMPRESSIONS, DATAFORSEO_CREDENTIAL_SECRET, DATAFORSEO_LOGIN_ENV, DATAFORSEO_PASSWORD_ENV, DEFAULT_PROVIDER_SPEND_LIMITS, DataForSeoClient, DataForSeoKeywordDiscoveryProvider, DataForSeoKeywordMetricsProvider, DataForSeoLinkProvider, DataForSeoProvider, DataForSeoRankTrackingCollector, DataForSeoSerpSnapshotProvider, GSC_FINAL_DATA_LAG_DAYS, GSC_TIME_ZONE, INDEXNOW_ENDPOINT, INDEXNOW_KEYS_SECRET, INDEXNOW_KEY_ENV, INDEXNOW_MAX_URLS, INDEX_WATCH_ATTEMPT_RETENTION, INTERNAL_LINK_LEXICAL_TARGET_LIMIT, INTERNAL_LINK_MATCH_EVIDENCE_LIMIT, JsonFileStorageAdapter, KEYWORD_SET_FIELD_LIMITS, KEYWORD_SET_LIMITS, KEYWORD_SET_SCHEMA_SQL, LINK_RECOVER_RUN_RETENTION, LOG_MAX_AGE_MS, LOG_MAX_FILE_BYTES, LOG_MAX_TOTAL_BYTES, LocalCredentialsProvider, MAX_CRAWL_CONCURRENCY, MAX_CRAWL_DEPTH, MAX_CRAWL_PAGES, MAX_CRAWL_TIMEOUT_MS, OKF_DEFAULT_CONCEPTS, OKF_MAX_CONCEPTS, PROVIDER_SPEND_MAX_BYTES, PROVIDER_SPEND_MAX_ROWS, PROVIDER_SPEND_RETENTION_DAYS, ProviderError, RANK_TRACKING_LIMITS, RANK_TRACKING_SCHEMA_SQL, REPORT_BASELINE_MAX_AGE_MS, REPORT_BASELINE_MAX_DEPTH, REPORT_BASELINE_MAX_PAGES, ResponseSizeLimitError, SCORECARD_CHECK_WEIGHTS, SCORECARD_STATUS_CREDIT, SEO_CRAWLER_IDENTITY, SEO_CRAWLER_TOKEN, SEO_CRAWLER_USER_AGENT, SEO_VERSION, SERP_TITLE_REFERENCE_WIDTH_PX, SERP_TITLE_WIDTH_PROFILE, SemrushProvider, SeoError, ServiceAccountAccessTokenClient, SessionLedger, TELEMETRY_AGENTS, TELEMETRY_ENDPOINT, TELEMETRY_ERROR_CATEGORIES, TELEMETRY_EVENTS, TELEMETRY_NOTICE, TELEMETRY_REPORTS, TELEMETRY_SCHEMA_VERSION, TELEMETRY_TIMEOUT_MS, acquirePseoAuditEvidence, activeRankTrackingRun, addKeywordsToSet, addStatus, affectedUrls, agentReadiness, aiPromptsForQuery, aiReadiness, aiReferralSourceForValue, aiReferralsReport, aiSearchScorecard, allocateIndexUrlsToProperties, analyticsConnectionsSchema, analyzeAiReferralRows, analyzeBingCrawl, analyzeBingDimensions, analyzeBingTraffic, analyzeCannibalRows, analyzeCtrUnderperformersFromRows, analyzeDecay, analyzeIndexCoverageSignals, analyzeInternalLinksFromRows, analyzeKeywordTrend, analyzeLocalSearchRows, analyzePageOpportunitiesFromRows, analyzeQueryClustersFromRows, analyzeQuickWinsFromRows, analyzeSecondPageRows, analyzeSegmentRows, analyzeStrikingDistanceRows, analyzeTrafficAnomalyRows, assertCrawlConfigLimits, assertUrlMatchesGscProperty, attachCrawlRecommendations, auditCrawlPages, auditCrawlRequests, auditLlmsTxt, auditPage, authStatus, bingAnalysisDefaults, bingWebmasterOverview, bingWebmasterSiteUrl, boundedSitemapInventory, buildOkfBundle, buildPseoAuditFromAcquisition, buildPseoAuditReportFromRows, buildQueryClusterReportFromRows, buildTelemetryPayload, cannibalReport, changeMeasurementCsvRows, checkDatabaseReadiness, classifyCommunityIntent, classifyCrawler, classifyLocalIntent, clearCache, clientProfileSchema, clusterPseoTemplates, clusterQueryRows, collectAgentDiscovery, collectBingLinkEvidence, collectDataForSeoLinkEvidence, combineRobotsValues, communityIntentReport, compareCrawlPages, compareCrawlReports, compareLinkEvidence, compareSegmentRows, competitorKeywordGapReport, configSchema, contentCoverageRecommendation, contentCoverageType, contentOptimizationFromPageOpportunities, contentOptimizationReport, countLabel, crawlConfigHash, crawlConfigSchema, crawlDefinitionId, crawlDiff, crawlFixToQueueItem, crawlImplementationQueueWorkflow, crawlIssueGroupSchema, crawlIssueSchema, crawlOne, crawlPageSnapshotSchema, crawlReportSchema, crawlReportStore, crawlReportSummarySchema, crawlRequestObservationSchema, crawlRunId, crawlSite, crawlSkipImpact, crawlSkipReasonCountsFromRecord, crawlStatusOnly, crawlTopFixSchema, crawlerJsonSchemas, crawlerRuleCategorySchema, crawlerRuleIdSchema, crawlerRuleInfoSchema, crawlerRuleRecommendationSchema, crawlerRuleSeveritySchema, crawlerSchemas, createAuthorizedClient, createBingWebmasterClient, createContentGroup, createCrawlReport, createCtrBenchmarkContext, createGoogleAccessTokenClient, createIndexNowKey, createIndexNowKeyRecord, createKeywordSet, createLocalIntentClassifier, createLogger, createPageRenderer, ctrUnderperformersReport, decayClusterDrafts, decayComparisonRange, decayingReport, deleteChange, deleteClient, deleteContentGroup, deleteCrawlReport, deleteDataForSeoCredentials, deleteKeywordSet, deleteProviderSecret, deleteTokens, deriveBrandTerms, detectBrandTerms, detectTelemetryAgent, diagnoseCsvFiles, diagnoseProperty, diagnosePropertyWorkflow, domainOverviewReport, effectiveRobotsDirectives, effectiveSnippetControl, emptyStatusBreakdown, enforceRankTrackingLogicalBytes, ensurePrivateDatabaseFiles, ensureSeoCliDirs, entityReadiness, estimateSerpTitleWidth, executeRankTracking, expectedCtrForPosition, explainOkfValidation, explainRule, failRankTrackingTask, fetchCruxFieldData, fetchPage, fetchPageStatus, fetchSearchStatusIncidents, fetchSitemapUrls, fetchText, fileSize, finalGscDateRange, finalizeProviderSpend, findOverlappingSearchUpdates, formatRelativeExpiry, ga4MatchReason, ga4PropertyIdFromName, ga4ReportQualityWarnings, ga4RequestCanUseCache, ga4RowsToObjects, generateLlmsTxt, geoGaps, geoGapsReport, getAuthModeStatus, getCacheStats, getChange, getClient, getClientConfig, getContentGroup, getDb, getKeywordProvider, getKeywordSet, getOrCreateRankTrackingConfiguration, getPreviousRun, getPrivacySnapshot, getProviderSpendLimits, getProviderSpendSummary, getRankTrackingRun, getRepeatedLinkRecoverUrls, getRunPages, getSeoCliPaths, getServiceAccountConfig, getServiceAccountStatus, getTelemetryStatus, getTokenStorageStatus, googleAnalyticsPropertyId, groupCrawlIssues, hasMetaRobotsDirective, hasRule, hasXRobotsDirective, hashKey, importLinkEvidence, importServerLog, indexCoveragePlan, indexCoverageSignals, indexMonitor, indexWatch, inferPseoTemplate, inferTemplateMovement, initializeTelemetry, insertCrawlRun, insertLinkRecoverRun, inspectUrl, internalLinksReport, interpretUpdateCorrelation, isBrandQuery, isCompatibleTechnicalBaseline, isCurrentTechnicalBaseline, isEntityEvidenceSchemaType, isSiteIdentitySchemaType, isSkippableReportError, isTelemetryCi, isTelemetryReport, isoDate, isoWeek, keywordDiscoverySourceSchema, keywordMetricsReport, keywordOpportunitiesReport, keywordResearchReport, keywordSetLogicalBytes, latestCrawlReport, latestCrawlSummaries, latestFinalGscDate, latestIndexWatchSummary, latestLinkRecoverSummary, latestRankTrackingRun, linkEntries, linkEvidenceKey, linkEvidenceReport, linkRecover, linkRecoverRecommendation, linkTargetContext, listChanges, listClients, listContentGroups, listCrawlReports, listGa4AccountSummaries, listGa4DataStreams, listIndexNowKeys, listKeywordSets, listRules, listSearchUpdates, listSites, loadCrawlReport, localCredentialsProvider, localSearchReport, loginWithLoopback, makeRunId, mapDataForSeoSerpSnapshot, markRankTasksPosted, markRankTasksPosting, matchGa4WebStreams, measureChange, measureCoverage, metaRobotsDirectives, migrateLegacyMacConfig, missingOAuthClientMessage, missingSharedClientLoginMessage, monitoringStatus, monthlyReport, monthlyReportWorkflow, narrativeCsvFiles, normalizeCrawlConfig, normalizeCrawlSkipReasonCounts, normalizeForCoverage, normalizeHttpUrl, normalizeJavaScriptRenderingMode, normalizeLinkEvidenceRow, normalizeLoadedCrawlReport, normalizeLocalSearchText, normalizeLocationTerms, normalizeSavedKeyword, noteCacheWrite, observedValue, okfConceptLimit, pageEvidenceRecommendation, pageOpportunitiesReport, parseCombinedLogLine, parseIndexNowSite, parseJsonLogLine, parseOlderThan, parsePseoPath, performanceAudit, performanceReportIsCacheable, planDueIndexUrls, planIndexCoverageFromUrls, plural, priorComparableRankTrackingRun, propertyMatchesUrl, providerCapabilitySchema, providerIdSchema, providerPreferenceSchema, providerRequestJson, providerRequestText, providerSpendLimitOverridesSchema, pruneIndexWatchSnapshots, pruneLogs, pseoAuditOptions, pseoAuditReport, pseoCsvFiles, pseoIndexStatus, pseoOpportunitiesReport, pseoPresentation, publicHttpFetch, queryClusterReport, queryClusterTokens, queryContentCoverageFromPage, queryContentFieldsFromPage, queryFieldCoverage, queryOpportunityRecommendation, queryPageMetrics, queryPageTopQuery, queryPagesMetrics, queryPagesMetricsBatch, queryPagesTopQueries, queryPagesTopQueriesBatch, querySearchAnalytics, quickWinsCsvRows, quickWinsReport, rankObservations, rankTrackingLogicalBytes, rankTrackingReport, rankTrackingTasks, rankedKeywordsReport, rankingPagesReport, readBoundedResponseText, readConfig, readDataForSeoCredentials, readOauthClient, readProviderSecret, readTokens, recommendCrawlDiffItem, recommendationForRule, recordChange, recoverRankTaskReceipt, redirectTrace, refreshAuthToken, refreshKeywordSet, refreshPrioritiesCsvFiles, refreshPrioritiesWorkflow, refreshRankTrackingRun, removeIndexNowKey, removeKeywordsFromSet, renderCrawlCsv, renderCrawlHtml, renderCrawlJunit, renderCrawlMarkdownTickets, renderCrawlPagesCsv, renderCrawlPretty, renderCsv, renderPresentationTablesMarkdown, renderPseoMarkdown, renderReportHtml, renderWorkflowMarkdown, reportNarrative, reportPresentation, requestHeaders, reserveProviderSpend, resolveIndexNowKey, resolveProvider, resolveSegmentRanges, resolveSeoCliPaths, resolveTechnicalBaseline, reviewObservations, roundedPosition, runDoctor, runGa4Report, safeError, saveClient, saveCrawlReport, saveIndexNowKey, saveRankObservation, saveRankObservations, savedKeywordSetReport, searchMarketSchema, secondPage, segmentComparisonRange, segmentImpact, segmentRangeDays, selectAffectedUrls, selectDueIndexUrls, seoErrorEnvelope, seoToAiQueryReport, serpCompetitorsReport, serpResultsReport, serverLogReport, sessionSourceInListFilter, setClientBingSite, setDefaultClient, setKeywordSetRefreshTime, setProviderSpendLimits, setTelemetryEnabled, setTokenStorageMode, shiftIsoDate, shouldExcludeBrandQuery, siteSchema, sqliteCrawlReportStore, startRankTrackingRun, strikingDistance, submitIndexNow, summarizeCrawlReport, targetMatchesDomain, pageTechnicalSignals as technicalPageSignals, technicalWatchWorkflow, telemetryErrorCategory, templateForUrl, toSeoError, tokenSchema, topCrawlRecommendations, topFixes, trackTelemetryReportComplete, trackTelemetryReportFailed, trackTelemetryReportStart, trackTelemetrySetupComplete, trafficAnomaly, unavailableSegmentImpactReport, unavailableValue, unicodeTokens, updateCorrelation, updatePostmortemCsvFiles, updatePostmortemWorkflow, validateIndexNowKey, validateIndexNowKeyRecord, validateOkfFiles, verifyIndexNowKey, verifyQueryContent, workflowPresentation, writeConfig, writeDataForSeoCredentials, writeOauthClient, writeProviderSecret, writeTokens, xRobotsDirectives };
|
|
43335
43874
|
|
|
43336
43875
|
//# sourceMappingURL=index.js.map
|