seo 0.12.1 → 0.13.0
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/PRIVACY.md +1 -1
- package/README.md +1 -1
- package/dist/cli.js +85 -85
- package/dist/cli.js.map +1 -1
- package/dist/{dist-GOZ_hkEB.js → dist-CLV_HXUd.js} +1 -1
- package/dist/dist-wzZsD_Fi.js +420 -0
- package/dist/dist-wzZsD_Fi.js.map +1 -0
- package/dist/{esm-Cyusbw2J.js → esm-BfNQ2FiC.js} +140 -176
- package/dist/esm-BfNQ2FiC.js.map +1 -0
- package/dist/index.d.ts +827 -788
- package/dist/index.js +334 -147
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +11 -11
- package/dist/mcp.js +506 -306
- package/dist/mcp.js.map +1 -1
- package/dist/provider-sdk.d.ts +26 -27
- package/package.json +17 -17
- package/skills/seo/SKILL.md +4 -3
- package/dist/dist-U5Io-lSG.js +0 -393
- package/dist/dist-U5Io-lSG.js.map +0 -1
- package/dist/esm-Cyusbw2J.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-BfNQ2FiC.js";
|
|
3
3
|
import { SEO_PROVIDER_API_VERSION } from "./provider-sdk.js";
|
|
4
4
|
import crypto, { createHash, randomUUID, sign } from "node:crypto";
|
|
5
5
|
import { z } from "zod";
|
|
@@ -1678,7 +1678,7 @@ async function listAnalyticsAccounts(gfetch, options) {
|
|
|
1678
1678
|
return googleJson(gfetch, url.toString(), {
|
|
1679
1679
|
...options,
|
|
1680
1680
|
label: "Google Analytics accounts",
|
|
1681
|
-
maxBytes:
|
|
1681
|
+
maxBytes: 10485760
|
|
1682
1682
|
});
|
|
1683
1683
|
});
|
|
1684
1684
|
}
|
|
@@ -1687,7 +1687,7 @@ async function listAnalyticsDataStreams(gfetch, propertyId, options) {
|
|
|
1687
1687
|
...options,
|
|
1688
1688
|
label: "Google Analytics data streams",
|
|
1689
1689
|
subject: `property ${propertyId}`,
|
|
1690
|
-
maxBytes:
|
|
1690
|
+
maxBytes: 1048576
|
|
1691
1691
|
})).dataStreams ?? [];
|
|
1692
1692
|
}
|
|
1693
1693
|
async function runAnalyticsReport(gfetch, propertyId, body, options) {
|
|
@@ -1736,13 +1736,13 @@ function day$2(value) {
|
|
|
1736
1736
|
const date = legacy ? new Date(Number(legacy[1])) : new Date(value);
|
|
1737
1737
|
return Number.isNaN(date.getTime()) ? null : date.toISOString().slice(0, 10);
|
|
1738
1738
|
}
|
|
1739
|
-
const count$
|
|
1740
|
-
function list$
|
|
1739
|
+
const count$3 = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
1740
|
+
function list$2(data, label) {
|
|
1741
1741
|
if (!Array.isArray(data)) throw new SeoError("OPTIONAL_PROVIDER_UNAVAILABLE", `Bing Webmaster returned an invalid ${label}.`);
|
|
1742
1742
|
return data.filter((item) => Boolean(item) && typeof item === "object");
|
|
1743
1743
|
}
|
|
1744
1744
|
async function listBingSites(call) {
|
|
1745
|
-
return list$
|
|
1745
|
+
return list$2(await call("GetUserSites"), "site list").filter((row) => typeof row.Url === "string").map((row) => ({
|
|
1746
1746
|
url: row.Url,
|
|
1747
1747
|
verified: row.IsVerified === true
|
|
1748
1748
|
})).sort((a, b) => a.url.localeCompare(b.url, "en"));
|
|
@@ -1750,10 +1750,10 @@ async function listBingSites(call) {
|
|
|
1750
1750
|
async function bingTraffic(call, site) {
|
|
1751
1751
|
const rows = [];
|
|
1752
1752
|
let invalidRows = 0;
|
|
1753
|
-
for (const row of list$
|
|
1753
|
+
for (const row of list$2(await call("GetRankAndTrafficStats", { siteUrl: site }), "traffic list")) {
|
|
1754
1754
|
const date = day$2(row.Date);
|
|
1755
|
-
const clicks = count$
|
|
1756
|
-
const impressions = count$
|
|
1755
|
+
const clicks = count$3(row.Clicks);
|
|
1756
|
+
const impressions = count$3(row.Impressions);
|
|
1757
1757
|
if (!date || clicks === null || impressions === null) {
|
|
1758
1758
|
invalidRows += 1;
|
|
1759
1759
|
continue;
|
|
@@ -1773,12 +1773,12 @@ async function bingTraffic(call, site) {
|
|
|
1773
1773
|
async function keyed(call, method, params) {
|
|
1774
1774
|
const rows = [];
|
|
1775
1775
|
let invalidRows = 0;
|
|
1776
|
-
for (const row of list$
|
|
1776
|
+
for (const row of list$2(await call(method, params), "row list")) {
|
|
1777
1777
|
const date = day$2(row.Date);
|
|
1778
1778
|
const key = typeof row.Query === "string" ? row.Query.trim() : "";
|
|
1779
|
-
const clicks = count$
|
|
1780
|
-
const impressions = count$
|
|
1781
|
-
const position = count$
|
|
1779
|
+
const clicks = count$3(row.Clicks);
|
|
1780
|
+
const impressions = count$3(row.Impressions);
|
|
1781
|
+
const position = count$3(row.AvgImpressionPosition);
|
|
1782
1782
|
if (!date || !key || clicks === null || impressions === null) {
|
|
1783
1783
|
invalidRows += 1;
|
|
1784
1784
|
continue;
|
|
@@ -2313,7 +2313,7 @@ function sharedReader(read, redirects = /* @__PURE__ */ new Map()) {
|
|
|
2313
2313
|
return found;
|
|
2314
2314
|
};
|
|
2315
2315
|
}
|
|
2316
|
-
const MAX_STATUS_BYTES =
|
|
2316
|
+
const MAX_STATUS_BYTES = 10485760;
|
|
2317
2317
|
async function googleUpdates(window) {
|
|
2318
2318
|
if (!window) return [];
|
|
2319
2319
|
const incidents = await fetch("https://status.search.google.com/incidents.json", { signal: AbortSignal.timeout(8e3) }).then((response) => response.ok ? readJson(response, "Google Search Status", { maxBytes: MAX_STATUS_BYTES }) : null).catch(() => null);
|
|
@@ -3631,6 +3631,15 @@ async function cloudCredentials(options = {}) {
|
|
|
3631
3631
|
})();
|
|
3632
3632
|
return credentials;
|
|
3633
3633
|
}
|
|
3634
|
+
async function cloudWeek() {
|
|
3635
|
+
const token = cloudToken();
|
|
3636
|
+
if (!token) throw new SeoError("AUTH_REQUIRED", notSignedIn());
|
|
3637
|
+
const result = await call$1("/account/cli/week", { token });
|
|
3638
|
+
if (result.status === 401) throw new SeoError("AUTH_EXPIRED", `This machine is not linked to a seoskill.dev account any more. ${CLOUD_SIGN_IN}`);
|
|
3639
|
+
if (result.status === 403) return null;
|
|
3640
|
+
if (!result.ok) throw new SeoError("PROVIDER_UNAVAILABLE", `seoskill.dev returned ${result.status}. Try again in a minute.`);
|
|
3641
|
+
return result.body;
|
|
3642
|
+
}
|
|
3634
3643
|
function notSignedIn() {
|
|
3635
3644
|
return "Not signed in. Run `seo login` to use your seoskill.dev account on this machine (it prints a link to approve), or `seo auth login` to sign in with Google on this machine only. `seo crawl --url <url>` works without sign-in.";
|
|
3636
3645
|
}
|
|
@@ -4768,9 +4777,9 @@ function googleFetchFor(client) {
|
|
|
4768
4777
|
const LOCAL_SIGN_IN = { signIn: "Run `seo auth login` again." };
|
|
4769
4778
|
//#endregion
|
|
4770
4779
|
//#region packages/core/src/storage/cache-maintenance.ts
|
|
4771
|
-
const MEBIBYTE$2 =
|
|
4780
|
+
const MEBIBYTE$2 = 1048576;
|
|
4772
4781
|
const CACHE_MAX_BYTES = 256 * MEBIBYTE$2;
|
|
4773
|
-
const CACHE_MAX_AGE_MS =
|
|
4782
|
+
const CACHE_MAX_AGE_MS = 2592e6;
|
|
4774
4783
|
const CACHE_LIMITS = {
|
|
4775
4784
|
gsc_cache: 48 * MEBIBYTE$2,
|
|
4776
4785
|
url_inspection_cache: 16 * MEBIBYTE$2,
|
|
@@ -6427,7 +6436,7 @@ async function extractMainMarkdown(fetchResult) {
|
|
|
6427
6436
|
const cut = markdown.slice(0, MARKDOWN_LIMIT);
|
|
6428
6437
|
const end = cut.lastIndexOf("\n");
|
|
6429
6438
|
return {
|
|
6430
|
-
markdown: (end >
|
|
6439
|
+
markdown: (end > 1e4 ? cut.slice(0, end) : cut).trimEnd(),
|
|
6431
6440
|
characters: markdown.length,
|
|
6432
6441
|
truncated: true
|
|
6433
6442
|
};
|
|
@@ -7691,7 +7700,7 @@ async function fetchWithRedirectChain(url, signal) {
|
|
|
7691
7700
|
}
|
|
7692
7701
|
//#endregion
|
|
7693
7702
|
//#region packages/core/src/fetch/page-fetcher/robots.ts
|
|
7694
|
-
const MAX_ROBOTS_RESPONSE_BYTES =
|
|
7703
|
+
const MAX_ROBOTS_RESPONSE_BYTES = 1048576;
|
|
7695
7704
|
var RobotsAccessError = class extends Error {
|
|
7696
7705
|
reason;
|
|
7697
7706
|
evidence;
|
|
@@ -7865,7 +7874,7 @@ function diagnosticRobotsEvidence(robots) {
|
|
|
7865
7874
|
}
|
|
7866
7875
|
//#endregion
|
|
7867
7876
|
//#region packages/core/src/fetch/page-fetcher/plain.ts
|
|
7868
|
-
const MAX_PAGE_RESPONSE_BYTES =
|
|
7877
|
+
const MAX_PAGE_RESPONSE_BYTES = 5242880;
|
|
7869
7878
|
function encodePageFetchCacheEvidence(result) {
|
|
7870
7879
|
return JSON.stringify({
|
|
7871
7880
|
finalUrl: result.finalUrl,
|
|
@@ -8630,7 +8639,7 @@ function assertUrlMatchesGscProperty(property, value) {
|
|
|
8630
8639
|
if (!url.startsWith(prefix)) return invalid(`${url} is outside the Search Console property ${property}.`);
|
|
8631
8640
|
return url;
|
|
8632
8641
|
}
|
|
8633
|
-
const RESERVATION_TTL_MS =
|
|
8642
|
+
const RESERVATION_TTL_MS = 3e5;
|
|
8634
8643
|
var UrlInspectionQuotaError = class extends SeoError {
|
|
8635
8644
|
property;
|
|
8636
8645
|
requestSent;
|
|
@@ -8675,8 +8684,8 @@ function reconcileExpiredReservations(input) {
|
|
|
8675
8684
|
WHERE credential_key = ? AND property_site_url = ? AND quota_date = ?`).run(expired.count, expired.count, input.now, input.credentialKey, input.property, input.quotaDate);
|
|
8676
8685
|
}
|
|
8677
8686
|
function pruneOldQuotaRows(database, now) {
|
|
8678
|
-
const reservationCutoff = now -
|
|
8679
|
-
const bucketCutoff = (/* @__PURE__ */ new Date(now -
|
|
8687
|
+
const reservationCutoff = now - 1728e5;
|
|
8688
|
+
const bucketCutoff = (/* @__PURE__ */ new Date(now - 6048e5)).toISOString().slice(0, 10);
|
|
8680
8689
|
database.prepare(`DELETE FROM url_inspection_quota_reservations
|
|
8681
8690
|
WHERE expires_at < ?`).run(reservationCutoff);
|
|
8682
8691
|
database.prepare(`DELETE FROM url_inspection_quota_buckets
|
|
@@ -10731,7 +10740,7 @@ const actionPriority = new Map([
|
|
|
10731
10740
|
"unverified",
|
|
10732
10741
|
"covered"
|
|
10733
10742
|
].map((type, index) => [type, index]));
|
|
10734
|
-
function plural$
|
|
10743
|
+
function plural$8(count, singular, pluralLabel) {
|
|
10735
10744
|
return count === 1 ? singular : pluralLabel;
|
|
10736
10745
|
}
|
|
10737
10746
|
function actionable(item) {
|
|
@@ -10765,7 +10774,7 @@ function pageOpportunityVerdict(input) {
|
|
|
10765
10774
|
if (focus === "unverified") return "GSC visibility exists, but the page was not verified and no specific change is justified yet.";
|
|
10766
10775
|
if (focus === "covered") return "The verified page covers the sampled queries and CTR is not materially below its directional benchmark.";
|
|
10767
10776
|
const count = analysis.summary.opportunities;
|
|
10768
|
-
const prefix = `${count} ${plural$
|
|
10777
|
+
const prefix = `${count} ${plural$8(count, "actionable observation", "actionable observations")} found.`;
|
|
10769
10778
|
if (focus === "content-gap") return `${prefix} Verified body coverage is the main issue; confirm intent before expanding the page.`;
|
|
10770
10779
|
if (focus === "serp-framing") return `${prefix} The body is relevant, but its title, meta description, or H1 framing is weak.`;
|
|
10771
10780
|
if (focus === "ranking") return `${prefix} Most upside requires stronger relevance or authority; no CTR-only lift is claimed for page-two queries.`;
|
|
@@ -10780,7 +10789,7 @@ function pageOpportunityRecommendations(analysis) {
|
|
|
10780
10789
|
}
|
|
10781
10790
|
const recommendations = [`Start with "${top.query}". ${top.recommendation}`];
|
|
10782
10791
|
const technical = actions.filter((item) => item.opportunityType === "technical-check").length;
|
|
10783
|
-
if (technical) recommendations.push(`Resolve the technical evidence affecting ${technical} ${plural$
|
|
10792
|
+
if (technical) recommendations.push(`Resolve the technical evidence affecting ${technical} ${plural$8(technical, "row", "rows")} before content work.`);
|
|
10784
10793
|
const gaps = actions.filter((item) => item.opportunityType === "content-gap").length;
|
|
10785
10794
|
if (gaps >= 3) recommendations.push(`Group the ${gaps} verified content gaps into useful sections rather than adding one thin paragraph per query.`);
|
|
10786
10795
|
return recommendations;
|
|
@@ -11207,8 +11216,8 @@ function urlKey(value) {
|
|
|
11207
11216
|
}
|
|
11208
11217
|
}
|
|
11209
11218
|
const SLOW_RESPONSE_MS = 2e3;
|
|
11210
|
-
const LARGE_HTML_BYTES =
|
|
11211
|
-
const COMPRESSION_MIN_BYTES =
|
|
11219
|
+
const LARGE_HTML_BYTES = 2097152;
|
|
11220
|
+
const COMPRESSION_MIN_BYTES = 4096;
|
|
11212
11221
|
const DEEP_PAGE_DEPTH = 4;
|
|
11213
11222
|
const WEAK_VALUABLE_INLINKS = 1;
|
|
11214
11223
|
const QUERY_COVERAGE_MIN_IMPRESSIONS = 50;
|
|
@@ -12969,8 +12978,10 @@ function summarizeCrawlReport(input) {
|
|
|
12969
12978
|
if (request.outcome === "response") {
|
|
12970
12979
|
responseRequests += 1;
|
|
12971
12980
|
if (request.extraction === "failed") extractionFailures += 1;
|
|
12972
|
-
} else if (request.outcome === "failure")
|
|
12973
|
-
|
|
12981
|
+
} else if (request.outcome === "failure") {
|
|
12982
|
+
if (request.failureKind === "aborted") abortedRequests += 1;
|
|
12983
|
+
else failedRequests += 1;
|
|
12984
|
+
}
|
|
12974
12985
|
}
|
|
12975
12986
|
let highIssues = 0;
|
|
12976
12987
|
let mediumIssues = 0;
|
|
@@ -14336,7 +14347,7 @@ function cancellationRace(signal) {
|
|
|
14336
14347
|
}
|
|
14337
14348
|
//#endregion
|
|
14338
14349
|
//#region packages/core/src/analyze/crawler/crawl-memory.ts
|
|
14339
|
-
const MEBIBYTE$1 =
|
|
14350
|
+
const MEBIBYTE$1 = 1048576;
|
|
14340
14351
|
const MIN_CRAWL_MEMORY_LIMIT = 384 * MEBIBYTE$1;
|
|
14341
14352
|
const MAX_CRAWL_MEMORY_LIMIT = 640 * MEBIBYTE$1;
|
|
14342
14353
|
const MIN_CRAWL_RSS_LIMIT = 512 * MEBIBYTE$1;
|
|
@@ -14739,7 +14750,7 @@ async function collectCrawlSiteChecks(input) {
|
|
|
14739
14750
|
}
|
|
14740
14751
|
//#endregion
|
|
14741
14752
|
//#region packages/core/src/analyze/crawler/site-crawl-ai-signals.ts
|
|
14742
|
-
const MAX_AUXILIARY_RESPONSE_BYTES =
|
|
14753
|
+
const MAX_AUXILIARY_RESPONSE_BYTES = 2097152;
|
|
14743
14754
|
const LLMS_LINK_LIMIT = 20;
|
|
14744
14755
|
const AI_BOTS = [
|
|
14745
14756
|
["OAI-SearchBot", "search"],
|
|
@@ -16424,7 +16435,7 @@ const identifierPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
|
16424
16435
|
const packageNamePattern = /^(?:@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*|[a-z0-9][a-z0-9._-]*)$/u;
|
|
16425
16436
|
const exactVersionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
|
|
16426
16437
|
const integrityPattern = /^sha(?:256|384|512)-[A-Za-z0-9+/=]+$/u;
|
|
16427
|
-
const MAX_SCHEMA_BYTES =
|
|
16438
|
+
const MAX_SCHEMA_BYTES = 65536;
|
|
16428
16439
|
const MAX_SCHEMA_DEPTH = 16;
|
|
16429
16440
|
const MAX_SCHEMA_VALUES = 5e3;
|
|
16430
16441
|
function boundedJsonObject(value) {
|
|
@@ -16497,7 +16508,7 @@ const providerCapabilityAdapterSchema = z.union([landingPageVisitsCapabilitySche
|
|
|
16497
16508
|
const providerActionAdapterSchema = z.custom((value) => {
|
|
16498
16509
|
if (typeof value !== "object" || value === null) return false;
|
|
16499
16510
|
const action = value;
|
|
16500
|
-
return typeof action.id === "string" && providerExtensionIdSchema.safeParse(action.id).success && typeof action.description === "string" && action.description.trim().length >= 1 && action.description.length <= 240 && supportedJsonSchema(action.inputSchema, true) && supportedJsonSchema(action.outputSchema) && (action.cacheTtlMs === void 0 || Number.isSafeInteger(action.cacheTtlMs) && action.cacheTtlMs >= 0 && action.cacheTtlMs <=
|
|
16511
|
+
return typeof action.id === "string" && providerExtensionIdSchema.safeParse(action.id).success && typeof action.description === "string" && action.description.trim().length >= 1 && action.description.length <= 240 && supportedJsonSchema(action.inputSchema, true) && supportedJsonSchema(action.outputSchema) && (action.cacheTtlMs === void 0 || Number.isSafeInteger(action.cacheTtlMs) && action.cacheTtlMs >= 0 && action.cacheTtlMs <= 6048e5) && typeof action.run === "function";
|
|
16501
16512
|
}, "Invalid provider action.");
|
|
16502
16513
|
const providerPackageManifestSchema = z.object({
|
|
16503
16514
|
apiVersion: z.literal(1),
|
|
@@ -16928,8 +16939,8 @@ async function loadInstalledProviderExtensions(options = {}) {
|
|
|
16928
16939
|
//#endregion
|
|
16929
16940
|
//#region packages/core/src/provider-extensions/runtime.ts
|
|
16930
16941
|
const DEFAULT_MAX_REQUESTS = 8;
|
|
16931
|
-
const MAX_RESPONSE_BYTES$1 =
|
|
16932
|
-
const MAX_REQUEST_BODY_BYTES =
|
|
16942
|
+
const MAX_RESPONSE_BYTES$1 = 10485760;
|
|
16943
|
+
const MAX_REQUEST_BODY_BYTES = 65536;
|
|
16933
16944
|
const TIMEOUT_MS$3 = 15e3;
|
|
16934
16945
|
function normalizeProviderExtensionAccount(provider, account) {
|
|
16935
16946
|
const fields = provider.connection.fields.filter((field) => field.kind === "account");
|
|
@@ -16999,7 +17010,7 @@ async function verifyProviderExtension(input) {
|
|
|
16999
17010
|
}
|
|
17000
17011
|
//#endregion
|
|
17001
17012
|
//#region packages/core/src/provider-extensions/analytics.ts
|
|
17002
|
-
const CACHE_TTL_MS$1 =
|
|
17013
|
+
const CACHE_TTL_MS$1 = 864e5;
|
|
17003
17014
|
const landingPageResultSchema = z.object({
|
|
17004
17015
|
metric: z.literal("landing-page-visits"),
|
|
17005
17016
|
rows: z.array(z.object({
|
|
@@ -17601,16 +17612,18 @@ async function crawlSite(input, dependencies) {
|
|
|
17601
17612
|
return;
|
|
17602
17613
|
}
|
|
17603
17614
|
if (queue.size + visited.size + inFlight.size >= config.maxPages * 5) {
|
|
17604
|
-
if (!queueSafetyDeferredUrls.has(normalized))
|
|
17605
|
-
queueSafetyDeferredUrls.
|
|
17606
|
-
|
|
17607
|
-
|
|
17608
|
-
|
|
17609
|
-
|
|
17610
|
-
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17615
|
+
if (!queueSafetyDeferredUrls.has(normalized)) {
|
|
17616
|
+
if (queueSafetyDeferredUrls.size < maxTrackedQueueSafetyUrls) {
|
|
17617
|
+
queueSafetyDeferredUrls.add(normalized);
|
|
17618
|
+
queueSafetySkippedUrls = queueSafetyDeferredUrls.size;
|
|
17619
|
+
recordSkip("queue-safety-limit");
|
|
17620
|
+
emitStatus("url_skipped", {
|
|
17621
|
+
url: normalized,
|
|
17622
|
+
depth,
|
|
17623
|
+
reason: "queue_safety_limit"
|
|
17624
|
+
});
|
|
17625
|
+
} else queueSafetyInventoryCapped = true;
|
|
17626
|
+
}
|
|
17614
17627
|
return;
|
|
17615
17628
|
}
|
|
17616
17629
|
discovered.add(normalized);
|
|
@@ -17716,12 +17729,14 @@ async function crawlSite(input, dependencies) {
|
|
|
17716
17729
|
extraction: "not-applicable"
|
|
17717
17730
|
});
|
|
17718
17731
|
requests.push(request);
|
|
17719
|
-
if (healthStrategy)
|
|
17720
|
-
|
|
17721
|
-
|
|
17722
|
-
|
|
17723
|
-
|
|
17724
|
-
|
|
17732
|
+
if (healthStrategy) {
|
|
17733
|
+
if (request.outcome === "response" && request.status < 400 && !request.accessBlock) {
|
|
17734
|
+
healthyProbeStreak += 1;
|
|
17735
|
+
activeConcurrency = Math.min(config.concurrency, 1 + Math.floor(healthyProbeStreak / 20));
|
|
17736
|
+
} else {
|
|
17737
|
+
healthyProbeStreak = 0;
|
|
17738
|
+
activeConcurrency = 1;
|
|
17739
|
+
}
|
|
17725
17740
|
}
|
|
17726
17741
|
checkMemoryPressure();
|
|
17727
17742
|
if (request.outcome === "skipped") {
|
|
@@ -17854,14 +17869,16 @@ async function crawlSite(input, dependencies) {
|
|
|
17854
17869
|
depth: task.depth,
|
|
17855
17870
|
statusCode: page.status
|
|
17856
17871
|
});
|
|
17857
|
-
if (followLinks && task.depth < config.maxDepth)
|
|
17858
|
-
|
|
17859
|
-
|
|
17860
|
-
|
|
17861
|
-
|
|
17872
|
+
if (followLinks && task.depth < config.maxDepth) {
|
|
17873
|
+
if (memoryLimited) for (const value of result.urls) {
|
|
17874
|
+
const normalized = normalizeUrl$1(value, finalUrl);
|
|
17875
|
+
if (normalized && sameOrigin(normalized, origin) && !isLikelyAsset(normalized) && passesFilters(normalized, config.include, config.exclude) && !visited.has(normalized) && !queue.has(normalized)) {
|
|
17876
|
+
discovered.add(normalized);
|
|
17877
|
+
memoryDeferredUrls.add(normalized);
|
|
17878
|
+
}
|
|
17862
17879
|
}
|
|
17880
|
+
else for (const url of result.urls) enqueue(url, task.depth + 1);
|
|
17863
17881
|
}
|
|
17864
|
-
else for (const url of result.urls) enqueue(url, task.depth + 1);
|
|
17865
17882
|
}
|
|
17866
17883
|
const memoryPressureSkippedUrls = memoryLimited ? queue.size + memoryDeferredUrls.size : 0;
|
|
17867
17884
|
const stoppedForMemory = memoryPressureSkippedUrls > 0;
|
|
@@ -18558,7 +18575,7 @@ function analyzeCtrUnderperformersFromRows(input) {
|
|
|
18558
18575
|
const MAX_GSC_ROWS$3 = 1e5;
|
|
18559
18576
|
const DEFAULT_DAYS$3 = 28;
|
|
18560
18577
|
const MAX_DAYS$2 = 548;
|
|
18561
|
-
function plural$
|
|
18578
|
+
function plural$7(count, singular, pluralLabel = `${singular}s`) {
|
|
18562
18579
|
return count === 1 ? singular : pluralLabel;
|
|
18563
18580
|
}
|
|
18564
18581
|
async function ctrUnderperformersReportWith(input, dependencies) {
|
|
@@ -18661,11 +18678,11 @@ async function ctrUnderperformersReportWith(input, dependencies) {
|
|
|
18661
18678
|
minImpressions,
|
|
18662
18679
|
limit,
|
|
18663
18680
|
brandFiltering: input.includeBrand ? "included" : "excluded",
|
|
18664
|
-
verdict: top ? `${selection.eligibleUnderperformers} CTR ${plural$
|
|
18681
|
+
verdict: top ? `${selection.eligibleUnderperformers} CTR ${plural$7(selection.eligibleUnderperformers, "underperformer")} found in retained rows, with a calculated heuristic shortfall of ${totalClickShortfall.toFixed(0)} clicks for this window. Start review with "${top.query}" because it has the largest gap.` : dataStatus === "partial" ? "No material CTR underperformers remained in the validated retained rows, but partial evidence prevents an all-clear." : `No high-impression rows at average position below 4 in the retained rows are materially below the heuristic CTR benchmark.`
|
|
18665
18682
|
},
|
|
18666
18683
|
items,
|
|
18667
18684
|
caveats: [
|
|
18668
|
-
`Date window: ${range.startDate} to ${range.endDate} (${days} ${plural$
|
|
18685
|
+
`Date window: ${range.startDate} to ${range.endDate} (${days} ${plural$7(days, "day")}), using final GSC data where available.`,
|
|
18669
18686
|
`Brand queries: ${input.includeBrand ? "included" : "excluded"}. Brand terms used: ${brand.terms.join(", ") || "none"} (${brand.source === "provided" ? "provided" : "host forms, plus the bare domain stem only when Search Console shows the site is its navigational answer"}).`,
|
|
18670
18687
|
`Only rows at average position below 4 with at least ${minImpressions} impressions were checked. Rows at position 4 to 10 belong to quick-wins, which uses the same site CTR curve.`,
|
|
18671
18688
|
minImpressionsCaveat({
|
|
@@ -18673,13 +18690,13 @@ async function ctrUnderperformersReportWith(input, dependencies) {
|
|
|
18673
18690
|
belowMinimumRows: selection.belowMinimumRows
|
|
18674
18691
|
}),
|
|
18675
18692
|
"Expected CTR uses the site CTR curve from all page-one rows (75th percentile of qualified URL CTRs per position) when enough peer data exists, otherwise the fallback position curve.",
|
|
18676
|
-
...selection.fragmentRows ? [`${selection.fragmentRows} source ${plural$
|
|
18677
|
-
...selection.flaggedRows ? [`${selection.flaggedRows} eligible ${plural$
|
|
18693
|
+
...selection.fragmentRows ? [`${selection.fragmentRows} source ${plural$7(selection.fragmentRows, "row")} with a #fragment URL ${selection.fragmentRows === 1 ? "was" : "were"} merged into the base URL.`] : [],
|
|
18694
|
+
...selection.flaggedRows ? [`${selection.flaggedRows} eligible ${plural$7(selection.flaggedRows, "row")} carry heuristic query flags (likely-automated or navigational-other) and rank after unflagged rows.`] : [],
|
|
18678
18695
|
"CTR benchmarks and calculated click shortfalls are directional heuristics, not forecasts or proof that clicks are available. Validate the search intent and live SERP before editing.",
|
|
18679
|
-
...selection.invalidRows ? [`${selection.invalidRows} invalid provider ${plural$
|
|
18680
|
-
...selection.duplicateRows ? [`${selection.duplicateRows} repeated query/page ${plural$
|
|
18696
|
+
...selection.invalidRows ? [`${selection.invalidRows} invalid provider ${plural$7(selection.invalidRows, "row")} ${selection.invalidRows === 1 ? "was" : "were"} excluded, so this report is partial.`] : [],
|
|
18697
|
+
...selection.duplicateRows ? [`${selection.duplicateRows} repeated query/page ${plural$7(selection.duplicateRows, "row")} ${selection.duplicateRows === 1 ? "was" : "were"} aggregated before CTR and average position were calculated.`] : [],
|
|
18681
18698
|
...possiblyTruncated ? [`The retained-row limit of ${MAX_GSC_ROWS$3} was reached, so lower-ranked source rows may be absent.`] : [],
|
|
18682
|
-
...selection.limitedUnderperformers ? [`${selection.limitedUnderperformers} eligible ${plural$
|
|
18699
|
+
...selection.limitedUnderperformers ? [`${selection.limitedUnderperformers} eligible ${plural$7(selection.limitedUnderperformers, "underperformer")} ${selection.limitedUnderperformers === 1 ? "was" : "were"} omitted by the output limit.`] : []
|
|
18683
18700
|
],
|
|
18684
18701
|
recommendations: top ? [
|
|
18685
18702
|
`Review the live SERP and displayed snippet for "${top.query}" first. Test the title or meta description only if the observed framing does not match the query intent.`,
|
|
@@ -19025,7 +19042,7 @@ function analyzeSegmentRows(input) {
|
|
|
19025
19042
|
}
|
|
19026
19043
|
//#endregion
|
|
19027
19044
|
//#region packages/core/src/analyze/segment-impact-input.ts
|
|
19028
|
-
const DAY_MS = 864e5;
|
|
19045
|
+
const DAY_MS$1 = 864e5;
|
|
19029
19046
|
const MAX_WINDOW_DAYS = 240;
|
|
19030
19047
|
function subtractDays(value, days) {
|
|
19031
19048
|
const date = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
@@ -19033,7 +19050,7 @@ function subtractDays(value, days) {
|
|
|
19033
19050
|
return date.toISOString().slice(0, 10);
|
|
19034
19051
|
}
|
|
19035
19052
|
function segmentRangeDays(range) {
|
|
19036
|
-
return Math.floor((Date.parse(`${range.endDate}T00:00:00.000Z`) - Date.parse(`${range.startDate}T00:00:00.000Z`)) / DAY_MS) + 1;
|
|
19053
|
+
return Math.floor((Date.parse(`${range.endDate}T00:00:00.000Z`) - Date.parse(`${range.startDate}T00:00:00.000Z`)) / DAY_MS$1) + 1;
|
|
19037
19054
|
}
|
|
19038
19055
|
function segmentComparisonRange(after, compareDays) {
|
|
19039
19056
|
const endDate = subtractDays(after.startDate, 1);
|
|
@@ -19301,9 +19318,10 @@ function delimiterCount(line, delimiter) {
|
|
|
19301
19318
|
let quoted = false;
|
|
19302
19319
|
for (let index = 0; index < line.length; index += 1) {
|
|
19303
19320
|
const character = line[index];
|
|
19304
|
-
if (character === "\"")
|
|
19305
|
-
|
|
19306
|
-
|
|
19321
|
+
if (character === "\"") {
|
|
19322
|
+
if (quoted && line[index + 1] === "\"") index += 1;
|
|
19323
|
+
else quoted = !quoted;
|
|
19324
|
+
} else if (!quoted && character === delimiter) count += 1;
|
|
19307
19325
|
}
|
|
19308
19326
|
return count;
|
|
19309
19327
|
}
|
|
@@ -19330,9 +19348,10 @@ function decodeText(decoder, chunk, stream, label) {
|
|
|
19330
19348
|
async function detectCsvDelimiter(path) {
|
|
19331
19349
|
const handle = await open$1(path, "r");
|
|
19332
19350
|
try {
|
|
19333
|
-
const buffer = Buffer.alloc(
|
|
19351
|
+
const buffer = Buffer.alloc(65536);
|
|
19334
19352
|
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
19335
|
-
const
|
|
19353
|
+
const encoding = await detectTextEncoding(path);
|
|
19354
|
+
const firstRecord = new TextDecoder$1(encoding).decode(buffer.subarray(0, bytesRead)).split(/\r?\n/u, 1)[0]?.replace(/^\uFEFF/u, "");
|
|
19336
19355
|
if (!firstRecord) return ",";
|
|
19337
19356
|
const candidates = [
|
|
19338
19357
|
",",
|
|
@@ -19371,9 +19390,10 @@ async function* csvRecords(input) {
|
|
|
19371
19390
|
quoted = false;
|
|
19372
19391
|
pendingQuote = false;
|
|
19373
19392
|
}
|
|
19374
|
-
if (quoted)
|
|
19375
|
-
|
|
19376
|
-
|
|
19393
|
+
if (quoted) {
|
|
19394
|
+
if (character === "\"") pendingQuote = true;
|
|
19395
|
+
else field += character;
|
|
19396
|
+
} else if (character === "\"" && field.length === 0) quoted = true;
|
|
19377
19397
|
else if (character === delimiter) {
|
|
19378
19398
|
record.push(field);
|
|
19379
19399
|
field = "";
|
|
@@ -22452,7 +22472,7 @@ const INCIDENTS_URL = "https://status.search.google.com/incidents.json";
|
|
|
22452
22472
|
async function fetchSearchStatusIncidents() {
|
|
22453
22473
|
const response = await fetch$1(INCIDENTS_URL, { signal: AbortSignal.timeout(8e3) });
|
|
22454
22474
|
if (!response.ok) throw new SeoError("OPTIONAL_PROVIDER_UNAVAILABLE", `Google Search Status fetch failed with ${response.status}.`);
|
|
22455
|
-
return readJson(response, "Google Search Status", { maxBytes:
|
|
22475
|
+
return readJson(response, "Google Search Status", { maxBytes: 10485760 });
|
|
22456
22476
|
}
|
|
22457
22477
|
async function listSearchUpdates(input = {}) {
|
|
22458
22478
|
return searchUpdatesFromIncidents(withRankingHistory(await fetchSearchStatusIncidents()), input);
|
|
@@ -22514,7 +22534,7 @@ function anomalyEvidence(anomaly) {
|
|
|
22514
22534
|
if (anomaly.direction === "normal") return `${formatMetric(anomaly.metric)} changed ${changePhrase} (${anomaly.baselineMean.toLocaleString("en-GB")} -> ${anomaly.comparisonMean.toLocaleString("en-GB")} per observed day${anomaly.zScore === null ? "; the baseline had no observed variance" : `, z ${anomaly.zScore}`}).`;
|
|
22515
22535
|
return `${formatMetric(anomaly.metric)} ${direction} ${changePhrase} (${anomaly.baselineMean.toLocaleString("en-GB")} -> ${anomaly.comparisonMean.toLocaleString("en-GB")} per observed day${anomaly.zScore === null ? "; outside the flat observed baseline" : `, z ${anomaly.zScore}`}).`;
|
|
22516
22536
|
}
|
|
22517
|
-
function plural$
|
|
22537
|
+
function plural$6(count, singular, pluralLabel = `${singular}s`) {
|
|
22518
22538
|
return count === 1 ? singular : pluralLabel;
|
|
22519
22539
|
}
|
|
22520
22540
|
function strongestSignal(anomalies) {
|
|
@@ -22570,13 +22590,13 @@ function interpretUpdateCorrelation(input) {
|
|
|
22570
22590
|
summary,
|
|
22571
22591
|
evidence: [
|
|
22572
22592
|
...input.anomalies.map(anomalyEvidence),
|
|
22573
|
-
hasUpdate ? `${input.overlappingUpdates.length} official Google Ranking update ${plural$
|
|
22593
|
+
hasUpdate ? `${input.overlappingUpdates.length} official Google Ranking update ${plural$6(input.overlappingUpdates.length, "window")} overlapped the change date or comparison window: ${updateNames.join(", ")}.` : "No official Google Ranking update windows overlapped the change date or comparison window.",
|
|
22574
22594
|
largeMovement ? "Both clicks and impressions moved sharply in the same direction." : "Clicks and impressions did not both meet the sharp-movement evidence threshold.",
|
|
22575
22595
|
hasConfounders ? `Known overlapping site changes: ${confounders.map((confounder) => confounder.title).join("; ")}.` : "No overlapping site changes were supplied."
|
|
22576
22596
|
],
|
|
22577
22597
|
caveats: [
|
|
22578
22598
|
`GSC window: ${input.days} days; recent comparison window: ${input.recentDays} days.`,
|
|
22579
|
-
`Update overlap padding: ${input.paddingDays} ${plural$
|
|
22599
|
+
`Update overlap padding: ${input.paddingDays} ${plural$6(input.paddingDays, "day")} either side of the step-change date, or of the recent comparison window when no step change was found.`,
|
|
22580
22600
|
input.refresh ? "Data freshness: cache bypassed for GSC data." : "Data freshness: local GSC cache may have been used.",
|
|
22581
22601
|
"Source: official Google Search Status Dashboard Ranking incidents only; third-party volatility and unconfirmed chatter are not included.",
|
|
22582
22602
|
"An update-window overlap cannot establish causation, even when movement is large and no known site change was recorded.",
|
|
@@ -22744,7 +22764,7 @@ function analyzeTrafficAnomalyRows(input) {
|
|
|
22744
22764
|
if (step && changeDate) {
|
|
22745
22765
|
const previousDate = aggregated.rows[step.index - 1]?.keys[0];
|
|
22746
22766
|
const gap = previousDate ? calendarDays(previousDate, changeDate) - 2 : 0;
|
|
22747
|
-
stepCaveats.push(`${formatMetric(metric)} changed level on ${changeDate}, so its baseline is every observed day before that date and its comparison is every observed day from it.`, ...gap > 0 ? [`The ${metric} change date follows ${gap} missing ${plural$
|
|
22767
|
+
stepCaveats.push(`${formatMetric(metric)} changed level on ${changeDate}, so its baseline is every observed day before that date and its comparison is every observed day from it.`, ...gap > 0 ? [`The ${metric} change date follows ${gap} missing ${plural$6(gap, "day")}; the change may have started on one of them.`] : []);
|
|
22748
22768
|
return compareSegments({
|
|
22749
22769
|
site: input.site,
|
|
22750
22770
|
metric,
|
|
@@ -22775,9 +22795,9 @@ function analyzeTrafficAnomalyRows(input) {
|
|
|
22775
22795
|
const comparisonMissingDays = input.recentDays - comparisonRows.length;
|
|
22776
22796
|
const missingDays = expectedDays - aggregated.rows.length;
|
|
22777
22797
|
const caveats = [
|
|
22778
|
-
...missingDays > 0 ? [`${missingDays} requested calendar ${plural$
|
|
22779
|
-
...aggregated.invalidRows > 0 ? [`${aggregated.invalidRows} invalid provider ${plural$
|
|
22780
|
-
...aggregated.duplicateRows > 0 ? [`${aggregated.duplicateRows} duplicate date ${plural$
|
|
22798
|
+
...missingDays > 0 ? [`${missingDays} requested calendar ${plural$6(missingDays, "day")} had no returned date aggregate and ${missingDays === 1 ? "was" : "were"} not filled with zeros.`] : [],
|
|
22799
|
+
...aggregated.invalidRows > 0 ? [`${aggregated.invalidRows} invalid provider ${plural$6(aggregated.invalidRows, "row")} ${aggregated.invalidRows === 1 ? "was" : "were"} excluded.`] : [],
|
|
22800
|
+
...aggregated.duplicateRows > 0 ? [`${aggregated.duplicateRows} duplicate date ${plural$6(aggregated.duplicateRows, "row")} ${aggregated.duplicateRows === 1 ? "was" : "were"} aggregated before analysis.`] : [],
|
|
22781
22801
|
...stepCaveats
|
|
22782
22802
|
];
|
|
22783
22803
|
return {
|
|
@@ -23461,7 +23481,7 @@ var DecisionModelError = class extends Error {
|
|
|
23461
23481
|
const TYPESAFE_URL = "https://api.typesafe.ai/v1/systemone";
|
|
23462
23482
|
const JEV_PRICE_PER_MILLION = .042;
|
|
23463
23483
|
const REQUEST_TIMEOUT_MS = 6e4;
|
|
23464
|
-
const MAX_RESPONSE_BYTES =
|
|
23484
|
+
const MAX_RESPONSE_BYTES = 1048576;
|
|
23465
23485
|
function retryAfter(response) {
|
|
23466
23486
|
const value = Number(response.headers.get("retry-after"));
|
|
23467
23487
|
return Number.isFinite(value) && value > 0 ? Math.min(value, 30) * 1e3 : void 0;
|
|
@@ -23799,7 +23819,7 @@ function keyTerms(page) {
|
|
|
23799
23819
|
const title = page.title.replace(/\s+[|\u2013\u2014-]\s+[^|\u2013\u2014]*$/u, "");
|
|
23800
23820
|
for (const term of terms(page.description)) key.set(term, 1);
|
|
23801
23821
|
for (const term of terms(`${title} ${page.h1} ${lastSegmentWords(page.url)}`)) key.set(term, NAME_WEIGHT);
|
|
23802
|
-
for (const term of terms(siteName(page.url))) key.delete(term);
|
|
23822
|
+
for (const term of terms(siteName$1(page.url))) key.delete(term);
|
|
23803
23823
|
return key;
|
|
23804
23824
|
}
|
|
23805
23825
|
function lastSegmentWords(url) {
|
|
@@ -23809,7 +23829,7 @@ function lastSegmentWords(url) {
|
|
|
23809
23829
|
return "";
|
|
23810
23830
|
}
|
|
23811
23831
|
}
|
|
23812
|
-
function siteName(url) {
|
|
23832
|
+
function siteName$1(url) {
|
|
23813
23833
|
try {
|
|
23814
23834
|
return new URL(url).hostname.replace(/^www\./u, "").split(".")[0] ?? "";
|
|
23815
23835
|
} catch {
|
|
@@ -24687,15 +24707,15 @@ function internalLinksReportRange(input, fallbackDays, now) {
|
|
|
24687
24707
|
days: Math.round((Date.parse(`${endDate}T00:00:00Z`) - Date.parse(`${startDate}T00:00:00Z`)) / DAY$1) + 1
|
|
24688
24708
|
};
|
|
24689
24709
|
}
|
|
24690
|
-
const plural$
|
|
24710
|
+
const plural$5 = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
24691
24711
|
function internalLinksVerdict(report) {
|
|
24692
24712
|
const { counts } = report;
|
|
24693
24713
|
if (report.dataStatus === "blocked") return "robots.txt does not allow this crawler to read the site, so nothing was checked.";
|
|
24694
24714
|
if (report.dataStatus === "empty") return "No HTML page could be crawled, so there is nothing to link.";
|
|
24695
24715
|
if (report.dataStatus === "target-not-crawled") return "The target page could not be crawled as its own page (it failed, left the site, or names another page as canonical). Check it with seo page first.";
|
|
24696
24716
|
if (report.dataStatus === "target-not-linkable") return `The target is ${report.target?.noindex ? "noindex" : `a ${report.target?.type?.replaceAll("_", " ")} page`}, so no links to it are suggested.`;
|
|
24697
|
-
const judged = report.judge.status === "used" || report.judge.status === "partial" ? `${counts.strong} strong and ${counts.possible} possible link${counts.strong + counts.possible === 1 ? "" : "s"} judged by ${report.judge.model}${counts.heuristic ? `, plus ${plural$
|
|
24698
|
-
const scope = report.mode === "url" ? `to ${report.target?.url}` : `across ${plural$
|
|
24717
|
+
const judged = report.judge.status === "used" || report.judge.status === "partial" ? `${counts.strong} strong and ${counts.possible} possible link${counts.strong + counts.possible === 1 ? "" : "s"} judged by ${report.judge.model}${counts.heuristic ? `, plus ${plural$5(counts.heuristic, "heuristic link")} where a request failed` : ""}` : `${plural$5(counts.heuristic, "heuristic link")} (no decision model)`;
|
|
24718
|
+
const scope = report.mode === "url" ? `to ${report.target?.url}` : `across ${plural$5(counts.pagesCrawled, "page")}`;
|
|
24699
24719
|
const capped = report.crawl.capReached ? ` The crawl stopped at its limit of ${report.crawl.pageLimit} pages, so pages beyond it were not checked.` : "";
|
|
24700
24720
|
return `${judged} ${scope}${counts.heldBack ? `; ${counts.heldBack} held back by the evidence floors` : ""}.${capped}`;
|
|
24701
24721
|
}
|
|
@@ -26500,7 +26520,7 @@ async function runLighthouse(input) {
|
|
|
26500
26520
|
], {
|
|
26501
26521
|
env: lighthouseEnvironment(),
|
|
26502
26522
|
timeout: input.timeoutMs ?? 12e4,
|
|
26503
|
-
maxBuffer:
|
|
26523
|
+
maxBuffer: 31457280
|
|
26504
26524
|
});
|
|
26505
26525
|
const jsonStart = stdout.indexOf("{");
|
|
26506
26526
|
if (jsonStart < 0) throw new LighthouseRunError("invalid_result");
|
|
@@ -27136,14 +27156,14 @@ function* retainedQueryClusterRows(input) {
|
|
|
27136
27156
|
};
|
|
27137
27157
|
}
|
|
27138
27158
|
}
|
|
27139
|
-
function plural$
|
|
27159
|
+
function plural$4(count, singular, pluralLabel = `${singular}s`) {
|
|
27140
27160
|
return count === 1 ? singular : pluralLabel;
|
|
27141
27161
|
}
|
|
27142
27162
|
function reportVerdict(input) {
|
|
27143
27163
|
if (!input.clusters.length) return "No query clusters were generated for this scope.";
|
|
27144
27164
|
if (input.highOpportunityClusters > 0) {
|
|
27145
27165
|
const verb = input.highOpportunityClusters === 1 ? "has" : "have";
|
|
27146
|
-
return `${input.highOpportunityClusters} ${plural$
|
|
27166
|
+
return `${input.highOpportunityClusters} ${plural$4(input.highOpportunityClusters, "cluster")} ${verb} enough demand and weak enough performance to review first.`;
|
|
27147
27167
|
}
|
|
27148
27168
|
return "No obvious high-opportunity clusters were found. Use this report for content structure and intent mapping rather than urgent fixes.";
|
|
27149
27169
|
}
|
|
@@ -27284,7 +27304,7 @@ function movementLine(report) {
|
|
|
27284
27304
|
}
|
|
27285
27305
|
function countPhrase(count, singular, pluralLabel = `${singular}s`) {
|
|
27286
27306
|
if (count === 0) return `no ${pluralLabel}`;
|
|
27287
|
-
return `${count} ${plural$
|
|
27307
|
+
return `${count} ${plural$3(count, singular, pluralLabel)}`;
|
|
27288
27308
|
}
|
|
27289
27309
|
function headlineLine(report) {
|
|
27290
27310
|
const parts = [
|
|
@@ -27339,7 +27359,7 @@ function decayDiagnosisLabel(diagnosis) {
|
|
|
27339
27359
|
if (diagnosis === "lost_clicks") return "clicks dropped without one dominant signal";
|
|
27340
27360
|
return diagnosis.replaceAll("_", " ");
|
|
27341
27361
|
}
|
|
27342
|
-
function plural$
|
|
27362
|
+
function plural$3(count, singular, pluralLabel = `${singular}s`) {
|
|
27343
27363
|
return count === 1 ? singular : pluralLabel;
|
|
27344
27364
|
}
|
|
27345
27365
|
function decayClusterLine(report) {
|
|
@@ -27347,7 +27367,7 @@ function decayClusterLine(report) {
|
|
|
27347
27367
|
if (!group) return "No material decay cluster stood out.";
|
|
27348
27368
|
const diagnosis = decayDiagnosisLabel(group.diagnosis);
|
|
27349
27369
|
const samples = group.sampleQueries.slice(0, 2).join("; ");
|
|
27350
|
-
return `Top decay cluster: ${group.count} ${plural$
|
|
27370
|
+
return `Top decay cluster: ${group.count} ${plural$3(group.count, "finding")} in the ${group.template.label} template (${diagnosis}), ${group.totalClickLoss.toFixed(0)} fewer observed clicks across rows retained in both windows. Action: ${group.recommendation}${samples ? ` Example queries: ${samples}.` : ""}`;
|
|
27351
27371
|
}
|
|
27352
27372
|
function cannibalSuppressionLine(report) {
|
|
27353
27373
|
const suppressed = report.cannibalization.selection.suppressedQueries;
|
|
@@ -27365,21 +27385,21 @@ function contentOpportunityBullets(report) {
|
|
|
27365
27385
|
if (unavailableSections.length) bullets.push(`Could not assess ${unavailableSections.join(", ")} opportunities in this run; see report caveats.`);
|
|
27366
27386
|
if (report.summary.decayItems) {
|
|
27367
27387
|
const top = report.decay.items[0];
|
|
27368
|
-
bullets.push(top ? `${report.summary.decayItems} observed retained query/page ${plural$
|
|
27388
|
+
bullets.push(top ? `${report.summary.decayItems} observed retained query/page ${plural$3(report.summary.decayItems, "decline")} need review. Start with "${top.query}" on ${top.url}; it had ${top.clickLoss.toFixed(0)} fewer clicks.` : `${report.summary.decayItems} observed retained query/page ${plural$3(report.summary.decayItems, "decline")} need review.`);
|
|
27369
27389
|
bullets.push(decayClusterLine(report));
|
|
27370
27390
|
}
|
|
27371
27391
|
if (report.summary.cannibalItems) {
|
|
27372
27392
|
const top = report.cannibalization.items[0];
|
|
27373
|
-
bullets.push(top ? `${report.summary.cannibalItems} multi-URL query ${plural$
|
|
27393
|
+
bullets.push(top ? `${report.summary.cannibalItems} multi-URL query ${plural$3(report.summary.cannibalItems, "candidate")} need intent and technical review. Start with "${top.query}" and decide whether ${top.pages.length} ranking URLs answer the same intent.` : `${report.summary.cannibalItems} multi-URL query ${plural$3(report.summary.cannibalItems, "candidate")} need intent and technical review.`);
|
|
27374
27394
|
}
|
|
27375
27395
|
if (report.cannibalization.selection.suppressedQueries) bullets.push(cannibalSuppressionLine(report));
|
|
27376
27396
|
if (report.summary.strikingDistanceItems) {
|
|
27377
27397
|
const top = report.strikingDistance.items[0];
|
|
27378
|
-
bullets.push(top ? `${countLabel(report.summary.strikingDistanceItems, "query/page candidate")} ${plural$
|
|
27398
|
+
bullets.push(top ? `${countLabel(report.summary.strikingDistanceItems, "query/page candidate")} ${plural$3(report.summary.strikingDistanceItems, "has", "have")} an average GSC position above 10 and at most 20. Start by investigating "${top.query}" on ${top.url}; it averages position ${top.position.toFixed(1)} with ${top.impressions.toFixed(0)} impressions.` : `${countLabel(report.summary.strikingDistanceItems, "query/page candidate")} ${plural$3(report.summary.strikingDistanceItems, "has", "have")} an average GSC position above 10 and at most 20.`);
|
|
27379
27399
|
}
|
|
27380
27400
|
if (!skippedReason(report, "quick-win opportunities") && report.summary.quickWinItems) {
|
|
27381
27401
|
const top = report.quickWins.items[0];
|
|
27382
|
-
bullets.push(top ? `${report.summary.quickWinItems} retained query/page ${plural$
|
|
27402
|
+
bullets.push(top ? `${report.summary.quickWinItems} retained query/page ${plural$3(report.summary.quickWinItems, "candidate")} fell below a site-peer or versioned heuristic CTR target. Start with "${top.query}" on ${top.url}; it had ${top.impressions.toFixed(0)} impressions at average position ${top.position.toFixed(1)}.` : `${report.summary.quickWinItems} CTR-target ${plural$3(report.summary.quickWinItems, "candidate")} need review.`);
|
|
27383
27403
|
}
|
|
27384
27404
|
const templateLine = templateOpportunityLine(report);
|
|
27385
27405
|
if (!templateLine.startsWith("No dominant")) bullets.push(templateLine);
|
|
@@ -29062,7 +29082,7 @@ async function clickyDomain(site) {
|
|
|
29062
29082
|
}
|
|
29063
29083
|
//#endregion
|
|
29064
29084
|
//#region packages/core/src/analyze/ai-queries.ts
|
|
29065
|
-
const RULES
|
|
29085
|
+
const RULES = [
|
|
29066
29086
|
["agent_prompt", "^(search the web for|browse the web|find sources)|do not invent|return the [0-9]+ most relevant|include source urls|context: location|do not include location|question: |reply only with|the model you are using"],
|
|
29067
29087
|
["tracker", "\\. ?my location is |\\?\\. ?my( |$)|^evaluate the .+ on "],
|
|
29068
29088
|
["reply", "^(yes|yeah|yep|ok|okay|sure|yes please|go on|continue|keep going|tell me more|why not|how so|really|exactly|show me|and then|thanks|thank you|got it|what else|anything else)[?!.,]*$"],
|
|
@@ -29080,8 +29100,8 @@ const AI_QUERY_KINDS = {
|
|
|
29080
29100
|
chat: "chat phrasing, such as \"can you\" or \"should i\"",
|
|
29081
29101
|
long: "ten or more words with no other sign"
|
|
29082
29102
|
};
|
|
29083
|
-
const MATCHERS = RULES
|
|
29084
|
-
const AI_QUERY_PATTERN = RULES
|
|
29103
|
+
const MATCHERS = RULES.map(([kind, pattern]) => [kind, new RegExp(pattern)]);
|
|
29104
|
+
const AI_QUERY_PATTERN = RULES.map(([, pattern]) => `(?:${pattern})`).join("|");
|
|
29085
29105
|
const NOISE = [
|
|
29086
29106
|
["spaced_letters", /(^|\s)(\S\s){3,}\S(\s|$)/u],
|
|
29087
29107
|
["broken_encoding", /^(яю|ÿþ|þÿ)|&#[0-9]+;?|&[a-z]+;|[âÃÂ][\u0080-\u00bf€™œ“”]|\ufffd/u],
|
|
@@ -29655,15 +29675,15 @@ async function runRows(input, deps) {
|
|
|
29655
29675
|
};
|
|
29656
29676
|
}
|
|
29657
29677
|
const QUESTION_DAYS = 90;
|
|
29658
|
-
const list = (value) => Array.isArray(value) ? value : [];
|
|
29678
|
+
const list$1 = (value) => Array.isArray(value) ? value : [];
|
|
29659
29679
|
const urls = (items) => items.map((item) => item.url).filter((url) => typeof url === "string");
|
|
29660
29680
|
const overview = (result) => {
|
|
29661
|
-
const block = list(result.items).find((item) => item.type === "ai_overview");
|
|
29681
|
+
const block = list$1(result.items).find((item) => item.type === "ai_overview");
|
|
29662
29682
|
if (!block) return null;
|
|
29663
|
-
const parts = list(block.items);
|
|
29683
|
+
const parts = list$1(block.items);
|
|
29664
29684
|
return {
|
|
29665
29685
|
markdown: typeof block.markdown === "string" ? block.markdown : parts.map((part) => String(part.markdown ?? "")).join("\n"),
|
|
29666
|
-
sources: urls([...list(block.references), ...parts.flatMap((part) => list(part.references))]),
|
|
29686
|
+
sources: urls([...list$1(block.references), ...parts.flatMap((part) => list$1(part.references))]),
|
|
29667
29687
|
model: null
|
|
29668
29688
|
};
|
|
29669
29689
|
};
|
|
@@ -29679,7 +29699,7 @@ const ENGINES = [
|
|
|
29679
29699
|
}),
|
|
29680
29700
|
shown: (result) => {
|
|
29681
29701
|
const markdown = typeof result.markdown === "string" ? result.markdown : "";
|
|
29682
|
-
const sources = urls(list(result.sources));
|
|
29702
|
+
const sources = urls(list$1(result.sources));
|
|
29683
29703
|
if (!markdown && !sources.length) return null;
|
|
29684
29704
|
return {
|
|
29685
29705
|
markdown,
|
|
@@ -30263,7 +30283,7 @@ async function analyticsBeside(access, site, range, before, pages) {
|
|
|
30263
30283
|
//#region packages/core/src/commands/audit.ts
|
|
30264
30284
|
const AUDIT_SECTION = "weekly-audit";
|
|
30265
30285
|
const AUDIT_SECTION_TEXT = "What the weekly crawl of the site found (broken and redirected internal links, missing or duplicate titles, descriptions and H1s, noindex, canonicals to another page, sitemap URLs that do not return 200, thin pages), with what is new, back or resolved since the crawl before. Only for sites added to the weekly site audit beta.";
|
|
30266
|
-
const
|
|
30286
|
+
const AUDIT_RULES = {
|
|
30267
30287
|
link_to_error: "Internal links to a page that returns an error",
|
|
30268
30288
|
link_to_redirect: "Internal links to a redirect",
|
|
30269
30289
|
link_failed: "Internal links to a page that could not be fetched",
|
|
@@ -30420,7 +30440,7 @@ function runAuditReport(input, snapshot, call) {
|
|
|
30420
30440
|
text: [
|
|
30421
30441
|
head,
|
|
30422
30442
|
indexText,
|
|
30423
|
-
byRule.length ? ["finding | open | what it is", ...byRule.map(([rule, count]) => `${rule} | ${count} | ${
|
|
30443
|
+
byRule.length ? ["finding | open | what it is", ...byRule.map(([rule, count]) => `${rule} | ${count} | ${AUDIT_RULES[rule] ?? rule}`)].join("\n") : "No findings in this crawl.",
|
|
30424
30444
|
fresh.length ? `New, back or resolved this week, 1 to ${Math.min(SUMMARY_ROWS, fresh.length)} of ${fresh.length}:\n${table(fresh.slice(0, SUMMARY_ROWS), origin, since)}` : previous ? "Nothing new, back or resolved since the crawl before." : null,
|
|
30425
30445
|
changes.compared ? `Between the two crawls: ${changes.new_urls} new URLs, ${changes.gone_urls} gone, ${changes.status_changed} changed status, ${changes.title_changed} changed title.` : null,
|
|
30426
30446
|
`More: ${call("report", {
|
|
@@ -30445,9 +30465,9 @@ function auditPreview(snapshot) {
|
|
|
30445
30465
|
const index = snapshot.index_changes;
|
|
30446
30466
|
return `${index && (index.dropped || index.returned) ? `${index.dropped} URLs dropped out of Google's index this week and ${index.returned} came back. ` : ""}Last crawl ${day$1(snapshot.finished_at)}: ${findings.open} open findings, ${findings.new + findings.reopened} new or back, ${findings.resolved} resolved.`;
|
|
30447
30467
|
}
|
|
30448
|
-
const WEEK_MS =
|
|
30468
|
+
const WEEK_MS = 6048e5;
|
|
30449
30469
|
const day = (ms) => ms === null ? "" : new Date(ms).toISOString().slice(0, 10);
|
|
30450
|
-
const count$
|
|
30470
|
+
const count$2 = (value) => value.toLocaleString("en-US");
|
|
30451
30471
|
function relative$1(value, origin) {
|
|
30452
30472
|
if (!value) return "";
|
|
30453
30473
|
return origin && value.startsWith(origin) ? value.slice(origin.length) || "/" : value;
|
|
@@ -30468,7 +30488,7 @@ function rowsTable(rows, checks) {
|
|
|
30468
30488
|
changed(row) ? row.prev_coverage ?? "" : "",
|
|
30469
30489
|
changed(row) ? day(row.changed_at) : "",
|
|
30470
30490
|
day(row.checked_at),
|
|
30471
|
-
`${count$
|
|
30491
|
+
`${count$2(row.clicks)} (${count$2(row.previous_clicks)})${row.lost ? " lost" : ""}`,
|
|
30472
30492
|
row.in_sitemap ? "yes" : "no",
|
|
30473
30493
|
row.google_canonical && row.google_canonical !== row.url ? relative$1(row.google_canonical, checks.origin) : "",
|
|
30474
30494
|
row.error ?? ""
|
|
@@ -30476,12 +30496,12 @@ function rowsTable(rows, checks) {
|
|
|
30476
30496
|
}
|
|
30477
30497
|
function head(site, checks) {
|
|
30478
30498
|
const { queued } = checks;
|
|
30479
|
-
const quota = checks.properties.map((item) => `${item.property} (${count$
|
|
30499
|
+
const quota = checks.properties.map((item) => `${item.property} (${count$2(item.urls)} URLs: ${count$2(item.top)} top pages checked daily, the rest every ${item.cycle_days === 1 ? "day" : `${item.cycle_days} days`}; ${count$2(item.used_today)} of ${count$2(checks.per_day)} checks used today)`).join(", ");
|
|
30480
30500
|
return [
|
|
30481
30501
|
`Google indexing of ${site}, from the URL Inspection results this server keeps for it (as of ${new Date(checks.as_of).toISOString().slice(0, 16).replace("T", " ")} UTC).`,
|
|
30482
|
-
checks.dropped_this_week || checks.returned_this_week ? `In the last 7 days ${count$
|
|
30483
|
-
`Covered: ${count$
|
|
30484
|
-
queued.total ? `Queued: ${count$
|
|
30502
|
+
checks.dropped_this_week || checks.returned_this_week ? `In the last 7 days ${count$2(checks.dropped_this_week)} ${checks.dropped_this_week === 1 ? "URL" : "URLs"} dropped out of Google's index and ${count$2(checks.returned_this_week)} came back; they are listed first. A top page's drop counts only after a second check an hour later.` : null,
|
|
30503
|
+
`Covered: ${count$2(checks.covered)} of ${count$2(checks.known)} known URLs have a current result. Known URLs are the sitemap's, the crawl's and the Search Console pages with clicks or impressions. Each property spends its full ${count$2(checks.per_day)} checks a day: its top pages (lost clicks, then clicks, then impressions, up to ${count$2(checks.top_per_day)}) daily, the rest as often as the quota allows, and a page the weekly crawl saw change at once.`,
|
|
30504
|
+
queued.total ? `Queued: ${count$2(queued.total)} (${count$2(queued.lost)} pages that lost their clicks, ${count$2(queued.search)} with clicks or impressions, ${count$2(queued.sitemap)} sitemap URLs never checked, ${count$2(queued.stale)} older or changed), checked in that order. Time left: ${etaText(checks.eta_hours)}, at up to ${count$2(checks.per_day)} checks a day per property, spread over the day.` : "Queued: nothing; every known URL has a current result.",
|
|
30485
30505
|
quota ? `Checked through ${quota}.` : null,
|
|
30486
30506
|
checks.stopped ? `Checks stopped early: ${checks.stopped}` : null,
|
|
30487
30507
|
"A URL without a result has none here, so this is not an all-clear for it."
|
|
@@ -30490,9 +30510,9 @@ function head(site, checks) {
|
|
|
30490
30510
|
function suggestionsText(checks) {
|
|
30491
30511
|
if (!checks.suggestions.length) return null;
|
|
30492
30512
|
return [
|
|
30493
|
-
`The sitemap has more URLs than these properties can check every 7 days. Each Search Console property gets its own ${count$
|
|
30513
|
+
`The sitemap has more URLs than these properties can check every 7 days. Each Search Console property gets its own ${count$2(checks.per_day)} checks a day, so adding these folders as URL-prefix properties (on the same Google account) would check more:`,
|
|
30494
30514
|
"property to add | sitemap URLs in it | more URLs checked a week",
|
|
30495
|
-
...checks.suggestions.map((item) => `${item.property} | ${count$
|
|
30515
|
+
...checks.suggestions.map((item) => `${item.property} | ${count$2(item.urls)} | ${count$2(item.gain)}`),
|
|
30496
30516
|
"The next day the checks find a new property in Search Console and use its quota for the URLs in that folder."
|
|
30497
30517
|
].join("\n");
|
|
30498
30518
|
}
|
|
@@ -30507,7 +30527,7 @@ function runAuditChecks(input, checks, call) {
|
|
|
30507
30527
|
data: checks
|
|
30508
30528
|
};
|
|
30509
30529
|
if (!checks.started) return {
|
|
30510
|
-
text: `${site} is in the weekly site audit, and its Google checks are starting now: pages that lost their clicks first, then pages by clicks and impressions, then sitemap URLs. ${checks.known ? `${count$
|
|
30530
|
+
text: `${site} is in the weekly site audit, and its Google checks are starting now: pages that lost their clicks first, then pages by clicks and impressions, then sitemap URLs. ${checks.known ? `${count$2(checks.known)} URLs are known so far. ` : ""}Run ${call("index", { site })} again in a few minutes for the coverage and the time left.`,
|
|
30511
30531
|
data: checks
|
|
30512
30532
|
};
|
|
30513
30533
|
const more = (offset) => input.paged ? `More: ${call("index", {
|
|
@@ -30531,7 +30551,7 @@ function runAuditChecks(input, checks, call) {
|
|
|
30531
30551
|
const reasons = checks.reasons.length ? [
|
|
30532
30552
|
"Google's reason at each URL's latest check:",
|
|
30533
30553
|
"reason | verdict | urls | changed to it this week",
|
|
30534
|
-
...checks.reasons.map((item) => `${item.coverage ?? ""} | ${item.verdict ?? ""} | ${count$
|
|
30554
|
+
...checks.reasons.map((item) => `${item.coverage ?? ""} | ${item.verdict ?? ""} | ${count$2(item.urls)} | ${count$2(item.changed_this_week)}`)
|
|
30535
30555
|
].join("\n") : "No URL has a result yet.";
|
|
30536
30556
|
const shown = checks.rows.slice(0, 20);
|
|
30537
30557
|
return {
|
|
@@ -30941,13 +30961,13 @@ function clickyRange(input) {
|
|
|
30941
30961
|
endDate: input.endDate
|
|
30942
30962
|
};
|
|
30943
30963
|
}
|
|
30944
|
-
const count = (item) => {
|
|
30964
|
+
const count$1 = (item) => {
|
|
30945
30965
|
const value = Number(item.value);
|
|
30946
30966
|
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
30947
30967
|
};
|
|
30948
30968
|
const totals = (items) => items.map((item) => ({
|
|
30949
30969
|
name: item.title ?? "",
|
|
30950
|
-
visits: count(item)
|
|
30970
|
+
visits: count$1(item)
|
|
30951
30971
|
})).filter((item) => Boolean(item.name) && item.visits !== null);
|
|
30952
30972
|
const line = (items) => {
|
|
30953
30973
|
const top = items.slice(0, 10).map((item) => `${item.name} ${item.visits}`);
|
|
@@ -30978,7 +30998,7 @@ async function runClickyRows(input, keys, options) {
|
|
|
30978
30998
|
const entrances = stats["pages-entrance"] ?? [];
|
|
30979
30999
|
const visitsByPage = /* @__PURE__ */ new Map();
|
|
30980
31000
|
for (const item of entrances) {
|
|
30981
|
-
const visits = count(item);
|
|
31001
|
+
const visits = count$1(item);
|
|
30982
31002
|
if (visits === null || !item.url) continue;
|
|
30983
31003
|
let page;
|
|
30984
31004
|
try {
|
|
@@ -31029,7 +31049,7 @@ async function runClickyRows(input, keys, options) {
|
|
|
31029
31049
|
const DR_ATTRIBUTION = "domain_rating: Domain Rating by Ahrefs (https://ahrefs.com/).";
|
|
31030
31050
|
const ENDPOINT = "https://api.ahrefs.com/v3/public/domain-rating-free";
|
|
31031
31051
|
const MAX_DOMAINS = 25;
|
|
31032
|
-
const TTL_SECONDS =
|
|
31052
|
+
const TTL_SECONDS = 86400;
|
|
31033
31053
|
const DOMAIN_FIELDS = [
|
|
31034
31054
|
"domain",
|
|
31035
31055
|
"competitor_domain",
|
|
@@ -31136,14 +31156,14 @@ const samePath = (a, b) => {
|
|
|
31136
31156
|
return from.pathname === to.pathname && from.search === to.search;
|
|
31137
31157
|
};
|
|
31138
31158
|
const authPath = (url) => AUTH_SEGMENTS.has(new URL(url).pathname.split("/")[1]?.toLowerCase() ?? "");
|
|
31139
|
-
const plural$
|
|
31159
|
+
const plural$2 = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
31140
31160
|
function labelOf(e) {
|
|
31141
31161
|
const { url, status, live } = e;
|
|
31142
31162
|
const { first, hops, last } = live;
|
|
31143
31163
|
const indexed = status.verdict === "PASS";
|
|
31144
31164
|
const coverage = status.coverage ?? "";
|
|
31145
31165
|
const referring = status.referring_urls.filter((item) => item !== url && sameSite(item, url) && !samePath(item, url));
|
|
31146
|
-
const pointers = [e.inSitemap ? "the sitemap lists it" : null, referring.length ? `Google lists ${plural$
|
|
31166
|
+
const pointers = [e.inSitemap ? "the sitemap lists it" : null, referring.length ? `Google lists ${plural$2(referring.length, "referring page")} on this site as of its last crawl (${referring[0]}${referring.length > 1 ? ", and more" : ""})` : null].filter(Boolean);
|
|
31147
31167
|
if (first.error) return indexed ? {
|
|
31148
31168
|
label: "indexed",
|
|
31149
31169
|
why: `Google indexes it. The live check failed (${first.error}).`
|
|
@@ -31425,20 +31445,20 @@ function indexingText(site, run, page, origin, askMore, now) {
|
|
|
31425
31445
|
const sources = [
|
|
31426
31446
|
`lost-click pages ${n(known.lostClicks)}`,
|
|
31427
31447
|
`Search Console pages with impressions in the last 28 days ${n(known.searchConsole)}${known.searchConsoleCapped ? ", capped" : ""}`,
|
|
31428
|
-
`sitemap ${n(known.sitemap.urls.length)} from ${plural$
|
|
31448
|
+
`sitemap ${n(known.sitemap.urls.length)} from ${plural$2(known.sitemap.files, "file")}${known.sitemap.partial ? ", partial" : ""}`
|
|
31429
31449
|
];
|
|
31430
31450
|
const lowerBound = known.sitemap.partial || known.searchConsoleCapped ? " at least" : "";
|
|
31431
31451
|
const asked = run.inspected - run.fromSaved;
|
|
31432
31452
|
const most = Math.min(total, MAX_INSPECT_LIMIT, run.quota ? run.inspected + run.quota.limit - run.quota.used - asked : MAX_INSPECT_LIMIT);
|
|
31433
31453
|
const days = run.oldestSaved ? Math.floor((now.getTime() - Date.parse(run.oldestSaved)) / 864e5) : 0;
|
|
31434
|
-
const savedNote = run.fromSaved ? ` (${n(asked)} asked of Google now, ${n(run.fromSaved)} from saved results; the oldest was inspected ${run.oldestSaved?.slice(0, 16).replace("T", " ")} UTC, ${days === 0 ? "under a day" : plural$
|
|
31454
|
+
const savedNote = run.fromSaved ? ` (${n(asked)} asked of Google now, ${n(run.fromSaved)} from saved results; the oldest was inspected ${run.oldestSaved?.slice(0, 16).replace("T", " ")} UTC, ${days === 0 ? "under a day" : plural$2(days, "day")} ago. Saved results are reused for 7 days)` : "";
|
|
31435
31455
|
const stop = run.stoppedBy === "quota" ? "The URL Inspection quota stopped it." : run.stoppedBy === "limit" ? `It stopped at ${n(run.limit)}, the number of inspections for this run${askMore && most > run.limit ? `; to inspect more, up to the quota left today: ${askMore(most)}` : ""}.` : "Every known URL has a result.";
|
|
31436
31456
|
const lines = [
|
|
31437
31457
|
`Google's indexing reasons for ${site}, from URL Inspection. Search Console's Page indexing report has no API, so known URLs are inspected one at a time, in a fixed order (lost-click pages, Search Console pages by clicks then impressions, sitemap URLs, then the rest), and checked live from here.`,
|
|
31438
31458
|
`Known URLs:${lowerBound} ${n(total)} (${sources.join("; ")}). Inspected ${n(run.inspected)} of ${n(total)}${savedNote}. ${stop}${notInspected > 0 ? ` The ${n(notInspected)} URLs not inspected have no result here, so this is not an all-clear for them.` : ""}${known.sitemap.notes.length ? ` Sitemap: ${known.sitemap.notes.join("; ")}.` : ""}`,
|
|
31439
31459
|
run.quota ? `URL Inspection quota: this machine had used ${n(run.quota.used)} of ${n(run.quota.limit)} today for this property before the run. Google also counts inspections from other tools.` : "URL Inspection quota: not counted on this server; Google stops inspections at 2,000 a day per property."
|
|
31440
31460
|
];
|
|
31441
|
-
if (run.failed.length) lines.push(`URL Inspection failed for ${plural$
|
|
31461
|
+
if (run.failed.length) lines.push(`URL Inspection failed for ${plural$2(run.failed.length, "URL")}: ${run.failed.slice(0, 5).map((item) => `${item.url} (${item.error})`).join("; ")}${run.failed.length > 5 ? "; and more" : ""}.`);
|
|
31442
31462
|
const groups = byReason(run.rows);
|
|
31443
31463
|
if (groups.length) lines.push(`By Google's reason, with labels from the rules below (not Google's):\n${table$1(groups, [
|
|
31444
31464
|
"reason",
|
|
@@ -32674,6 +32694,164 @@ async function runLostPages(input, deps) {
|
|
|
32674
32694
|
};
|
|
32675
32695
|
}
|
|
32676
32696
|
//#endregion
|
|
32697
|
+
//#region packages/core/src/commands/week.ts
|
|
32698
|
+
const DAY_MS = 864e5;
|
|
32699
|
+
function weekLabel(start, end) {
|
|
32700
|
+
const parts = (date) => {
|
|
32701
|
+
const value = /* @__PURE__ */ new Date(`${date}T00:00:00Z`);
|
|
32702
|
+
return {
|
|
32703
|
+
day: value.getUTCDate(),
|
|
32704
|
+
month: value.toLocaleString("en-GB", {
|
|
32705
|
+
month: "short",
|
|
32706
|
+
timeZone: "UTC"
|
|
32707
|
+
})
|
|
32708
|
+
};
|
|
32709
|
+
};
|
|
32710
|
+
const from = parts(start);
|
|
32711
|
+
const to = parts(end);
|
|
32712
|
+
return from.month === to.month ? `${from.day} to ${to.day} ${to.month}` : `${from.day} ${from.month} to ${to.day} ${to.month}`;
|
|
32713
|
+
}
|
|
32714
|
+
const weekTitle = (data) => `Your SEO Skill week, ${weekLabel(data.start, data.end)}`;
|
|
32715
|
+
const siteName = (property) => property.replace(/^sc-domain:|^https?:\/\//, "").replace(/\/$/, "");
|
|
32716
|
+
const count = (n) => n.toLocaleString("en-US");
|
|
32717
|
+
const plural$1 = (n, one, many = `${one}s`) => `${count(n)} ${n === 1 ? one : many}`;
|
|
32718
|
+
const list = (items) => items.length <= 1 ? items[0] ?? "" : `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
32719
|
+
function path(url) {
|
|
32720
|
+
try {
|
|
32721
|
+
const parsed = new URL(url);
|
|
32722
|
+
return `${parsed.pathname}${parsed.search}`;
|
|
32723
|
+
} catch {
|
|
32724
|
+
return url;
|
|
32725
|
+
}
|
|
32726
|
+
}
|
|
32727
|
+
function clicksChange(site) {
|
|
32728
|
+
const clicks = site.clicks;
|
|
32729
|
+
if (!clicks) return null;
|
|
32730
|
+
const change = clicks.clicks - clicks.previous;
|
|
32731
|
+
if (Math.abs(change) < 50) return null;
|
|
32732
|
+
if (change < 0 && clicks.previous >= 100 && -change >= .3 * clicks.previous) return "fell";
|
|
32733
|
+
if (change > 0 && clicks.previous >= 50 && change >= .3 * clicks.previous) return "rose";
|
|
32734
|
+
return null;
|
|
32735
|
+
}
|
|
32736
|
+
const percent = (site) => {
|
|
32737
|
+
const clicks = site.clicks;
|
|
32738
|
+
return Math.round(Math.abs(clicks.clicks - clicks.previous) / clicks.previous * 100);
|
|
32739
|
+
};
|
|
32740
|
+
function issues(site, startMs, endMs) {
|
|
32741
|
+
const name = siteName(site.property);
|
|
32742
|
+
const found = [];
|
|
32743
|
+
const index = site.index;
|
|
32744
|
+
if (index?.dropped) {
|
|
32745
|
+
const paths = index.dropped_pages.map((page) => path(page.url));
|
|
32746
|
+
const more = index.dropped - paths.length;
|
|
32747
|
+
const shown = more > 0 ? `${paths.join(", ")} and ${plural$1(more, "more")}` : list(paths);
|
|
32748
|
+
const reasons = new Set(index.dropped_pages.map((page) => page.coverage));
|
|
32749
|
+
const reason = reasons.size === 1 ? [...reasons][0] : null;
|
|
32750
|
+
found.push({
|
|
32751
|
+
rank: 0,
|
|
32752
|
+
heading: `${plural$1(index.dropped, "page")} dropped`,
|
|
32753
|
+
line: `${shown} dropped out of Google’s index.${reason ? ` Google says: ${reason}.` : ""}`,
|
|
32754
|
+
next: `check ${index.dropped === 1 ? "it" : "they"} still return${index.dropped === 1 ? "s" : ""} 200 and ${index.dropped === 1 ? "is" : "are"} not noindexed.`,
|
|
32755
|
+
prompt: `Why did ${paths[0] ?? "a page"} on ${name} drop out of Google’s index this week, and how do I fix it?`
|
|
32756
|
+
});
|
|
32757
|
+
}
|
|
32758
|
+
const crawl = site.crawl;
|
|
32759
|
+
const crawled = crawl !== null && crawl.finished_at >= startMs && crawl.finished_at < endMs;
|
|
32760
|
+
if (crawled && crawl.status === "failed") found.push({
|
|
32761
|
+
rank: 1,
|
|
32762
|
+
heading: "crawl stopped early",
|
|
32763
|
+
line: `The weekly crawl stopped after ${plural$1(crawl.pages, "page")}${crawl.error ? `: ${crawl.error}` : "."}`,
|
|
32764
|
+
next: "check the site is up. The next weekly crawl tries again.",
|
|
32765
|
+
prompt: `Why did the weekly crawl of ${name} stop early, and what should I check?`
|
|
32766
|
+
});
|
|
32767
|
+
else if (!crawled && site.added_at < startMs) found.push({
|
|
32768
|
+
rank: 1,
|
|
32769
|
+
heading: "no crawl this week",
|
|
32770
|
+
line: `No weekly crawl finished this week${site.start_error ? `: ${site.start_error}` : "."}`,
|
|
32771
|
+
next: "check the site is up. The crawl tries again each hour.",
|
|
32772
|
+
prompt: `Why has the weekly crawl of ${name} not finished, and what should I check?`
|
|
32773
|
+
});
|
|
32774
|
+
if (clicksChange(site) === "fell") {
|
|
32775
|
+
const clicks = site.clicks;
|
|
32776
|
+
found.push({
|
|
32777
|
+
rank: 2,
|
|
32778
|
+
heading: `clicks down ${percent(site)}%`,
|
|
32779
|
+
line: `Search clicks fell ${percent(site)}%, from ${count(clicks.previous)} to ${count(clicks.clicks)} (${weekLabel(clicks.start, clicks.end)}, against the 7 days before).`,
|
|
32780
|
+
next: "ask your agent which pages and queries lost them.",
|
|
32781
|
+
prompt: `Which pages and queries lost search clicks on ${name} in the last week, and why?`
|
|
32782
|
+
});
|
|
32783
|
+
}
|
|
32784
|
+
if (crawled && crawl.new_issues > 0) {
|
|
32785
|
+
const rule = crawl.top_rule ? AUDIT_RULES[crawl.top_rule] : void 0;
|
|
32786
|
+
found.push({
|
|
32787
|
+
rank: 3,
|
|
32788
|
+
heading: plural$1(crawl.new_issues, "new issue"),
|
|
32789
|
+
line: `The weekly crawl found ${plural$1(crawl.new_issues, "new issue")}${rule ? `, most of them ${rule.charAt(0).toLowerCase()}${rule.slice(1)}` : ""}.`,
|
|
32790
|
+
next: "ask your agent for the weekly audit of this site.",
|
|
32791
|
+
prompt: `What did the weekly crawl of ${name} find this week, and what should I fix first?`
|
|
32792
|
+
});
|
|
32793
|
+
}
|
|
32794
|
+
return found;
|
|
32795
|
+
}
|
|
32796
|
+
function weekMarkdown(data) {
|
|
32797
|
+
const startMs = Date.parse(`${data.start}T00:00:00Z`);
|
|
32798
|
+
const endMs = Date.parse(`${data.end}T00:00:00Z`) + DAY_MS;
|
|
32799
|
+
const title = `# ${weekTitle(data)}`;
|
|
32800
|
+
if (!data.sites.length) return `${title}\n\nNo site is in your weekly audit yet. Add one on the Sites tab: ${data.account}#sites`;
|
|
32801
|
+
const byName = (a, b) => siteName(a.property).localeCompare(siteName(b.property)) || a.property.localeCompare(b.property);
|
|
32802
|
+
const sites = [...data.sites].sort(byName);
|
|
32803
|
+
const attention = [];
|
|
32804
|
+
const quiet = [];
|
|
32805
|
+
const waiting = [];
|
|
32806
|
+
for (const site of sites) {
|
|
32807
|
+
const found = issues(site, startMs, endMs);
|
|
32808
|
+
if (found.length) attention.push({
|
|
32809
|
+
site,
|
|
32810
|
+
found
|
|
32811
|
+
});
|
|
32812
|
+
else if (!site.crawl || !site.index) waiting.push(site);
|
|
32813
|
+
else quiet.push(site);
|
|
32814
|
+
}
|
|
32815
|
+
attention.sort((a, b) => a.found[0].rank - b.found[0].rank || byName(a.site, b.site));
|
|
32816
|
+
const total = sites.length;
|
|
32817
|
+
const out = [
|
|
32818
|
+
title,
|
|
32819
|
+
"",
|
|
32820
|
+
attention.length ? total === 1 ? "Your site needs a look this week." : `${attention.length} of your ${total} sites ${attention.length === 1 ? "needs" : "need"} a look this week.` : quiet.length ? total === 1 ? "Your site had a quiet week." : quiet.length === total ? `All ${total} of your sites had a quiet week.` : `${quiet.length} of your ${total} sites had a quiet week.` : "Your sites are still on their first checks."
|
|
32821
|
+
];
|
|
32822
|
+
if (attention.length) {
|
|
32823
|
+
out.push("", "## Needs attention");
|
|
32824
|
+
for (const { site, found } of attention) {
|
|
32825
|
+
const [first, ...rest] = found;
|
|
32826
|
+
const also = rest.length ? ` Also: ${list(rest.map((item) => item.heading))}.` : "";
|
|
32827
|
+
const rose = clicksChange(site) === "rose" ? ` Search clicks rose ${percent(site)}%.` : "";
|
|
32828
|
+
out.push("", `### ${siteName(site.property)}: ${first.heading}`, "", `- ${first.line}${also}${rose}`, `- Next step: ${first.next}`, ...site.link ? [`- [View site](${site.link})`] : []);
|
|
32829
|
+
}
|
|
32830
|
+
}
|
|
32831
|
+
if (quiet.length) {
|
|
32832
|
+
const risen = quiet.filter((site) => clicksChange(site) === "rose");
|
|
32833
|
+
out.push("", "## All quiet", "", `${list(quiet.map((site) => siteName(site.property)))}: no drops, no new issues.${risen.map((site) => ` Search clicks rose ${percent(site)}% on ${siteName(site.property)}.`).join("")}`);
|
|
32834
|
+
}
|
|
32835
|
+
if (waiting.length) out.push("", "## Not checked yet", "", `${list(waiting.map((site) => siteName(site.property)))}: the first crawl or Google checks are still running.`);
|
|
32836
|
+
const first = attention[0];
|
|
32837
|
+
const prompt = first ? first.found[0].prompt : `What are the best quick wins on ${siteName((quiet[0] ?? sites[0]).property)} this month?`;
|
|
32838
|
+
out.push("", "## Ask your agent", "", "Paste this into your agent:", "", "```", prompt, "```");
|
|
32839
|
+
return out.join("\n");
|
|
32840
|
+
}
|
|
32841
|
+
async function runAllSites(deps) {
|
|
32842
|
+
const data = deps.week ? await deps.week() : null;
|
|
32843
|
+
if (!data) throw new CommandError("INVALID_INPUT", `No site given. Name one: ${deps.call("report", { site: "<site>" })}. The summary of every site is for accounts in the weekly site audit beta.`);
|
|
32844
|
+
const startMs = Date.parse(`${data.start}T00:00:00Z`);
|
|
32845
|
+
const endMs = Date.parse(`${data.end}T00:00:00Z`) + DAY_MS;
|
|
32846
|
+
const urgency = (site) => issues(site, startMs, endMs)[0]?.rank ?? Number.POSITIVE_INFINITY;
|
|
32847
|
+
const first = [...data.sites].sort((a, b) => urgency(a) - urgency(b) || siteName(a.property).localeCompare(siteName(b.property)))[0];
|
|
32848
|
+
const example = first ? siteName(first.property) : "<site>";
|
|
32849
|
+
return {
|
|
32850
|
+
text: `${weekMarkdown(data)}\n\nFull report on one site: ${deps.call("report", { site: example })}`,
|
|
32851
|
+
data
|
|
32852
|
+
};
|
|
32853
|
+
}
|
|
32854
|
+
//#endregion
|
|
32677
32855
|
//#region packages/core/src/commands/tools.ts
|
|
32678
32856
|
const site = z.string().min(1).describe("Search Console property or site, such as sc-domain:example.com, https://example.com/ or example.com.");
|
|
32679
32857
|
const dates = {
|
|
@@ -32717,17 +32895,25 @@ const SHARED_TOOLS = [
|
|
|
32717
32895
|
name: "seo_report",
|
|
32718
32896
|
command: "report",
|
|
32719
32897
|
title: "Search Console report",
|
|
32720
|
-
description: `
|
|
32898
|
+
description: `Start here. Without site: a short summary of every site in the person's weekly site audit (beta) for last week, with what needs a look and the next step for each, ending with the site to ask about next; an account outside that beta must send site. With site: the main report for that Search Console property, the overview plus each section's row count and top rows. With site and section: that section's rows; page sections also check their top 20 URLs live. Sections:\n${Object.entries(SECTIONS).map(([id, text]) => `- ${id}: ${text}`).join("\n")}`,
|
|
32721
32899
|
input: z.object({
|
|
32722
|
-
site,
|
|
32723
|
-
section: z.enum(SECTION_IDS).optional().describe("Optional. Leave it out first; then ask for one section."),
|
|
32900
|
+
site: site.optional().describe("Leave it out for the summary of every audited site (weekly site audit beta). Otherwise a Search Console property or site, such as sc-domain:example.com, https://example.com/ or example.com."),
|
|
32901
|
+
section: z.enum(SECTION_IDS).optional().describe("Optional, needs site. Leave it out first; then ask for one section."),
|
|
32724
32902
|
...dates,
|
|
32725
32903
|
...detail
|
|
32726
32904
|
}),
|
|
32727
32905
|
run: async (surface, input) => {
|
|
32728
|
-
|
|
32906
|
+
if (!input.site) {
|
|
32907
|
+
if (input.section) throw new CommandError("INVALID_INPUT", `section needs a site: ${surface.deps.call("report", {
|
|
32908
|
+
site: "<site>",
|
|
32909
|
+
section: input.section
|
|
32910
|
+
})}.`);
|
|
32911
|
+
return runAllSites(surface.deps);
|
|
32912
|
+
}
|
|
32913
|
+
const target = input.site;
|
|
32914
|
+
const resolved = await surface.resolveSite(target).catch((error) => error instanceof Error ? error : new Error(String(error)));
|
|
32729
32915
|
if (resolved instanceof Error && !input.section && surface.deps.bing) return runBingReport({
|
|
32730
|
-
site:
|
|
32916
|
+
site: target,
|
|
32731
32917
|
startDate: input.start_date,
|
|
32732
32918
|
endDate: input.end_date
|
|
32733
32919
|
}, surface.deps).catch(() => {
|
|
@@ -32735,7 +32921,7 @@ const SHARED_TOOLS = [
|
|
|
32735
32921
|
});
|
|
32736
32922
|
if (resolved instanceof Error && input.section !== "bing") throw resolved;
|
|
32737
32923
|
return runReport({
|
|
32738
|
-
site: resolved instanceof Error ?
|
|
32924
|
+
site: resolved instanceof Error ? target : resolved,
|
|
32739
32925
|
section: input.section,
|
|
32740
32926
|
startDate: input.start_date,
|
|
32741
32927
|
endDate: input.end_date,
|
|
@@ -32939,7 +33125,7 @@ SHARED_TOOLS.map((shared) => shared.name === "seo_index" ? indexFromStore(shared
|
|
|
32939
33125
|
} : shared);
|
|
32940
33126
|
//#endregion
|
|
32941
33127
|
//#region packages/core/src/gsc/client/inspection-cache.ts
|
|
32942
|
-
const INSPECTION_FRESH_MS =
|
|
33128
|
+
const INSPECTION_FRESH_MS = 6048e5;
|
|
32943
33129
|
function readSavedInspections(site, urls, now = Date.now()) {
|
|
32944
33130
|
const found = /* @__PURE__ */ new Map();
|
|
32945
33131
|
try {
|
|
@@ -33184,7 +33370,8 @@ function nodeCommandDeps(options) {
|
|
|
33184
33370
|
analytics: {
|
|
33185
33371
|
accounts: localAccounts,
|
|
33186
33372
|
signIn: LOCAL_SIGN_IN.signIn
|
|
33187
|
-
}
|
|
33373
|
+
},
|
|
33374
|
+
...cloudLinked() ? { week: cloudWeek } : {}
|
|
33188
33375
|
};
|
|
33189
33376
|
}
|
|
33190
33377
|
const LOCAL_BING_CONNECT = "Connect Bing on your seoskill.dev account and run `seo login`, or run `seo providers bing connect` with an API key from Bing Webmaster Tools (Settings, API access).";
|
|
@@ -33998,10 +34185,10 @@ function reconcileExportPagesWithCrawl(input) {
|
|
|
33998
34185
|
}
|
|
33999
34186
|
//#endregion
|
|
34000
34187
|
//#region packages/core/src/storage/log-retention.ts
|
|
34001
|
-
const MEBIBYTE =
|
|
34188
|
+
const MEBIBYTE = 1048576;
|
|
34002
34189
|
const LOG_MAX_FILE_BYTES = 8 * MEBIBYTE;
|
|
34003
34190
|
const LOG_MAX_TOTAL_BYTES = 64 * MEBIBYTE;
|
|
34004
|
-
const LOG_MAX_AGE_MS =
|
|
34191
|
+
const LOG_MAX_AGE_MS = 12096e5;
|
|
34005
34192
|
function logFiles(directory) {
|
|
34006
34193
|
if (!existsSync(directory)) return [];
|
|
34007
34194
|
return readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".log")).map((entry) => {
|
|
@@ -34081,10 +34268,10 @@ function makeRunId() {
|
|
|
34081
34268
|
}
|
|
34082
34269
|
//#endregion
|
|
34083
34270
|
//#region packages/core/src/provider-extensions/actions.ts
|
|
34084
|
-
const MAX_ACTION_PARAM_BYTES =
|
|
34271
|
+
const MAX_ACTION_PARAM_BYTES = 65536;
|
|
34085
34272
|
const MAX_ACTION_PARAM_DEPTH = 16;
|
|
34086
34273
|
const MAX_ACTION_PARAM_VALUES = 5e3;
|
|
34087
|
-
const MAX_ACTION_RESULT_BYTES =
|
|
34274
|
+
const MAX_ACTION_RESULT_BYTES = 10485760;
|
|
34088
34275
|
const MAX_ACTION_RESULT_DEPTH = 20;
|
|
34089
34276
|
const MAX_ACTION_RESULT_VALUES = 1e5;
|
|
34090
34277
|
function normalizedParams(params) {
|
|
@@ -34299,8 +34486,8 @@ async function describeInstalledProviderExtension(id) {
|
|
|
34299
34486
|
//#endregion
|
|
34300
34487
|
//#region packages/core/src/provider-extensions/installer.ts
|
|
34301
34488
|
const execFileAsync = promisify(execFile);
|
|
34302
|
-
const MAX_PROVIDER_UNPACKED_BYTES =
|
|
34303
|
-
const MAX_NPM_OUTPUT_BYTES =
|
|
34489
|
+
const MAX_PROVIDER_UNPACKED_BYTES = 5242880;
|
|
34490
|
+
const MAX_NPM_OUTPUT_BYTES = 1048576;
|
|
34304
34491
|
const PACKAGE_REFERENCE_EXAMPLE = "@scope/provider or @scope/provider@1.2.3";
|
|
34305
34492
|
const npmPackageMetadataSchema = z.object({
|
|
34306
34493
|
name: providerPackageNameSchema,
|
|
@@ -34525,7 +34712,7 @@ const ahrefsLimitsAndUsageResponseSchema = z.object({ limits_and_usage: z.object
|
|
|
34525
34712
|
const DEFAULT_BASE_URL$1 = "https://api.ahrefs.com/v3/";
|
|
34526
34713
|
const LIMITS_AND_USAGE_PATH = "subscription-info/limits-and-usage";
|
|
34527
34714
|
const DEFAULT_TIMEOUT_MS$1 = 1e4;
|
|
34528
|
-
const MAX_ACCOUNT_RESPONSE_BYTES =
|
|
34715
|
+
const MAX_ACCOUNT_RESPONSE_BYTES = 65536;
|
|
34529
34716
|
function remaining(limit, used) {
|
|
34530
34717
|
if (limit === null || used === null) return null;
|
|
34531
34718
|
return Math.max(0, limit - used);
|
|
@@ -34652,7 +34839,7 @@ function dataForSeoTaskErrorCode(statusCode) {
|
|
|
34652
34839
|
const DEFAULT_BASE_URL = "https://api.dataforseo.com/";
|
|
34653
34840
|
const USER_DATA_PATH = "v3/appendix/user_data";
|
|
34654
34841
|
const DEFAULT_TIMEOUT_MS = 15e3;
|
|
34655
|
-
const MAX_USER_DATA_RESPONSE_BYTES =
|
|
34842
|
+
const MAX_USER_DATA_RESPONSE_BYTES = 5242880;
|
|
34656
34843
|
var DataForSeoClient = class {
|
|
34657
34844
|
fetch;
|
|
34658
34845
|
credentials;
|
|
@@ -34952,6 +35139,6 @@ function isoDate(daysAgo = 0) {
|
|
|
34952
35139
|
return date.toISOString().slice(0, 10);
|
|
34953
35140
|
}
|
|
34954
35141
|
//#endregion
|
|
34955
|
-
export { AHREFS_API_KEY_ENV, AHREFS_API_KEY_SECRET, AI_REFERRAL_SOURCES, AI_REFERRAL_SOURCES_VERSION, ANALYTICS_PROVIDER_IDS, AUDIT_PAGE_RULE_IDS, AhrefsClient, BING_API_KEY_ENV, BING_API_KEY_SECRET, BROWSER_USER_AGENT, CANNIBAL_MAXIMUM_DOMINANT_SHARE, CANNIBAL_MINIMUM_PAGE_SHARE, CLICKY_SITEKEY_ENV, CLICKY_SITE_KEYS_SECRET, CLOUD_SIGN_IN, CLOUD_URL, CRAWL_SKIP_REASONS, CTR_DEFAULT_LIMIT, CTR_MAX_LIMIT, CTR_MAX_MIN_IMPRESSIONS, DATAFORSEO_CREDENTIAL_SECRET, DATAFORSEO_LOGIN_ENV, DATAFORSEO_PASSWORD_ENV, DEFAULT_MAX_PAGES, DEFAULT_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, DEFAULT_UNREACHED_PAGE_LIMIT, DataForSeoClient, DecisionModelError, DecisionRunner, GSC_FINAL_DATA_LAG_DAYS, GSC_TIME_ZONE, GoogleApiError, INCIDENTS_URL, JsonFileStorageAdapter, LINK_ROLES, LOCAL_BING_CONNECT, LOCAL_SIGN_IN, LOG_MAX_AGE_MS, LOG_MAX_FILE_BYTES, LOG_MAX_TOTAL_BYTES, LocalCredentialsProvider, MANAGED_PROVIDER_SECRET_NAMES, MAX_CRAWL_CONCURRENCY, MAX_CRAWL_DEPTH, MAX_CRAWL_PAGES, MAX_CRAWL_TIMEOUT_MS, MAX_MAX_PAGES, MAX_SEARCH_CONSOLE_EXPORT_FILES, MAX_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, PAGE_TYPES, PROVIDER_SECRET_NAMES, ProviderError, ProviderExtensionRegistry, RESEARCH_IMPORT_COLUMN_KEYS, ResponseSizeLimitError, SEARCH_INTENTS, SEO_CRAWLER_IDENTITY, SEO_CRAWLER_TOKEN, SEO_CRAWLER_USER_AGENT, SEO_PROVIDER_API_VERSION, SEO_VERSION, SERP_TITLE_REFERENCE_WIDTH_PX, SERP_TITLE_WIDTH_PROFILE, STEP_CHANGE_RULES, SeoError, ServiceAccountAccessTokenClient, SessionLedger, TELEMETRY_AGENTS, TELEMETRY_ENDPOINT, TELEMETRY_ERROR_CATEGORIES, TELEMETRY_EVENTS, TELEMETRY_FAILURE_CONTEXTS, TELEMETRY_FAILURE_REASONS, TELEMETRY_NOTICE, TELEMETRY_OPERATIONS, TELEMETRY_REPORTS, TELEMETRY_SCHEMA_VERSION, TELEMETRY_TIMEOUT_MS, TYPESAFE_API_KEY_ENV, agentActionsView, aiReferralSourceForValue, aiReferralsReport, analyticsConnectionLabel, analyticsConnectionProviderId, analyticsConnectionSchema, analyticsProviderDetails, analyticsProviderSchema, analyzeAiReferralRows, analyzeCannibalImportRows, analyzeCannibalRows, analyzeCtrUnderperformersFromRows, analyzeDecay, analyzeIndexCoverageSignals, analyzePageOpportunitiesFromRows, analyzeQueryClustersFromRows, analyzeQuickWinsFromRows, analyzeSecondPageRows, analyzeSegmentRows, analyzeStrikingDistanceRows, analyzeTrafficAnomalyRows, assertCrawlConfigLimits, assertUrlMatchesGscProperty, auditCrawlPages, auditCrawlRequests, auditPage, authStatus, bearerFetch, boundedSitemapInventory, buildAgentFindings, buildQueryClusterReportFromRows, buildTelemetryPayload, cacheDb, cannibalImportReport, cannibalReport, cannibalReportWith, changeWindow, checkDatabaseReadiness, classifyTelemetryFailure, clearCache, cloudAccountFor, cloudBing, cloudCredentials, cloudGoogleEmails, cloudGoogleToken, cloudKeys, cloudLinked, cloudLogout, clusterQueryRows, combineRobotsValues, compareSegmentRows, configSchema, contentCoverageRecommendation, contentCoverageType, contentOptimizationFromPageOpportunities, contentOptimizationReport, countLabel, crawlConfigHash, crawlConfigSchema, crawlDefinitionId, crawlFixToQueueItem, crawlImplementationQueueWorkflow, crawlIssueGroupSchema, crawlIssueSchema, crawlOne, crawlPageSnapshotSchema, crawlReportSchema, crawlReportSummarySchema, crawlRequestObservationSchema, crawlRunId, crawlSite, crawlSkipImpact, crawlSkipReasonCountsFromRecord, crawlStatusOnly, crawlTopFixSchema, crawlerJsonSchemas, crawlerRuleCategorySchema, crawlerRuleIdSchema, crawlerRuleInfoSchema, crawlerRuleRecommendationSchema, crawlerRuleSeveritySchema, crawlerSchemas, createAuthorizedClient, createCrawlReport, createCtrBenchmarkContext, createGoogleAccessTokenClient, createLogger, createPageRenderer, ctrUnderperformersReport, ctrUnderperformersReportWith, decayClusterDrafts, decayComparisonRange, decayingReport, decayingReportWith, deleteAhrefsApiKey, deleteBingApiKey, deleteClickySiteKey, deleteDataForSeoCredentials, deleteManagedProviderSecrets, deleteProviderExtensionAccount, deleteProviderExtensionCredentials, deleteProviderSecret, deleteTokens, deriveBrandTerms, describeInstalledProviderExtension, detectBrandTerms, detectTelemetryAgent, diagnoseCsvFiles, diagnoseProperty, diagnosePropertyWorkflow, effectiveRobotsDirectives, effectiveSnippetControl, ensurePrivateDatabaseFiles, ensureSeoCliDirs, estimateSerpTitleWidth, expectedCtrForPosition, explainRule, fetchCruxFieldData, fetchPage, fetchPageStatus, fetchSearchStatusIncidents, fetchSitemapUrls, fileSize, finalGscDateRange, findOverlappingSearchUpdates, finishCloudLogin, formatRelativeExpiry, ga4MatchReason, ga4PropertyIdFromName, ga4ReportQualityWarnings, ga4RequestCanUseCache, ga4RowsToObjects, getAuthModeStatus, getCacheStats, getClientConfig, getDb, getPrivacySnapshot, getSeoCliPaths, getServiceAccountConfig, getServiceAccountStatus, getTelemetryStatus, getTokenStorageStatus, googleJson, groupCrawlIssues, hasMetaRobotsDirective, hasRule, hasXRobotsDirective, hashKey, indexCoverageSignals, inferTemplateMovement, initializeTelemetry, inspectProviderPackage, inspectSearchConsoleUrl, inspectUrl, installProviderPackage, installedProviderPackageSchema, installedProviderPackagesSchema, internalLinksReport, interpretUpdateCorrelation, isBrandQuery, isNewerVersion, isSkippableReportError, isTelemetryCi, isTelemetryOperation, isTelemetryReport, isoDate, isoWeek, latestFinalGscDate, linkRecover, linkRecoverRecommendation, listAnalyticsAccounts, listAnalyticsDataStreams, listGa4AccountSummaries, listGa4DataStreams, listGoogleAccounts, listInstalledProviderExtensions, listRules, listSearchConsoleSites, listSearchUpdates, listSites, loadInstalledProviderExtensions, loadProviderExtensions, loadProviderPackage, loadSearchConsoleExport, localAccounts, localBing, localCredentialsProvider, localError, localGoogleFetch, localInternalLinksDependencies, localRefreshClicky, localResearchKeys, loginWithLoopback, makeRunId, matchGa4WebStreams, measureCoverage, metaRobotsDirectives, migrateLegacyMacConfig, missingOAuthClientMessage, missingSharedClientLoginMessage, nodeCommandDeps, normalizeBrandText, normalizeClickySiteId, normalizeClickySiteKey, normalizeCrawlConfig, normalizeCrawlSkipReasonCounts, normalizeForCoverage, normalizeHttpUrl, normalizeJavaScriptRenderingMode, normalizeLoadedCrawlReport, normalizeProviderExtensionAccount, normalizedResearchColumnName, notSignedIn, noteCacheWrite, openCloudLink, pageEvidenceRecommendation, pageOpportunitiesReport, parseOlderThan, pendingCloudLogin, performanceAudit, performanceReportIsCacheable, plural, providerCapabilitySchema, providerConnectionFieldSchema, providerExactVersionSchema, providerExtensionIdSchema, providerIdSchema, providerIntegritySchema, providerKindSchema, providerPackageManifestSchema, providerPackageNameSchema, providerPreferenceSchema, providerRegistrationSchema, providerRequestJson, providerRequestText, pruneLogs, publicHttpFetch, queryClusterReport, queryClusterTokens, queryContentCoverageFromPage, queryContentFieldsFromPage, queryFieldCoverage, queryOpportunityRecommendation, queryPageMetrics, queryPageTopQuery, queryPagesMetrics, queryPagesMetricsBatch, queryPagesTopQueries, queryPagesTopQueriesBatch, querySearchAnalytics, quickWinsCsvRows, quickWinsReport, quickWinsReportWith, readAhrefsApiKey, readAllTokens, readBingApiKey, readBoundedResponseText, readClickySiteKey, readClickySiteKeys, readConfig, readDataForSeoCredentials, readInstalledProviderPackages, readJson, readJsonOrUndefined, readOauthClient, readProviderExtensionAccount, readProviderExtensionCredentials, readProviderSecret, readTokens, recommendationForRule, reconcileExportPagesWithCrawl, redirectTrace, refreshAuthToken, refreshPrioritiesCsvFiles, refreshPrioritiesWorkflow, registrableDomain, removeInstalledProviderPackage, renderCrawlCsv, renderCrawlHtml, renderCrawlJunit, renderCrawlMarkdownTickets, renderCrawlPagesCsv, renderCrawlPretty, renderCsv, renderPresentationTablesMarkdown, renderWorkflowMarkdown, reportNarrative, requestHeaders, requirePageVerification, researchImportColumns, resetSeoData, resolveLocalSite, resolveSegmentRanges, resolveSeoCliPaths, reviewObservations, roundedPosition, runAnalyticsProviderLandingPages, runAnalyticsReport, runDoctor, runGa4Report, runProviderExtensionAction, runtimeForProvider, safeProviderAdapterError, saveInstalledProviderPackage, searchAnalyticsRows, searchUpdatesFromIncidents, secondPage, secondPageWith, segmentComparisonRange, segmentImpact, segmentRangeDays, seoErrorEnvelope, sessionSourceInListFilter, setActiveGoogleAccount, setTelemetryEnabled, setTokenStorageMode, shiftIsoDate, shouldExcludeBrandQuery, siteStartUrl, startCloudLogin, strikingDistance, strikingDistanceWith, summarizeCrawlReport, pageTechnicalSignals as technicalPageSignals, telemetryErrorCategory, toSeoError, tokenSchema, tokenStoreSchema, topFixes, trackTelemetryCommandFailed, trackTelemetryReportComplete, trackTelemetryReportFailed, trackTelemetryReportStart, trackTelemetrySetupComplete, trafficAnomaly, trafficAnomalyWith, typesafeDecisionModel, unavailableSegmentImpactReport, unicodeTokens, uninstallProviderExtension, updateCorrelation, updateNotice, updatePostmortemCsvFiles, updatePostmortemWorkflow, urlInspectionQuota, validateResearchImportColumnSources, verifyAnalyticsProvider, verifyProviderExtension, verifyQueryContent, verifyQueryContentWith, withAgentReportContract, withRankingHistory, workflowPresentation, writeAhrefsApiKey, writeBingApiKey, writeClickySiteKey, writeConfig, writeDataForSeoCredentials, writeInstalledProviderPackages, writeOauthClient, writeProviderExtensionAccount, writeProviderExtensionCredentials, writeProviderSecret, writeToCache, writeTokens, xRobotsDirectives };
|
|
35142
|
+
export { AHREFS_API_KEY_ENV, AHREFS_API_KEY_SECRET, AI_REFERRAL_SOURCES, AI_REFERRAL_SOURCES_VERSION, ANALYTICS_PROVIDER_IDS, AUDIT_PAGE_RULE_IDS, AhrefsClient, BING_API_KEY_ENV, BING_API_KEY_SECRET, BROWSER_USER_AGENT, CANNIBAL_MAXIMUM_DOMINANT_SHARE, CANNIBAL_MINIMUM_PAGE_SHARE, CLICKY_SITEKEY_ENV, CLICKY_SITE_KEYS_SECRET, CLOUD_SIGN_IN, CLOUD_URL, CRAWL_SKIP_REASONS, CTR_DEFAULT_LIMIT, CTR_MAX_LIMIT, CTR_MAX_MIN_IMPRESSIONS, DATAFORSEO_CREDENTIAL_SECRET, DATAFORSEO_LOGIN_ENV, DATAFORSEO_PASSWORD_ENV, DEFAULT_MAX_PAGES, DEFAULT_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, DEFAULT_UNREACHED_PAGE_LIMIT, DataForSeoClient, DecisionModelError, DecisionRunner, GSC_FINAL_DATA_LAG_DAYS, GSC_TIME_ZONE, GoogleApiError, INCIDENTS_URL, JsonFileStorageAdapter, LINK_ROLES, LOCAL_BING_CONNECT, LOCAL_SIGN_IN, LOG_MAX_AGE_MS, LOG_MAX_FILE_BYTES, LOG_MAX_TOTAL_BYTES, LocalCredentialsProvider, MANAGED_PROVIDER_SECRET_NAMES, MAX_CRAWL_CONCURRENCY, MAX_CRAWL_DEPTH, MAX_CRAWL_PAGES, MAX_CRAWL_TIMEOUT_MS, MAX_MAX_PAGES, MAX_SEARCH_CONSOLE_EXPORT_FILES, MAX_SEARCH_CONSOLE_EXPORT_ROW_LIMIT, PAGE_TYPES, PROVIDER_SECRET_NAMES, ProviderError, ProviderExtensionRegistry, RESEARCH_IMPORT_COLUMN_KEYS, ResponseSizeLimitError, SEARCH_INTENTS, SEO_CRAWLER_IDENTITY, SEO_CRAWLER_TOKEN, SEO_CRAWLER_USER_AGENT, SEO_PROVIDER_API_VERSION, SEO_VERSION, SERP_TITLE_REFERENCE_WIDTH_PX, SERP_TITLE_WIDTH_PROFILE, STEP_CHANGE_RULES, SeoError, ServiceAccountAccessTokenClient, SessionLedger, TELEMETRY_AGENTS, TELEMETRY_ENDPOINT, TELEMETRY_ERROR_CATEGORIES, TELEMETRY_EVENTS, TELEMETRY_FAILURE_CONTEXTS, TELEMETRY_FAILURE_REASONS, TELEMETRY_NOTICE, TELEMETRY_OPERATIONS, TELEMETRY_REPORTS, TELEMETRY_SCHEMA_VERSION, TELEMETRY_TIMEOUT_MS, TYPESAFE_API_KEY_ENV, agentActionsView, aiReferralSourceForValue, aiReferralsReport, analyticsConnectionLabel, analyticsConnectionProviderId, analyticsConnectionSchema, analyticsProviderDetails, analyticsProviderSchema, analyzeAiReferralRows, analyzeCannibalImportRows, analyzeCannibalRows, analyzeCtrUnderperformersFromRows, analyzeDecay, analyzeIndexCoverageSignals, analyzePageOpportunitiesFromRows, analyzeQueryClustersFromRows, analyzeQuickWinsFromRows, analyzeSecondPageRows, analyzeSegmentRows, analyzeStrikingDistanceRows, analyzeTrafficAnomalyRows, assertCrawlConfigLimits, assertUrlMatchesGscProperty, auditCrawlPages, auditCrawlRequests, auditPage, authStatus, bearerFetch, boundedSitemapInventory, buildAgentFindings, buildQueryClusterReportFromRows, buildTelemetryPayload, cacheDb, cannibalImportReport, cannibalReport, cannibalReportWith, changeWindow, checkDatabaseReadiness, classifyTelemetryFailure, clearCache, cloudAccountFor, cloudBing, cloudCredentials, cloudGoogleEmails, cloudGoogleToken, cloudKeys, cloudLinked, cloudLogout, cloudWeek, clusterQueryRows, combineRobotsValues, compareSegmentRows, configSchema, contentCoverageRecommendation, contentCoverageType, contentOptimizationFromPageOpportunities, contentOptimizationReport, countLabel, crawlConfigHash, crawlConfigSchema, crawlDefinitionId, crawlFixToQueueItem, crawlImplementationQueueWorkflow, crawlIssueGroupSchema, crawlIssueSchema, crawlOne, crawlPageSnapshotSchema, crawlReportSchema, crawlReportSummarySchema, crawlRequestObservationSchema, crawlRunId, crawlSite, crawlSkipImpact, crawlSkipReasonCountsFromRecord, crawlStatusOnly, crawlTopFixSchema, crawlerJsonSchemas, crawlerRuleCategorySchema, crawlerRuleIdSchema, crawlerRuleInfoSchema, crawlerRuleRecommendationSchema, crawlerRuleSeveritySchema, crawlerSchemas, createAuthorizedClient, createCrawlReport, createCtrBenchmarkContext, createGoogleAccessTokenClient, createLogger, createPageRenderer, ctrUnderperformersReport, ctrUnderperformersReportWith, decayClusterDrafts, decayComparisonRange, decayingReport, decayingReportWith, deleteAhrefsApiKey, deleteBingApiKey, deleteClickySiteKey, deleteDataForSeoCredentials, deleteManagedProviderSecrets, deleteProviderExtensionAccount, deleteProviderExtensionCredentials, deleteProviderSecret, deleteTokens, deriveBrandTerms, describeInstalledProviderExtension, detectBrandTerms, detectTelemetryAgent, diagnoseCsvFiles, diagnoseProperty, diagnosePropertyWorkflow, effectiveRobotsDirectives, effectiveSnippetControl, ensurePrivateDatabaseFiles, ensureSeoCliDirs, estimateSerpTitleWidth, expectedCtrForPosition, explainRule, fetchCruxFieldData, fetchPage, fetchPageStatus, fetchSearchStatusIncidents, fetchSitemapUrls, fileSize, finalGscDateRange, findOverlappingSearchUpdates, finishCloudLogin, formatRelativeExpiry, ga4MatchReason, ga4PropertyIdFromName, ga4ReportQualityWarnings, ga4RequestCanUseCache, ga4RowsToObjects, getAuthModeStatus, getCacheStats, getClientConfig, getDb, getPrivacySnapshot, getSeoCliPaths, getServiceAccountConfig, getServiceAccountStatus, getTelemetryStatus, getTokenStorageStatus, googleJson, groupCrawlIssues, hasMetaRobotsDirective, hasRule, hasXRobotsDirective, hashKey, indexCoverageSignals, inferTemplateMovement, initializeTelemetry, inspectProviderPackage, inspectSearchConsoleUrl, inspectUrl, installProviderPackage, installedProviderPackageSchema, installedProviderPackagesSchema, internalLinksReport, interpretUpdateCorrelation, isBrandQuery, isNewerVersion, isSkippableReportError, isTelemetryCi, isTelemetryOperation, isTelemetryReport, isoDate, isoWeek, latestFinalGscDate, linkRecover, linkRecoverRecommendation, listAnalyticsAccounts, listAnalyticsDataStreams, listGa4AccountSummaries, listGa4DataStreams, listGoogleAccounts, listInstalledProviderExtensions, listRules, listSearchConsoleSites, listSearchUpdates, listSites, loadInstalledProviderExtensions, loadProviderExtensions, loadProviderPackage, loadSearchConsoleExport, localAccounts, localBing, localCredentialsProvider, localError, localGoogleFetch, localInternalLinksDependencies, localRefreshClicky, localResearchKeys, loginWithLoopback, makeRunId, matchGa4WebStreams, measureCoverage, metaRobotsDirectives, migrateLegacyMacConfig, missingOAuthClientMessage, missingSharedClientLoginMessage, nodeCommandDeps, normalizeBrandText, normalizeClickySiteId, normalizeClickySiteKey, normalizeCrawlConfig, normalizeCrawlSkipReasonCounts, normalizeForCoverage, normalizeHttpUrl, normalizeJavaScriptRenderingMode, normalizeLoadedCrawlReport, normalizeProviderExtensionAccount, normalizedResearchColumnName, notSignedIn, noteCacheWrite, openCloudLink, pageEvidenceRecommendation, pageOpportunitiesReport, parseOlderThan, pendingCloudLogin, performanceAudit, performanceReportIsCacheable, plural, providerCapabilitySchema, providerConnectionFieldSchema, providerExactVersionSchema, providerExtensionIdSchema, providerIdSchema, providerIntegritySchema, providerKindSchema, providerPackageManifestSchema, providerPackageNameSchema, providerPreferenceSchema, providerRegistrationSchema, providerRequestJson, providerRequestText, pruneLogs, publicHttpFetch, queryClusterReport, queryClusterTokens, queryContentCoverageFromPage, queryContentFieldsFromPage, queryFieldCoverage, queryOpportunityRecommendation, queryPageMetrics, queryPageTopQuery, queryPagesMetrics, queryPagesMetricsBatch, queryPagesTopQueries, queryPagesTopQueriesBatch, querySearchAnalytics, quickWinsCsvRows, quickWinsReport, quickWinsReportWith, readAhrefsApiKey, readAllTokens, readBingApiKey, readBoundedResponseText, readClickySiteKey, readClickySiteKeys, readConfig, readDataForSeoCredentials, readInstalledProviderPackages, readJson, readJsonOrUndefined, readOauthClient, readProviderExtensionAccount, readProviderExtensionCredentials, readProviderSecret, readTokens, recommendationForRule, reconcileExportPagesWithCrawl, redirectTrace, refreshAuthToken, refreshPrioritiesCsvFiles, refreshPrioritiesWorkflow, registrableDomain, removeInstalledProviderPackage, renderCrawlCsv, renderCrawlHtml, renderCrawlJunit, renderCrawlMarkdownTickets, renderCrawlPagesCsv, renderCrawlPretty, renderCsv, renderPresentationTablesMarkdown, renderWorkflowMarkdown, reportNarrative, requestHeaders, requirePageVerification, researchImportColumns, resetSeoData, resolveLocalSite, resolveSegmentRanges, resolveSeoCliPaths, reviewObservations, roundedPosition, runAnalyticsProviderLandingPages, runAnalyticsReport, runDoctor, runGa4Report, runProviderExtensionAction, runtimeForProvider, safeProviderAdapterError, saveInstalledProviderPackage, searchAnalyticsRows, searchUpdatesFromIncidents, secondPage, secondPageWith, segmentComparisonRange, segmentImpact, segmentRangeDays, seoErrorEnvelope, sessionSourceInListFilter, setActiveGoogleAccount, setTelemetryEnabled, setTokenStorageMode, shiftIsoDate, shouldExcludeBrandQuery, siteStartUrl, startCloudLogin, strikingDistance, strikingDistanceWith, summarizeCrawlReport, pageTechnicalSignals as technicalPageSignals, telemetryErrorCategory, toSeoError, tokenSchema, tokenStoreSchema, topFixes, trackTelemetryCommandFailed, trackTelemetryReportComplete, trackTelemetryReportFailed, trackTelemetryReportStart, trackTelemetrySetupComplete, trafficAnomaly, trafficAnomalyWith, typesafeDecisionModel, unavailableSegmentImpactReport, unicodeTokens, uninstallProviderExtension, updateCorrelation, updateNotice, updatePostmortemCsvFiles, updatePostmortemWorkflow, urlInspectionQuota, validateResearchImportColumnSources, verifyAnalyticsProvider, verifyProviderExtension, verifyQueryContent, verifyQueryContentWith, withAgentReportContract, withRankingHistory, workflowPresentation, writeAhrefsApiKey, writeBingApiKey, writeClickySiteKey, writeConfig, writeDataForSeoCredentials, writeInstalledProviderPackages, writeOauthClient, writeProviderExtensionAccount, writeProviderExtensionCredentials, writeProviderSecret, writeToCache, writeTokens, xRobotsDirectives };
|
|
34956
35143
|
|
|
34957
35144
|
//# sourceMappingURL=index.js.map
|